diff options
37 files changed, 699 insertions, 134 deletions
diff --git a/build/utils.js b/build/utils.js index b45ffc16..c094c3c8 100644 --- a/build/utils.js +++ b/build/utils.js @@ -27,16 +27,17 @@ exports.cssLoaders = function (options) { return [ { test: /\.(post)?css$/, - use: generateLoaders(['css-loader']), + use: generateLoaders(['css-loader', 'postcss-loader']), }, { test: /\.less$/, - use: generateLoaders(['css-loader', 'less-loader']), + use: generateLoaders(['css-loader', 'postcss-loader', 'less-loader']), }, { test: /\.sass$/, use: generateLoaders([ 'css-loader', + 'postcss-loader', { loader: 'sass-loader', options: { @@ -47,11 +48,11 @@ exports.cssLoaders = function (options) { }, { test: /\.scss$/, - use: generateLoaders(['css-loader', 'sass-loader']) + use: generateLoaders(['css-loader', 'postcss-loader', 'sass-loader']) }, { test: /\.styl(us)?$/, - use: generateLoaders(['css-loader', 'stylus-loader']), + use: generateLoaders(['css-loader', 'postcss-loader', 'stylus-loader']), }, ] } diff --git a/package.json b/package.json index 28f3beba..0f6ae9c8 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "nightwatch": "^0.9.8", "opn": "^4.0.2", "ora": "^0.3.0", + "postcss-loader": "^3.0.0", "raw-loader": "^0.5.1", "sass": "^1.17.3", "sass-loader": "git://github.com/webpack-contrib/sass-loader", diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..88752c6c --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + require('autoprefixer') + ] +} @@ -89,7 +89,11 @@ export default { sitename () { return this.$store.state.instance.name }, chat () { return this.$store.state.chat.channel.state === 'joined' }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, - showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel }, + showInstanceSpecificPanel () { + return this.$store.state.instance.showInstanceSpecificPanel && + !this.$store.state.config.hideISP && + this.$store.state.instance.instanceSpecificPanelContent + }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, isMobileLayout () { return this.$store.state.interface.mobileLayout } }, diff --git a/src/App.vue b/src/App.vue index bf6e62e2..719e00a4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -107,6 +107,7 @@ :floating="true" class="floating-chat mobile-hidden" /> + <MobilePostStatusModal /> <UserReportingModal /> <portal-target name="modal" /> </div> diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 568e9359..8a02174e 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -87,6 +87,7 @@ &-expanded-content { flex: 1; margin-left: 0.7em; + min-width: 0; } } </style> diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index 2ec72729..2d05ad39 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -16,6 +16,16 @@ const ExtraButtons = { this.$store.dispatch('unpinStatus', this.status.id) .then(() => this.$emit('onSuccess')) .catch(err => this.$emit('onError', err.error.error)) + }, + muteConversation () { + this.$store.dispatch('muteConversation', this.status.id) + .then(() => this.$emit('onSuccess')) + .catch(err => this.$emit('onError', err.error.error)) + }, + unmuteConversation () { + this.$store.dispatch('unmuteConversation', this.status.id) + .then(() => this.$emit('onSuccess')) + .catch(err => this.$emit('onError', err.error.error)) } }, computed: { @@ -30,9 +40,6 @@ const ExtraButtons = { }, canPin () { return this.ownStatus && (this.status.visibility === 'public' || this.status.visibility === 'unlisted') - }, - enabled () { - return this.canPin || this.canDelete } } } diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index cdad1666..564d34df 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -1,6 +1,5 @@ <template> <v-popover - v-if="enabled" trigger="click" placement="top" class="extra-button-popover" @@ -10,6 +9,20 @@ <div slot="popover"> <div class="dropdown-menu"> <button + v-if="!status.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="status.muted" + class="dropdown-item dropdown-item-icon" + @click.prevent="unmuteConversation" + > + <i class="icon-eye-off" /><span>{{ $t("status.unmute_conversation") }}</span> + </button> + <button v-if="!status.pinned && canPin" v-close-popover class="dropdown-item dropdown-item-icon" diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue index 6adfb76c..6169d294 100644 --- a/src/components/gallery/gallery.vue +++ b/src/components/gallery/gallery.vue @@ -61,13 +61,17 @@ } &.contain-fit { - img, video { + img, + video, + canvas { object-fit: contain; } } &.cover-fit { - img, video { + img, + video, + canvas { object-fit: cover; } } diff --git a/src/components/instance_specific_panel/instance_specific_panel.js b/src/components/instance_specific_panel/instance_specific_panel.js index 9bb5e945..09e3d055 100644 --- a/src/components/instance_specific_panel/instance_specific_panel.js +++ b/src/components/instance_specific_panel/instance_specific_panel.js @@ -2,9 +2,6 @@ const InstanceSpecificPanel = { computed: { instanceSpecificPanelContent () { return this.$store.state.instance.instanceSpecificPanelContent - }, - show () { - return !this.$store.state.config.hideISP } } } diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue index a7cf6b48..7448ca06 100644 --- a/src/components/instance_specific_panel/instance_specific_panel.vue +++ b/src/components/instance_specific_panel/instance_specific_panel.vue @@ -1,8 +1,5 @@ <template> - <div - v-if="show" - class="instance-specific-panel" - > + <div class="instance-specific-panel"> <div class="panel panel-default"> <div class="panel-body"> <!-- eslint-disable vue/no-v-html --> @@ -14,6 +11,3 @@ </template> <script src="./instance_specific_panel.js" ></script> - -<style lang="scss"> -</style> diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js index d4e3cc17..1f8a9de9 100644 --- a/src/components/interactions/interactions.js +++ b/src/components/interactions/interactions.js @@ -13,8 +13,8 @@ const Interactions = { } }, methods: { - onModeSwitch (index, dataset) { - this.filterMode = tabModeDict[dataset.filter] + onModeSwitch (key) { + this.filterMode = tabModeDict[key] } }, components: { diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue index d71c99d5..08cee343 100644 --- a/src/components/interactions/interactions.vue +++ b/src/components/interactions/interactions.vue @@ -10,18 +10,15 @@ :on-switch="onModeSwitch" > <span - data-tab-dummy - data-filter="mentions" + key="mentions" :label="$t('nav.mentions')" /> <span - data-tab-dummy - data-filter="likes+repeats" + key="likes+repeats" :label="$t('interactions.favs_repeats')" /> <span - data-tab-dummy - data-filter="follows" + key="follows" :label="$t('interactions.follows')" /> </tab-switcher> diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js index 2f6da55e..444aafbe 100644 --- a/src/components/link-preview/link-preview.js +++ b/src/components/link-preview/link-preview.js @@ -5,6 +5,11 @@ const LinkPreview = { 'size', 'nsfw' ], + data () { + return { + imageLoaded: false + } + }, computed: { useImage () { // Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid @@ -15,6 +20,15 @@ const LinkPreview = { useDescription () { return this.card.description && /\S/.test(this.card.description) } + }, + created () { + if (this.useImage) { + const newImg = new Image() + newImg.onload = () => { + this.imageLoaded = true + } + newImg.src = this.card.image + } } } diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue index 493774c2..69171977 100644 --- a/src/components/link-preview/link-preview.vue +++ b/src/components/link-preview/link-preview.vue @@ -7,7 +7,7 @@ rel="noopener" > <div - v-if="useImage" + v-if="useImage && imageLoaded" class="card-image" :class="{ 'small-image': size === 'small' }" > diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index 9b341a3b..c2bb76ee 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -1,14 +1,12 @@ import SideDrawer from '../side_drawer/side_drawer.vue' import Notifications from '../notifications/notifications.vue' -import MobilePostStatusModal from '../mobile_post_status_modal/mobile_post_status_modal.vue' import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' import GestureService from '../../services/gesture_service/gesture_service' const MobileNav = { components: { SideDrawer, - Notifications, - MobilePostStatusModal + Notifications }, data: () => ({ notificationsCloseGesture: undefined, diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index f67b7ff8..d1c24e56 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -70,7 +70,6 @@ ref="sideDrawer" :logout="logout" /> - <MobilePostStatusModal /> </div> </template> diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue index 5db7584b..b6d7d3ba 100644 --- a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue +++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue @@ -34,14 +34,19 @@ @import '../../_variables.scss'; .post-form-modal-view { - max-height: 100%; - display: block; + align-items: flex-start; } .post-form-modal-panel { flex-shrink: 0; - margin: 25% 0 4em 0; + margin-top: 25%; + margin-bottom: 2em; width: 100%; + max-width: 700px; + + @media (orientation: landscape) { + margin-top: 8%; + } } .new-status-button { diff --git a/src/components/search/search.js b/src/components/search/search.js index b434e127..8e903052 100644 --- a/src/components/search/search.js +++ b/src/components/search/search.js @@ -75,8 +75,8 @@ const Search = { const length = this[tabName].length return length === 0 ? '' : ` (${length})` }, - onResultTabSwitch (_index, dataset) { - this.currenResultTab = dataset.filter + onResultTabSwitch (key) { + this.currenResultTab = key }, getActiveTab () { if (this.visibleStatuses.length > 0) { diff --git a/src/components/search/search.vue b/src/components/search/search.vue index 4350e672..746bbaa2 100644 --- a/src/components/search/search.vue +++ b/src/components/search/search.vue @@ -31,21 +31,18 @@ <tab-switcher ref="tabSwitcher" :on-switch="onResultTabSwitch" - :custom-active="currenResultTab" + :active-tab="currenResultTab" > <span - data-tab-dummy - data-filter="statuses" + key="statuses" :label="$t('user_card.statuses') + resultCount('visibleStatuses')" /> <span - data-tab-dummy - data-filter="people" + key="people" :label="$t('search.people') + resultCount('users')" /> <span - data-tab-dummy - data-filter="hashtags" + key="hashtags" :label="$t('search.hashtags') + resultCount('hashtags')" /> </tab-switcher> diff --git a/src/components/status/status.js b/src/components/status/status.js index 3c172e5b..502d9583 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -335,7 +335,7 @@ const Status = { return } } - if (target.className.match(/hashtag/)) { + if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) { // Extract tag name from link url const tag = extractTagFromUrl(target.href) if (tag) { diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js index a5fe019c..08d5d08f 100644 --- a/src/components/tab_switcher/tab_switcher.js +++ b/src/components/tab_switcher/tab_switcher.js @@ -4,12 +4,22 @@ import './tab_switcher.scss' export default Vue.component('tab-switcher', { name: 'TabSwitcher', - props: ['renderOnlyFocused', 'onSwitch', 'customActive'], + props: ['renderOnlyFocused', 'onSwitch', 'activeTab'], data () { return { active: this.$slots.default.findIndex(_ => _.tag) } }, + computed: { + activeIndex () { + // In case of controlled component + if (this.activeTab) { + return this.$slots.default.findIndex(slot => this.activeTab === slot.key) + } else { + return this.active + } + } + }, beforeUpdate () { const currentSlot = this.$slots.default[this.active] if (!currentSlot.tag) { @@ -17,21 +27,13 @@ export default Vue.component('tab-switcher', { } }, methods: { - activateTab (index, dataset) { + activateTab (index) { return () => { if (typeof this.onSwitch === 'function') { - this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset) + this.onSwitch.call(null, this.$slots.default[index].key) } this.active = index } - }, - isActiveTab (index) { - const customActiveIndex = this.$slots.default.findIndex(slot => { - const dataFilter = slot.data && slot.data.attrs && slot.data.attrs['data-filter'] - return this.customActive && this.customActive === dataFilter - }) - - return customActiveIndex > -1 ? customActiveIndex === index : index === this.active } }, render (h) { @@ -41,13 +43,13 @@ export default Vue.component('tab-switcher', { const classesTab = ['tab'] const classesWrapper = ['tab-wrapper'] - if (this.isActiveTab(index)) { + if (this.activeIndex === index) { classesTab.push('active') classesWrapper.push('active') } if (slot.data.attrs.image) { return ( - <div class={ classesWrapper.join(' ')}> + <div class={classesWrapper.join(' ')}> <button disabled={slot.data.attrs.disabled} onClick={this.activateTab(index)} @@ -59,7 +61,7 @@ export default Vue.component('tab-switcher', { ) } return ( - <div class={ classesWrapper.join(' ')}> + <div class={classesWrapper.join(' ')}> <button disabled={slot.data.attrs.disabled} onClick={this.activateTab(index)} @@ -71,7 +73,7 @@ export default Vue.component('tab-switcher', { const contents = this.$slots.default.map((slot, index) => { if (!slot.tag) return - const active = index === this.active + const active = this.activeIndex === index if (this.renderOnlyFocused) { return active ? <div class="active">{slot}</div> diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 39b99dac..00055707 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -22,21 +22,23 @@ const FriendList = withLoadMore({ additionalPropNames: ['userId'] })(List) +const defaultTabKey = 'statuses' + const UserProfile = { data () { return { error: false, - userId: null + userId: null, + tab: defaultTabKey } }, created () { - // Make sure that timelines used in this page are empty - this.cleanUp() const routeParams = this.$route.params this.load(routeParams.name || routeParams.id) + this.tab = get(this.$route, 'query.tab', defaultTabKey) }, destroyed () { - this.cleanUp() + this.stopFetching() }, computed: { timeline () { @@ -67,17 +69,36 @@ const UserProfile = { }, methods: { load (userNameOrId) { + const startFetchingTimeline = (timeline, userId) => { + // Clear timeline only if load another user's profile + if (userId !== this.$store.state.statuses.timelines[timeline].userId) { + this.$store.commit('clearTimeline', { timeline }) + } + this.$store.dispatch('startFetchingTimeline', { timeline, userId }) + } + + const loadById = (userId) => { + this.userId = userId + startFetchingTimeline('user', userId) + startFetchingTimeline('media', userId) + if (this.isUs) { + startFetchingTimeline('favorites', userId) + } + // Fetch all pinned statuses immediately + this.$store.dispatch('fetchPinnedStatuses', userId) + } + + // Reset view + this.userId = null + this.error = false + // Check if user data is already loaded in store const user = this.$store.getters.findUser(userNameOrId) if (user) { - this.userId = user.id - this.fetchTimelines() + loadById(user.id) } else { this.$store.dispatch('fetchUser', userNameOrId) - .then(({ id }) => { - this.userId = id - this.fetchTimelines() - }) + .then(({ id }) => loadById(id)) .catch((reason) => { const errorMessage = get(reason, 'error.error') if (errorMessage === 'No user with such user_id') { // Known error @@ -90,40 +111,33 @@ const UserProfile = { }) } }, - fetchTimelines () { - const userId = this.userId - this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId }) - this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId }) - if (this.isUs) { - this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId }) - } - // Fetch all pinned statuses immediately - this.$store.dispatch('fetchPinnedStatuses', userId) - }, - cleanUp () { + stopFetching () { this.$store.dispatch('stopFetching', 'user') this.$store.dispatch('stopFetching', 'favorites') this.$store.dispatch('stopFetching', 'media') - this.$store.commit('clearTimeline', { timeline: 'user' }) - this.$store.commit('clearTimeline', { timeline: 'favorites' }) - this.$store.commit('clearTimeline', { timeline: 'media' }) + }, + switchUser (userNameOrId) { + this.stopFetching() + this.load(userNameOrId) + }, + onTabSwitch (tab) { + this.tab = tab + this.$router.replace({ query: { tab } }) } }, watch: { '$route.params.id': function (newVal) { if (newVal) { - this.cleanUp() - this.load(newVal) + this.switchUser(newVal) } }, '$route.params.name': function (newVal) { if (newVal) { - this.cleanUp() - this.load(newVal) + this.switchUser(newVal) } }, - $route () { - this.$refs.tabSwitcher.activateTab(0)() + '$route.query': function (newVal) { + this.tab = newVal.tab || defaultTabKey } }, components: { diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index cffa28f1..42516916 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -12,22 +12,24 @@ rounded="top" /> <tab-switcher - ref="tabSwitcher" + :active-tab="tab" :render-only-focused="true" + :on-switch="onTabSwitch" > - <div :label="$t('user_card.statuses')"> - <Timeline - :count="user.statuses_count" - :embedded="true" - :title="$t('user_profile.timeline_title')" - :timeline="timeline" - timeline-name="user" - :user-id="userId" - :pinned-status-ids="user.pinnedStatusIds" - /> - </div> + <Timeline + key="statuses" + :label="$t('user_card.statuses')" + :count="user.statuses_count" + :embedded="true" + :title="$t('user_profile.timeline_title')" + :timeline="timeline" + timeline-name="user" + :user-id="userId" + :pinned-status-ids="user.pinnedStatusIds" + /> <div v-if="followsTabVisible" + key="followees" :label="$t('user_card.followees')" :disabled="!user.friends_count" > @@ -42,6 +44,7 @@ </div> <div v-if="followersTabVisible" + key="followers" :label="$t('user_card.followers')" :disabled="!user.followers_count" > @@ -58,6 +61,7 @@ </FollowerList> </div> <Timeline + key="media" :label="$t('user_card.media')" :disabled="!media.visibleStatuses.length" :embedded="true" @@ -68,6 +72,7 @@ /> <Timeline v-if="isUs" + key="favorites" :label="$t('user_card.favorites')" :disabled="!favorites.visibleStatuses.length" :embedded="true" diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index f8100257..8fab6c4d 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -21,7 +21,8 @@ const WhoToFollow = { name: i.display_name, screen_name: i.acct, profile_image_url: i.avatar || '/images/avi.png', - profile_image_url_original: i.avatar || '/images/avi.png' + profile_image_url_original: i.avatar || '/images/avi.png', + statusnet_profile_url: i.url } this.users.push(user) diff --git a/src/i18n/en.json b/src/i18n/en.json index 60a3e284..6a9af55c 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -262,7 +262,7 @@ "loop_video": "Loop videos", "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", "mutes_tab": "Mutes", - "play_videos_in_modal": "Play videos directly in the media viewer", + "play_videos_in_modal": "Play videos in a popup frame", "use_contain_fit": "Don't crop the attachment in thumbnails", "name": "Name", "name_bio": "Name & Bio", @@ -508,7 +508,9 @@ "pinned": "Pinned", "delete_confirm": "Do you really want to delete this status?", "reply_to": "Reply to", - "replies_list": "Replies:" + "replies_list": "Replies:", + "mute_conversation": "Mute conversation", + "unmute_conversation": "Unmute conversation" }, "user_card": { "approve": "Approve", diff --git a/src/i18n/fi.json b/src/i18n/fi.json index f4179495..e7ed5408 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -278,8 +278,15 @@ "status": { "favorites": "Tykkäykset", "repeats": "Toistot", + "delete": "Poista", + "pin": "Kiinnitä profiiliisi", + "unpin": "Poista kiinnitys", + "pinned": "Kiinnitetty", + "delete_confirm": "Haluatko varmasti postaa viestin?", "reply_to": "Vastaus", - "replies_list": "Vastaukset:" + "replies_list": "Vastaukset:", + "mute_conversation": "Hiljennä keskustelu", + "unmute_conversation": "Poista hiljennys" }, "user_card": { "approve": "Hyväksy", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index c77f28a6..b4c6015d 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -78,6 +78,7 @@ "timeline": "タイムライン", "twkn": "つながっているすべてのネットワーク", "user_search": "ユーザーをさがす", + "search": "さがす", "who_to_follow": "おすすめユーザー", "preferences": "せってい" }, @@ -105,6 +106,9 @@ "expired": "いれふだは {0} まえに、おわりました", "not_enough_options": "ユニークなオプションが、たりません" }, + "stickers": { + "add_sticker": "ステッカーをふやす" + }, "interactions": { "favs_repeats": "リピートとおきにいり", "follows": "あたらしいフォロー", @@ -506,7 +510,9 @@ "pinned": "ピンどめ", "delete_confirm": "ほんとうに、このステータスを、けしてもいいですか?", "reply_to": "へんしん:", - "replies_list": "へんしん:" + "replies_list": "へんしん:", + "mute_conversation": "スレッドをミュートする", + "unmute_conversation": "スレッドをミュートするのをやめる" }, "user_card": { "approve": "うけいれ", @@ -531,6 +537,8 @@ "remote_follow": "リモートフォロー", "report": "つうほう", "statuses": "ステータス", + "subscribe": "サブスクライブ", + "unsubscribe": "サブスクライブをやめる", "unblock": "ブロックをやめる", "unblock_progress": "ブロックをとりけしています...", "block_progress": "ブロックしています...", @@ -595,5 +603,12 @@ "GiB": "GiB", "TiB": "TiB" } + }, + "search": { + "people": "ひとびと", + "hashtags": "ハッシュタグ", + "person_talking": "{count} にんが、はなしています", + "people_talking": "{count} にんが、はなしています", + "no_results": "みつかりませんでした" } } diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json index 992a2fa6..42bb53d4 100644 --- a/src/i18n/ja_pedantic.json +++ b/src/i18n/ja_pedantic.json @@ -78,6 +78,7 @@ "timeline": "タイムライン", "twkn": "接続しているすべてのネットワーク", "user_search": "ユーザーを探す", + "search": "検索", "who_to_follow": "おすすめユーザー", "preferences": "設定" }, @@ -105,6 +106,9 @@ "expired": "投票は {0} 前に終了しました", "not_enough_options": "相異なる選択肢が不足しています" }, + "stickers": { + "add_sticker": "ステッカーを追加" + }, "interactions": { "favs_repeats": "リピートとお気に入り", "follows": "新しいフォロワー", @@ -506,7 +510,9 @@ "pinned": "ピン留め", "delete_confirm": "本当にこのステータスを削除してもよろしいですか?", "reply_to": "返信", - "replies_list": "返信:" + "replies_list": "返信:", + "mute_conversation": "スレッドをミュート", + "unmute_conversation": "スレッドのミュートを解除" }, "user_card": { "approve": "受け入れ", @@ -531,6 +537,8 @@ "remote_follow": "リモートフォロー", "report": "通報", "statuses": "ステータス", + "subscribe": "購読", + "unsubscribe": "購読を解除", "unblock": "ブロック解除", "unblock_progress": "ブロックを解除しています...", "block_progress": "ブロックしています...", @@ -595,5 +603,12 @@ "GiB": "GiB", "TiB": "TiB" } + }, + "search": { + "people": "人々", + "hashtags": "ハッシュタグ", + "person_talking": "{count} 人が話しています", + "people_talking": "{count} 人が話しています", + "no_results": "見つかりませんでした" } } diff --git a/src/i18n/messages.js b/src/i18n/messages.js index 404a4079..ca02ae48 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -32,6 +32,7 @@ const messages = { pt: require('./pt.json'), ro: require('./ro.json'), ru: require('./ru.json'), + te: require('./te.json'), zh: require('./zh.json') } diff --git a/src/i18n/te.json b/src/i18n/te.json new file mode 100644 index 00000000..f0953d97 --- /dev/null +++ b/src/i18n/te.json @@ -0,0 +1,352 @@ +{ + "chat.title": "చాట్", + "features_panel.chat": "చాట్", + "features_panel.gopher": "గోఫర్", + "features_panel.media_proxy": "మీడియా ప్రాక్సీ", + "features_panel.scope_options": "స్కోప్ ఎంపికలు", + "features_panel.text_limit": "వచన పరిమితి", + "features_panel.title": "లక్షణాలు", + "features_panel.who_to_follow": "ఎవరిని అనుసరించాలి", + "finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం", + "finder.find_user": "వినియోగదారుని కనుగొనండి", + "general.apply": "వర్తించు", + "general.submit": "సమర్పించు", + "general.more": "మరిన్ని", + "general.generic_error": "ఒక తప్పిదం సంభవించినది", + "general.optional": "ఐచ్చికం", + "image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి", + "image_cropper.save": "దాచు", + "image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి", + "image_cropper.cancel": "రద్దుచేయి", + "login.login": "లాగిన్", + "login.description": "OAuth తో లాగిన్ అవ్వండి", + "login.logout": "లాగౌట్", + "login.password": "సంకేతపదము", + "login.placeholder": "ఉదా. lain", + "login.register": "నమోదు చేసుకోండి", + "login.username": "వాడుకరి పేరు", + "login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి", + "media_modal.previous": "ముందరి పుట", + "media_modal.next": "తరువాత", + "nav.about": "గురించి", + "nav.back": "వెనక్కి", + "nav.chat": "స్థానిక చాట్", + "nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు", + "nav.mentions": "ప్రస్తావనలు", + "nav.dms": "నేరుగా పంపిన సందేశాలు", + "nav.public_tl": "ప్రజా కాలక్రమం", + "nav.timeline": "కాలక్రమం", + "nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్", + "nav.user_search": "వాడుకరి శోధన", + "nav.who_to_follow": "ఎవరిని అనుసరించాలి", + "nav.preferences": "ప్రాధాన్యతలు", + "notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...", + "notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు", + "notifications.followed_you": "మిమ్మల్ని అనుసరించారు", + "notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి", + "notifications.notifications": "ప్రకటనలు", + "notifications.read": "చదివాను!", + "notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు", + "notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు", + "post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి", + "post_status.account_not_locked_warning": "మీ ఖాతా {౦} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.", + "post_status.account_not_locked_warning_link": "తాళం వేయబడినది", + "post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి", + "post_status.content_type.text/plain": "సాధారణ అక్షరాలు", + "post_status.content_type.text/html": "హెచ్టిఎమ్ఎల్", + "post_status.content_type.text/markdown": "మార్క్డౌన్", + "post_status.content_warning": "విషయం (ఐచ్ఛికం)", + "post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.", + "post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.", + "post_status.posting": "పోస్ట్ చేస్తున్నా", + "post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది", + "post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది", + "post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది", + "post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు", + "registration.bio": "బయో", + "registration.email": "ఈ మెయిల్", + "registration.fullname": "ప్రదర్శన పేరు", + "registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ", + "registration.registration": "నమోదు", + "registration.token": "ఆహ్వాన టోకెన్", + "registration.captcha": "కాప్చా", + "registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి", + "registration.username_placeholder": "ఉదా. lain", + "registration.fullname_placeholder": "ఉదా. Lain Iwakura", + "registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.", + "registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు", + "registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి", + "settings.app_name": "అనువర్తన పేరు", + "settings.attachmentRadius": "జోడింపులు", + "settings.attachments": "జోడింపులు", + "settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు", + "settings.avatar": "అవతారం", + "settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)", + "settings.avatarRadius": "అవతారాలు", + "settings.background": "బ్యాక్గ్రౌండు", + "settings.bio": "బయో", + "settings.blocks_tab": "బ్లాక్లు", + "settings.btnRadius": "బటన్లు", + "settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)", + "settings.cGreen": "Green (Retweet)", + "settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)", + "settings.cRed": "Red (Cancel)", + "settings.change_password": "పాస్వర్డ్ మార్చండి", + "settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.", + "settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!", + "settings.collapse_subject": "Collapse posts with subjects", + "settings.composing": "Composing", + "settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి", + "settings.current_avatar": "మీ ప్రస్తుత అవతారం", + "settings.current_password": "ప్రస్తుత పాస్వర్డ్", + "settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్", + "settings.data_import_export_tab": "Data Import / Export", + "settings.default_vis": "Default visibility scope", + "settings.delete_account": "Delete Account", + "settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.", + "settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.", + "settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.", + "settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.", + "settings.export_theme": "Save preset", + "settings.filtering": "వడపోత", + "settings.filtering_explanation": "All statuses containing these words will be muted, one per line", + "settings.follow_export": "Follow export", + "settings.follow_export_button": "Export your follows to a csv file", + "settings.follow_export_processing": "Processing, you'll soon be asked to download your file", + "settings.follow_import": "Follow import", + "settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం", + "settings.follows_imported": "Follows imported! Processing them will take a while.", + "settings.foreground": "Foreground", + "settings.general": "General", + "settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు", + "settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు", + "settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు", + "settings.max_thumbnails": "Maximum amount of thumbnails per post", + "settings.hide_isp": "Hide instance-specific panel", + "settings.preload_images": "Preload images", + "settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి", + "settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)", + "settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)", + "settings.hide_filtered_statuses": "Hide filtered statuses", + "settings.import_followers_from_a_csv_file": "Import follows from a csv file", + "settings.import_theme": "Load preset", + "settings.inputRadius": "Input fields", + "settings.checkboxRadius": "Checkboxes", + "settings.instance_default": "(default: {value})", + "settings.instance_default_simple": "(default)", + "settings.interface": "Interface", + "settings.interfaceLanguage": "Interface language", + "settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.", + "settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు", + "settings.links": "Links", + "settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి", + "settings.loop_video": "Loop videos", + "settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", + "settings.mutes_tab": "మ్యూట్ చేయబడినవి", + "settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి", + "settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు", + "settings.name": "Name", + "settings.name_bio": "పేరు & బయో", + "settings.new_password": "కొత్త సంకేతపదం", + "settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు", + "settings.notification_visibility_follows": "Follows", + "settings.notification_visibility_likes": "ఇష్టాలు", + "settings.notification_visibility_mentions": "ప్రస్తావనలు", + "settings.notification_visibility_repeats": "పునఃప్రసారాలు", + "settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి", + "settings.no_blocks": "బ్లాక్స్ లేవు", + "settings.no_mutes": "మ్యూట్లు లేవు", + "settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు", + "settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు", + "settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు", + "settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు", + "settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", + "settings.oauth_tokens": "OAuth tokens", + "settings.token": "Token", + "settings.refresh_token": "Refresh Token", + "settings.valid_until": "Valid Until", + "settings.revoke_token": "Revoke", + "settings.panelRadius": "Panels", + "settings.pause_on_unfocused": "Pause streaming when tab is not focused", + "settings.presets": "Presets", + "settings.profile_background": "Profile Background", + "settings.profile_banner": "Profile Banner", + "settings.profile_tab": "Profile", + "settings.radii_help": "Set up interface edge rounding (in pixels)", + "settings.replies_in_timeline": "Replies in timeline", + "settings.reply_link_preview": "Enable reply-link preview on mouse hover", + "settings.reply_visibility_all": "Show all replies", + "settings.reply_visibility_following": "Only show replies directed at me or users I'm following", + "settings.reply_visibility_self": "Only show replies directed at me", + "settings.saving_err": "Error saving settings", + "settings.saving_ok": "Settings saved", + "settings.security_tab": "Security", + "settings.scope_copy": "Copy scope when replying (DMs are always copied)", + "settings.set_new_avatar": "Set new avatar", + "settings.set_new_profile_background": "Set new profile background", + "settings.set_new_profile_banner": "Set new profile banner", + "settings.settings": "Settings", + "settings.subject_input_always_show": "Always show subject field", + "settings.subject_line_behavior": "Copy subject when replying", + "settings.subject_line_email": "Like email: \"re: subject\"", + "settings.subject_line_mastodon": "Like mastodon: copy as is", + "settings.subject_line_noop": "Do not copy", + "settings.post_status_content_type": "Post status content type", + "settings.stop_gifs": "Play-on-hover GIFs", + "settings.streaming": "Enable automatic streaming of new posts when scrolled to the top", + "settings.text": "Text", + "settings.theme": "Theme", + "settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.", + "settings.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.", + "settings.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.", + "settings.tooltipRadius": "Tooltips/alerts", + "settings.upload_a_photo": "Upload a photo", + "settings.user_settings": "User Settings", + "settings.values.false": "no", + "settings.values.true": "yes", + "settings.notifications": "Notifications", + "settings.enable_web_push_notifications": "Enable web push notifications", + "settings.style.switcher.keep_color": "Keep colors", + "settings.style.switcher.keep_shadows": "Keep shadows", + "settings.style.switcher.keep_opacity": "Keep opacity", + "settings.style.switcher.keep_roundness": "Keep roundness", + "settings.style.switcher.keep_fonts": "Keep fonts", + "settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.", + "settings.style.switcher.reset": "Reset", + "settings.style.switcher.clear_all": "Clear all", + "settings.style.switcher.clear_opacity": "Clear opacity", + "settings.style.common.color": "Color", + "settings.style.common.opacity": "Opacity", + "settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}", + "settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)", + "settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)", + "settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines", + "settings.style.common.contrast.context.18pt": "for large (18pt+) text", + "settings.style.common.contrast.context.text": "for text", + "settings.style.common_colors._tab_label": "Common", + "settings.style.common_colors.main": "Common colors", + "settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control", + "settings.style.common_colors.rgbo": "Icons, accents, badges", + "settings.style.advanced_colors._tab_label": "Advanced", + "settings.style.advanced_colors.alert": "Alert background", + "settings.style.advanced_colors.alert_error": "Error", + "settings.style.advanced_colors.badge": "Badge background", + "settings.style.advanced_colors.badge_notification": "Notification", + "settings.style.advanced_colors.panel_header": "Panel header", + "settings.style.advanced_colors.top_bar": "Top bar", + "settings.style.advanced_colors.borders": "Borders", + "settings.style.advanced_colors.buttons": "Buttons", + "settings.style.advanced_colors.inputs": "Input fields", + "settings.style.advanced_colors.faint_text": "Faded text", + "settings.style.radii._tab_label": "Roundness", + "settings.style.shadows._tab_label": "Shadow and lighting", + "settings.style.shadows.component": "Component", + "settings.style.shadows.override": "Override", + "settings.style.shadows.shadow_id": "Shadow #{value}", + "settings.style.shadows.blur": "Blur", + "settings.style.shadows.spread": "Spread", + "settings.style.shadows.inset": "Inset", + "settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.", + "settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.", + "settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.", + "settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.", + "settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero", + "settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}", + "settings.style.shadows.components.panel": "Panel", + "settings.style.shadows.components.panelHeader": "Panel header", + "settings.style.shadows.components.topBar": "Top bar", + "settings.style.shadows.components.avatar": "User avatar (in profile view)", + "settings.style.shadows.components.avatarStatus": "User avatar (in post display)", + "settings.style.shadows.components.popup": "Popups and tooltips", + "settings.style.shadows.components.button": "Button", + "settings.style.shadows.components.buttonHover": "Button (hover)", + "settings.style.shadows.components.buttonPressed": "Button (pressed)", + "settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)", + "settings.style.shadows.components.input": "Input field", + "settings.style.fonts._tab_label": "Fonts", + "settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.", + "settings.style.fonts.components.interface": "Interface", + "settings.style.fonts.components.input": "Input fields", + "settings.style.fonts.components.post": "Post text", + "settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)", + "settings.style.fonts.family": "Font name", + "settings.style.fonts.size": "Size (in px)", + "settings.style.fonts.weight": "Weight (boldness)", + "settings.style.fonts.custom": "Custom", + "settings.style.preview.header": "Preview", + "settings.style.preview.content": "Content", + "settings.style.preview.error": "Example error", + "settings.style.preview.button": "Button", + "settings.style.preview.text": "A bunch of more {0} and {1}", + "settings.style.preview.mono": "content", + "settings.style.preview.input": "Just landed in L.A.", + "settings.style.preview.faint_link": "helpful manual", + "settings.style.preview.fine_print": "Read our {0} to learn nothing useful!", + "settings.style.preview.header_faint": "This is fine", + "settings.style.preview.checkbox": "I have skimmed over terms and conditions", + "settings.style.preview.link": "a nice lil' link", + "settings.version.title": "Version", + "settings.version.backend_version": "Backend Version", + "settings.version.frontend_version": "Frontend Version", + "timeline.collapse": "Collapse", + "timeline.conversation": "Conversation", + "timeline.error_fetching": "Error fetching updates", + "timeline.load_older": "Load older statuses", + "timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", + "timeline.repeated": "repeated", + "timeline.show_new": "Show new", + "timeline.up_to_date": "Up-to-date", + "timeline.no_more_statuses": "No more statuses", + "timeline.no_statuses": "No statuses", + "status.reply_to": "Reply to", + "status.replies_list": "Replies:", + "user_card.approve": "Approve", + "user_card.block": "Block", + "user_card.blocked": "Blocked!", + "user_card.deny": "Deny", + "user_card.favorites": "Favorites", + "user_card.follow": "Follow", + "user_card.follow_sent": "Request sent!", + "user_card.follow_progress": "Requesting…", + "user_card.follow_again": "Send request again?", + "user_card.follow_unfollow": "Unfollow", + "user_card.followees": "Following", + "user_card.followers": "Followers", + "user_card.following": "Following!", + "user_card.follows_you": "Follows you!", + "user_card.its_you": "It's you!", + "user_card.media": "Media", + "user_card.mute": "Mute", + "user_card.muted": "Muted", + "user_card.per_day": "per day", + "user_card.remote_follow": "Remote follow", + "user_card.statuses": "Statuses", + "user_card.unblock": "Unblock", + "user_card.unblock_progress": "Unblocking...", + "user_card.block_progress": "Blocking...", + "user_card.unmute": "Unmute", + "user_card.unmute_progress": "Unmuting...", + "user_card.mute_progress": "Muting...", + "user_profile.timeline_title": "User Timeline", + "user_profile.profile_does_not_exist": "Sorry, this profile does not exist.", + "user_profile.profile_loading_error": "Sorry, there was an error loading this profile.", + "who_to_follow.more": "More", + "who_to_follow.who_to_follow": "Who to follow", + "tool_tip.media_upload": "Upload Media", + "tool_tip.repeat": "Repeat", + "tool_tip.reply": "Reply", + "tool_tip.favorite": "Favorite", + "tool_tip.user_settings": "User Settings", + "upload.error.base": "Upload failed.", + "upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", + "upload.error.default": "Try again later", + "upload.file_size_units.B": "B", + "upload.file_size_units.KiB": "KiB", + "upload.file_size_units.MiB": "MiB", + "upload.file_size_units.GiB": "GiB", + "upload.file_size_units.TiB": "TiB" +} diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 7d5d5a67..e863d8a5 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -430,6 +430,10 @@ export const mutations = { const newStatus = state.allStatusesObject[status.id] newStatus.pinned = status.pinned }, + setMuted (state, status) { + const newStatus = state.allStatusesObject[status.id] + newStatus.muted = status.muted + }, setRetweeted (state, { status, value }) { const newStatus = state.allStatusesObject[status.id] @@ -564,6 +568,14 @@ const statuses = { rootState.api.backendInteractor.unpinOwnStatus(statusId) .then((status) => commit('setPinned', status)) }, + muteConversation ({ rootState, commit }, statusId) { + return rootState.api.backendInteractor.muteConversation(statusId) + .then((status) => commit('setMuted', status)) + }, + unmuteConversation ({ rootState, commit }, statusId) { + return rootState.api.backendInteractor.unmuteConversation(statusId) + .then((status) => commit('setMuted', status)) + }, retweet ({ rootState, commit }, status) { // Optimistic retweeting... commit('setRetweeted', { status, value: true }) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 84616dd1..083d4f4f 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -67,6 +67,8 @@ const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials' const MASTODON_REPORT_USER_URL = '/api/v1/reports' const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin` const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin` +const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` +const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' @@ -252,6 +254,16 @@ const unpinOwnStatus = ({ id, credentials }) => { .then((data) => parseStatus(data)) } +const muteConversation = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST' }) + .then((data) => parseStatus(data)) +} + +const unmuteConversation = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST' }) + .then((data) => parseStatus(data)) +} + const blockUser = ({ id, credentials }) => { return fetch(MASTODON_BLOCK_USER_URL(id), { headers: authHeaders(credentials), @@ -920,6 +932,8 @@ const apiService = { unfollowUser, pinOwnStatus, unpinOwnStatus, + muteConversation, + unmuteConversation, blockUser, unblockUser, fetchUser, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index bdfe0465..846d9415 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -117,6 +117,8 @@ const backendInteractorService = credentials => { const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id }) const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id }) const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id }) + const muteConversation = (id) => apiService.muteConversation({ credentials, id }) + const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id }) const getCaptcha = () => apiService.getCaptcha() const register = (params) => apiService.register({ credentials, params }) @@ -178,6 +180,8 @@ const backendInteractorService = credentials => { fetchPinnedStatuses, pinOwnStatus, unpinOwnStatus, + muteConversation, + unmuteConversation, tagUser, untagUser, addRight, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 8b4d2594..6cc1851d 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -65,6 +65,7 @@ export const parseUser = (data) => { if (relationship) { output.follows_you = relationship.followed_by + output.requested = relationship.requested output.following = relationship.following output.statusnet_blocking = relationship.blocking output.muted = relationship.muting @@ -240,6 +241,7 @@ export const parseStatus = (data) => { output.external_url = data.url output.poll = data.poll output.pinned = data.pinned + output.muted = data.muted } else { output.favorited = data.favorited output.fave_num = data.fave_num diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index b2486e7c..529fdb9b 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -2,17 +2,17 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => { setTimeout(() => { store.state.api.backendInteractor.fetchUser({ id: user.id }) .then((user) => store.commit('addNewUsers', [user])) - .then(() => resolve([user.following, attempt])) + .then(() => resolve([user.following, user.requested, user.locked, attempt])) .catch((e) => reject(e)) }, 500) -}).then(([following, attempt]) => { - if (!following && attempt <= 3) { +}).then(([following, sent, locked, attempt]) => { + 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 following + return sent } }) @@ -21,14 +21,10 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => { .then((updated) => { store.commit('updateUserRelationship', [updated]) - // For locked users we just mark it that we sent the follow request - if (updated.locked) { - resolve({ sent: true }) - } - - if (updated.following) { - // If we get result immediately, just stop. - resolve({ sent: false }) + if (updated.following || (user.locked && user.requested)) { + // If we get result immediately or the account is locked, just stop. + resolve({ sent: updated.requested }) + return } // But usually we don't get result immediately, so we ask server @@ -39,14 +35,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((following) => { - if (following) { - // We confirmed and everything's good. - resolve({ sent: false }) - } else { - // If after all the tries, just treat it as if user is locked - resolve({ sent: false }) - } + .then((sent) => { + resolve({ sent }) }) }) }) @@ -1387,10 +1387,29 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + callsite@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1817,6 +1836,16 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: parse-json "^2.2.0" require-from-string "^1.1.0" +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" @@ -2390,7 +2419,7 @@ errno@^0.1.3, errno@~0.1.7: dependencies: prr "~1.0.1" -error-ex@^1.2.0: +error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" dependencies: @@ -3503,6 +3532,21 @@ immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" +import-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" + integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= + dependencies: + import-from "^2.1.0" + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + import-fresh@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390" @@ -3510,6 +3554,13 @@ import-fresh@^3.0.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-from@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" + integrity sha1-M1238qev/VOqpHHUuAId7ja387E= + dependencies: + resolve-from "^3.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3949,7 +4000,7 @@ js-yaml@3.x, js-yaml@^3.4.3: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.13.0: +js-yaml@^3.13.0, js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" dependencies: @@ -3979,7 +4030,7 @@ json-loader@^0.5.4: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" -json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -5315,6 +5366,14 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -5541,6 +5600,14 @@ postcss-load-config@^1.1.0: postcss-load-options "^1.2.0" postcss-load-plugins "^2.3.0" +postcss-load-config@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" + integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== + dependencies: + cosmiconfig "^5.0.0" + import-cwd "^2.0.0" + postcss-load-options@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c" @@ -5555,6 +5622,16 @@ postcss-load-plugins@^2.3.0: cosmiconfig "^2.1.1" object-assign "^4.1.0" +postcss-loader@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" + integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== + dependencies: + loader-utils "^1.1.0" + postcss "^7.0.0" + postcss-load-config "^2.0.0" + schema-utils "^1.0.0" + postcss-merge-idents@^2.1.5: version "2.1.7" resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" @@ -5740,6 +5817,15 @@ postcss@^6.0.1, postcss@^6.0.8: source-map "^0.6.1" supports-color "^5.4.0" +postcss@^7.0.0: + version "7.0.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" + integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + postcss@^7.0.5: version "7.0.8" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696" @@ -6203,6 +6289,11 @@ requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -6844,7 +6935,7 @@ supports-color@^5.3.0, supports-color@^5.4.0: dependencies: has-flag "^3.0.0" -supports-color@^6.0.0: +supports-color@^6.0.0, supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" dependencies: |
