Merge branch 'develop' into feature/mobile-improvements-2

This commit is contained in:
shpuld 2019-03-07 21:02:12 +02:00
commit 5d55fc53ac
34 changed files with 160 additions and 168 deletions

View File

@ -169,6 +169,8 @@ const afterStoreSetup = ({ store, i18n }) => {
store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
const suggestions = metadata.suggestions const suggestions = metadata.suggestions

View File

@ -160,6 +160,7 @@
.hider { .hider {
position: absolute; position: absolute;
right: 0;
white-space: nowrap; white-space: nowrap;
margin: 10px; margin: 10px;
padding: 5px; padding: 5px;

View File

@ -1,4 +1,4 @@
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCard from '../user_card/user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -12,7 +12,7 @@ const BasicUserCard = {
} }
}, },
components: { components: {
UserCardContent, UserCard,
UserAvatar UserAvatar
}, },
methods: { methods: {

View File

@ -1,18 +1,18 @@
<template> <template>
<div class="user-card"> <div class="basic-user-card">
<router-link :to="userProfileLink(user)"> <router-link :to="userProfileLink(user)">
<UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/> <UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
</router-link> </router-link>
<div class="user-card-expanded-content" v-if="userExpanded"> <div class="basic-user-card-expanded-content" v-if="userExpanded">
<user-card-content :user="user" :switcher="false"></user-card-content> <UserCard :user="user" :rounded="true" :bordered="true"/>
</div> </div>
<div class="user-card-collapsed-content" v-else> <div class="basic-user-card-collapsed-content" v-else>
<div :title="user.name" class="user-card-user-name"> <div :title="user.name" class="basic-user-card-user-name">
<span v-if="user.name_html" v-html="user.name_html"></span> <span v-if="user.name_html" v-html="user.name_html"></span>
<span v-else>{{ user.name }}</span> <span v-else>{{ user.name }}</span>
</div> </div>
<div> <div>
<router-link class="user-card-screen-name" :to="userProfileLink(user)"> <router-link class="basic-user-card-screen-name" :to="userProfileLink(user)">
@{{user.screen_name}} @{{user.screen_name}}
</router-link> </router-link>
</div> </div>
@ -26,15 +26,15 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.user-card { .basic-user-card {
display: flex; display: flex;
flex: 1 0; flex: 1 0;
margin: 0;
padding-top: 0.6em; padding-top: 0.6em;
padding-right: 1em; padding-right: 1em;
padding-bottom: 0.6em; padding-bottom: 0.6em;
padding-left: 1em; padding-left: 1em;
border-bottom: 1px solid; border-bottom: 1px solid;
margin: 0;
border-bottom-color: $fallback--border; border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border); border-bottom-color: var(--border, $fallback--border);
@ -57,23 +57,6 @@
&-expanded-content { &-expanded-content {
flex: 1; flex: 1;
margin-left: 0.7em; margin-left: 0.7em;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-width: 1px;
overflow: hidden;
.panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
p {
margin-bottom: 0;
}
} }
} }
</style> </style>

View File

@ -1,6 +1,6 @@
import Status from '../status/status.vue' import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCard from '../user_card/user_card.vue'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -13,7 +13,7 @@ const Notification = {
}, },
props: [ 'notification' ], props: [ 'notification' ],
components: { components: {
Status, UserAvatar, UserCardContent Status, UserAvatar, UserCard
}, },
methods: { methods: {
toggleUserExpanded () { toggleUserExpanded () {

View File

@ -5,9 +5,7 @@
<UserAvatar :compact="true" :betterShadow="betterShadow" :src="notification.action.user.profile_image_url_original"/> <UserAvatar :compact="true" :betterShadow="betterShadow" :src="notification.action.user.profile_image_url_original"/>
</a> </a>
<div class='notification-right'> <div class='notification-right'>
<div class="usercard notification-usercard" v-if="userExpanded"> <UserCard :user="notification.action.user" :rounded="true" :bordered="true" v-if="userExpanded"/>
<user-card-content :user="notification.action.user" :switcher="false"></user-card-content>
</div>
<span class="notification-details"> <span class="notification-details">
<div class="name-and-action"> <div class="name-and-action">
<span class="username" v-if="!!notification.action.user.name_html" :title="'@'+notification.action.user.screen_name" v-html="notification.action.user.name_html"></span> <span class="username" v-if="!!notification.action.user.name_html" :title="'@'+notification.action.user.screen_name" v-html="notification.action.user.name_html"></span>

View File

@ -11,7 +11,8 @@ const Notifications = {
const store = this.$store const store = this.$store
const credentials = store.state.users.currentUser.credentials const credentials = store.state.users.currentUser.credentials
notificationsFetcher.startFetching({ store, credentials }) const fetcherId = notificationsFetcher.startFetching({ store, credentials })
this.$store.commit('setNotificationFetcher', { fetcherId })
}, },
data () { data () {
return { return {

View File

@ -45,10 +45,6 @@
} }
} }
.notification-usercard {
margin: 0;
}
.non-mention { .non-mention {
display: flex; display: flex;
flex: 1; flex: 1;

View File

@ -171,6 +171,9 @@ const PostStatusForm = {
}, },
formattingOptionsEnabled () { formattingOptionsEnabled () {
return this.$store.state.instance.formattingOptionsEnabled return this.$store.state.instance.formattingOptionsEnabled
},
postFormats () {
return this.$store.state.instance.postFormats || []
} }
}, },
methods: { methods: {

View File

@ -38,9 +38,9 @@
<span class="text-format" v-if="formattingOptionsEnabled"> <span class="text-format" v-if="formattingOptionsEnabled">
<label for="post-content-type" class="select"> <label for="post-content-type" class="select">
<select id="post-content-type" v-model="newStatus.contentType" class="form-control"> <select id="post-content-type" v-model="newStatus.contentType" class="form-control">
<option value="text/plain">{{$t('post_status.content_type.plain_text')}}</option> <option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
<option value="text/html">HTML</option> {{$t(`post_status.content_type["${postFormat}"]`)}}
<option value="text/markdown">Markdown</option> </option>
</select> </select>
<i class="icon-down-open"></i> <i class="icon-down-open"></i>
</label> </label>

View File

@ -93,6 +93,9 @@ const settings = {
currentSaveStateNotice () { currentSaveStateNotice () {
return this.$store.state.interface.settings.currentSaveStateNotice return this.$store.state.interface.settings.currentSaveStateNotice
}, },
postFormats () {
return this.$store.state.instance.postFormats || []
},
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel } instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel }
}, },
watch: { watch: {

View File

@ -105,17 +105,9 @@
{{$t('settings.post_status_content_type')}} {{$t('settings.post_status_content_type')}}
<label for="postContentType" class="select"> <label for="postContentType" class="select">
<select id="postContentType" v-model="postContentTypeLocal"> <select id="postContentType" v-model="postContentTypeLocal">
<option value="text/plain"> <option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
{{$t('settings.status_content_type_plain')}} {{$t(`post_status.content_type["${postFormat}"]`)}}
{{postContentTypeDefault == 'text/plain' ? $t('settings.instance_default_simple') : ''}} {{postContentTypeDefault === postFormat ? $t('settings.instance_default_simple') : ''}}
</option>
<option value="text/html">
HTML
{{postContentTypeDefault == 'text/html' ? $t('settings.instance_default_simple') : ''}}
</option>
<option value="text/markdown">
Markdown
{{postContentTypeDefault == 'text/markdown' ? $t('settings.instance_default_simple') : ''}}
</option> </option>
</select> </select>
<i class="icon-down-open"/> <i class="icon-down-open"/>

View File

@ -1,4 +1,4 @@
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCard from '../user_card/user_card.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
// TODO: separate touch gesture stuff into their own utils if more components want them // TODO: separate touch gesture stuff into their own utils if more components want them
@ -12,7 +12,7 @@ const SideDrawer = {
closed: true, closed: true,
touchCoord: [0, 0] touchCoord: [0, 0]
}), }),
components: { UserCardContent }, components: { UserCard },
computed: { computed: {
currentUser () { currentUser () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser

View File

@ -8,7 +8,7 @@
@touchmove="touchMove" @touchmove="touchMove"
> >
<div class="side-drawer-heading" @click="toggleDrawer"> <div class="side-drawer-heading" @click="toggleDrawer">
<user-card-content :user="currentUser" :switcher="false" :hideBio="true" v-if="currentUser"/> <UserCard :user="currentUser" :hideBio="true" v-if="currentUser"/>
<div class="side-drawer-logo-wrapper" v-else> <div class="side-drawer-logo-wrapper" v-else>
<img :src="logo"/> <img :src="logo"/>
<span>{{sitename}}</span> <span>{{sitename}}</span>
@ -176,15 +176,6 @@
display: flex; display: flex;
padding: 0; padding: 0;
margin: 0; margin: 0;
.profile-panel-background {
border-radius: 0;
.panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
}
} }
.side-drawer ul { .side-drawer ul {

View File

@ -3,7 +3,7 @@ import FavoriteButton from '../favorite_button/favorite_button.vue'
import RetweetButton from '../retweet_button/retweet_button.vue' import RetweetButton from '../retweet_button/retweet_button.vue'
import DeleteButton from '../delete_button/delete_button.vue' import DeleteButton from '../delete_button/delete_button.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue' import PostStatusForm from '../post_status_form/post_status_form.vue'
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCard from '../user_card/user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import Gallery from '../gallery/gallery.vue' import Gallery from '../gallery/gallery.vue'
import LinkPreview from '../link-preview/link-preview.vue' import LinkPreview from '../link-preview/link-preview.vue'
@ -259,7 +259,7 @@ const Status = {
RetweetButton, RetweetButton,
DeleteButton, DeleteButton,
PostStatusForm, PostStatusForm,
UserCardContent, UserCard,
UserAvatar, UserAvatar,
Gallery, Gallery,
LinkPreview LinkPreview

View File

@ -31,9 +31,7 @@
</router-link> </router-link>
</div> </div>
<div class="status-body"> <div class="status-body">
<div class="usercard" v-if="userExpanded"> <UserCard :user="status.user" :rounded="true" :bordered="true" class="status-usercard" v-if="userExpanded"/>
<user-card-content :user="status.user" :switcher="false"></user-card-content>
</div>
<div v-if="!noHeading" class="media-heading"> <div v-if="!noHeading" class="media-heading">
<div class="heading-name-row"> <div class="heading-name-row">
<div class="name-and-account-name"> <div class="name-and-account-name">
@ -248,8 +246,7 @@ $status-margin: 0.75em;
padding: 0; padding: 0;
} }
.usercard { .status-usercard {
margin: 0;
margin-bottom: $status-margin; margin-bottom: $status-margin;
} }

View File

@ -132,7 +132,9 @@ const Timeline = {
} }
if (count > 0) { if (count > 0) {
// only 'stream' them when you're scrolled to the top // only 'stream' them when you're scrolled to the top
if (window.pageYOffset < 15 && const doc = document.documentElement
const top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
if (top < 15 &&
!this.paused && !this.paused &&
!(this.unfocused && this.$store.state.config.pauseOnUnfocused) !(this.unfocused && this.$store.state.config.pauseOnUnfocused)
) { ) {

View File

@ -4,7 +4,7 @@ import { requestFollow, requestUnfollow } from '../../services/follow_manipulate
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
export default { export default {
props: [ 'user', 'switcher', 'selected', 'hideBio' ], props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered' ],
data () { data () {
return { return {
followRequestInProgress: false, followRequestInProgress: false,
@ -16,7 +16,14 @@ export default {
} }
}, },
computed: { computed: {
headingStyle () { classes () {
return [{
'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius
'user-card-rounded': this.rounded === true, // set border-radius for all sides
'user-card-bordered': this.bordered === true // set border for all sides
}]
},
style () {
const color = this.$store.state.config.customTheme.colors const color = this.$store.state.config.customTheme.colors
? this.$store.state.config.customTheme.colors.bg // v2 ? this.$store.state.config.customTheme.colors.bg // v2
: this.$store.state.config.colors.bg // v1 : this.$store.state.config.colors.bg // v1
@ -93,22 +100,30 @@ export default {
}, },
methods: { methods: {
followUser () { followUser () {
const store = this.$store
this.followRequestInProgress = true this.followRequestInProgress = true
requestFollow(this.user, this.$store).then(({sent}) => { requestFollow(this.user, store).then(({sent}) => {
this.followRequestInProgress = false this.followRequestInProgress = false
this.followRequestSent = sent this.followRequestSent = sent
}) })
}, },
unfollowUser () { unfollowUser () {
const store = this.$store
this.followRequestInProgress = true this.followRequestInProgress = true
requestUnfollow(this.user, this.$store).then(() => { requestUnfollow(this.user, store).then(() => {
this.followRequestInProgress = false this.followRequestInProgress = false
store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
}) })
}, },
blockUser () { blockUser () {
const store = this.$store const store = this.$store
store.state.api.backendInteractor.blockUser(this.user.id) store.state.api.backendInteractor.blockUser(this.user.id)
.then((blockedUser) => store.commit('addNewUsers', [blockedUser])) .then((blockedUser) => {
store.commit('addNewUsers', [blockedUser])
store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
store.commit('removeStatus', { timeline: 'public', userId: this.user.id })
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: this.user.id })
})
}, },
unblockUser () { unblockUser () {
const store = this.$store const store = this.$store

View File

@ -1,6 +1,6 @@
<template> <template>
<div id="heading" class="profile-panel-background" :style="headingStyle"> <div class="user-card" :class="classes" :style="style">
<div class="panel-heading text-center"> <div class="panel-heading">
<div class='user-info'> <div class='user-info'>
<div class='container'> <div class='container'>
<router-link :to="userProfileLink(user)"> <router-link :to="userProfileLink(user)">
@ -108,7 +108,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="panel-body profile-panel-body" v-if="!hideBio"> <div class="panel-body" v-if="!hideBio">
<div v-if="!hideUserStatsLocal && switcher" class="user-counts"> <div v-if="!hideUserStatsLocal && switcher" class="user-counts">
<div class="user-count" v-on:click.prevent="setProfileView('statuses')"> <div class="user-count" v-on:click.prevent="setProfileView('statuses')">
<h5>{{ $t('user_card.statuses') }}</h5> <h5>{{ $t('user_card.statuses') }}</h5>
@ -123,41 +123,64 @@
<span>{{user.followers_count}}</span> <span>{{user.followers_count}}</span>
</div> </div>
</div> </div>
<p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="profile-bio" v-html="user.description_html"></p> <p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="user-card-bio" v-html="user.description_html"></p>
<p v-else-if="!hideBio" class="profile-bio">{{ user.description }}</p> <p v-else-if="!hideBio" class="user-card-bio">{{ user.description }}</p>
</div> </div>
</div> </div>
</template> </template>
<script src="./user_card_content.js"></script> <script src="./user_card.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.profile-panel-background { .user-card {
background-size: cover; background-size: cover;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
overflow: hidden; overflow: hidden;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
.panel-heading { .panel-heading {
padding: .5em 0; padding: .5em 0;
text-align: center; text-align: center;
box-shadow: none; box-shadow: none;
background: transparent;
flex-direction: column;
align-items: stretch;
} }
}
.profile-panel-body { .panel-body {
word-wrap: break-word; word-wrap: break-word;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%); background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%);
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%); background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%);
}
.profile-bio { p {
margin-bottom: 0;
}
&-bio {
text-align: center; text-align: center;
} }
// Modifiers
&-rounded-t {
border-top-left-radius: $fallback--panelRadius;
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
border-top-right-radius: $fallback--panelRadius;
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
}
&-rounded {
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
}
&-bordered {
border-width: 1px;
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
}
} }
.user-info { .user-info {
@ -393,25 +416,4 @@
text-decoration: none; text-decoration: none;
} }
} }
.usercard {
width: fill-available;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-width: 1px;
overflow: hidden;
.panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
p {
margin-bottom: 0;
}
}
</style> </style>

View File

@ -1,6 +1,6 @@
import LoginForm from '../login_form/login_form.vue' import LoginForm from '../login_form/login_form.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue' import PostStatusForm from '../post_status_form/post_status_form.vue'
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCard from '../user_card/user_card.vue'
const UserPanel = { const UserPanel = {
computed: { computed: {
@ -9,7 +9,7 @@ const UserPanel = {
components: { components: {
LoginForm, LoginForm,
PostStatusForm, PostStatusForm,
UserCardContent UserCard
} }
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="user-panel"> <div class="user-panel">
<div v-if='user' class="panel panel-default" style="overflow: visible;"> <div v-if='user' class="panel panel-default" style="overflow: visible;">
<user-card-content :user="user" :switcher="false" :hideBio="true"></user-card-content> <UserCard :user="user" :hideBio="true" rounded="top"/>
<div class="panel-footer"> <div class="panel-footer">
<post-status-form v-if='user'></post-status-form> <post-status-form v-if='user'></post-status-form>
</div> </div>
@ -11,13 +11,3 @@
</template> </template>
<script src="./user_panel.js"></script> <script src="./user_panel.js"></script>
<style lang="scss">
.user-panel {
.profile-panel-background .panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
}
</style>

View File

@ -1,6 +1,6 @@
import { compose } from 'vue-compose' import { compose } from 'vue-compose'
import get from 'lodash/get' import get from 'lodash/get'
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCard from '../user_card/user_card.vue'
import FollowCard from '../follow_card/follow_card.vue' import FollowCard from '../follow_card/follow_card.vue'
import Timeline from '../timeline/timeline.vue' import Timeline from '../timeline/timeline.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more' import withLoadMore from '../../hocs/with_load_more/with_load_more'
@ -147,7 +147,7 @@ const UserProfile = {
} }
}, },
components: { components: {
UserCardContent, UserCard,
Timeline, Timeline,
FollowerList, FollowerList,
FriendList FriendList

View File

@ -1,11 +1,7 @@
<template> <template>
<div> <div>
<div v-if="user.id" class="user-profile panel panel-default"> <div v-if="user.id" class="user-profile panel panel-default">
<user-card-content <UserCard :user="user" :switcher="true" :selected="timeline.viewing" rounded="top"/>
:user="user"
:switcher="true"
:selected="timeline.viewing"
/>
<tab-switcher :renderOnlyFocused="true" ref="tabSwitcher"> <tab-switcher :renderOnlyFocused="true" ref="tabSwitcher">
<Timeline <Timeline
:label="$t('user_card.statuses')" :label="$t('user_card.statuses')"
@ -64,11 +60,6 @@
flex: 2; flex: 2;
flex-basis: 500px; flex-basis: 500px;
.profile-panel-background .panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
.userlist-placeholder { .userlist-placeholder {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -71,7 +71,9 @@
"account_not_locked_warning_link": "locked", "account_not_locked_warning_link": "locked",
"attachments_sensitive": "Mark attachments as sensitive", "attachments_sensitive": "Mark attachments as sensitive",
"content_type": { "content_type": {
"plain_text": "Plain text" "text/plain": "Plain text",
"text/html": "HTML",
"text/markdown": "Markdown"
}, },
"content_warning": "Subject (optional)", "content_warning": "Subject (optional)",
"default": "Just landed in L.A.", "default": "Just landed in L.A.",
@ -221,7 +223,6 @@
"subject_line_mastodon": "Like mastodon: copy as is", "subject_line_mastodon": "Like mastodon: copy as is",
"subject_line_noop": "Do not copy", "subject_line_noop": "Do not copy",
"post_status_content_type": "Post status content type", "post_status_content_type": "Post status content type",
"status_content_type_plain": "Plain text",
"stop_gifs": "Play-on-hover GIFs", "stop_gifs": "Play-on-hover GIFs",
"streaming": "Enable automatic streaming of new posts when scrolled to the top", "streaming": "Enable automatic streaming of new posts when scrolled to the top",
"text": "Text", "text": "Text",

View File

@ -221,7 +221,6 @@
"subject_line_mastodon": "Kiel Mastodon: kopii senŝanĝe", "subject_line_mastodon": "Kiel Mastodon: kopii senŝanĝe",
"subject_line_noop": "Ne kopii", "subject_line_noop": "Ne kopii",
"post_status_content_type": "Afiŝi specon de la enhavo de la stato", "post_status_content_type": "Afiŝi specon de la enhavo de la stato",
"status_content_type_plain": "Plata teksto",
"stop_gifs": "Movi GIF-bildojn dum musa ŝvebo", "stop_gifs": "Movi GIF-bildojn dum musa ŝvebo",
"streaming": "Ŝalti memfaran fluigon de novaj afiŝoj ĉe la supro de la paĝo", "streaming": "Ŝalti memfaran fluigon de novaj afiŝoj ĉe la supro de la paĝo",
"text": "Teksto", "text": "Teksto",

View File

@ -202,7 +202,6 @@
"subject_line_mastodon": "Tipo mastodon: copiar como es", "subject_line_mastodon": "Tipo mastodon: copiar como es",
"subject_line_noop": "No copiar", "subject_line_noop": "No copiar",
"post_status_content_type": "Formato de publicación", "post_status_content_type": "Formato de publicación",
"status_content_type_plain": "Texto plano",
"stop_gifs": "Iniciar GIFs al pasar el ratón", "stop_gifs": "Iniciar GIFs al pasar el ratón",
"streaming": "Habilite la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior", "streaming": "Habilite la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
"text": "Texto", "text": "Texto",

View File

@ -202,7 +202,6 @@
"subject_line_mastodon": "マストドンふう: そのままコピー", "subject_line_mastodon": "マストドンふう: そのままコピー",
"subject_line_noop": "コピーしない", "subject_line_noop": "コピーしない",
"post_status_content_type": "とうこうのコンテントタイプ", "post_status_content_type": "とうこうのコンテントタイプ",
"status_content_type_plain": "プレーンテキスト",
"stop_gifs": "カーソルをかさねたとき、GIFをうごかす", "stop_gifs": "カーソルをかさねたとき、GIFをうごかす",
"streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする", "streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする",
"text": "もじ", "text": "もじ",

View File

@ -221,7 +221,6 @@
"subject_line_mastodon": "Coma mastodon: copiar tal coma es", "subject_line_mastodon": "Coma mastodon: copiar tal coma es",
"subject_line_noop": "Copiar pas", "subject_line_noop": "Copiar pas",
"post_status_content_type": "Publicar lo tipe de contengut dels estatuts", "post_status_content_type": "Publicar lo tipe de contengut dels estatuts",
"status_content_type_plain": "Tèxte brut",
"stop_gifs": "Lançar los GIFs al subrevòl", "stop_gifs": "Lançar los GIFs al subrevòl",
"streaming": "Activar lo cargament automatic dels novèls estatus en anar amont", "streaming": "Activar lo cargament automatic dels novèls estatus en anar amont",
"text": "Tèxt", "text": "Tèxt",

View File

@ -221,7 +221,6 @@
"subject_line_mastodon": "Como o Mastodon: copiar como está", "subject_line_mastodon": "Como o Mastodon: copiar como está",
"subject_line_noop": "Não copiar", "subject_line_noop": "Não copiar",
"post_status_content_type": "Postar tipo de conteúdo do status", "post_status_content_type": "Postar tipo de conteúdo do status",
"status_content_type_plain": "Texto puro",
"stop_gifs": "Reproduzir GIFs ao passar o cursor em cima", "stop_gifs": "Reproduzir GIFs ao passar o cursor em cima",
"streaming": "Habilitar o fluxo automático de postagens quando ao topo da página", "streaming": "Habilitar o fluxo automático de postagens quando ao topo da página",
"text": "Texto", "text": "Texto",

View File

@ -37,6 +37,7 @@ const defaultState = {
emoji: [], emoji: [],
customEmoji: [], customEmoji: [],
restrictedNicknames: [], restrictedNicknames: [],
postFormats: [],
// Feature-set, apparently, not everything here is reported... // Feature-set, apparently, not everything here is reported...
mediaProxyAvailable: false, mediaProxyAvailable: false,

View File

@ -1,4 +1,4 @@
import { remove, slice, each, find, maxBy, minBy, merge, last, isArray } from 'lodash' import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray } from 'lodash'
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
// import parse from '../services/status_parser/status_parser.js' // import parse from '../services/status_parser/status_parser.js'
@ -19,7 +19,7 @@ const emptyTl = (userId = 0) => ({
flushMarker: 0 flushMarker: 0
}) })
export const defaultState = { export const defaultState = () => ({
allStatuses: [], allStatuses: [],
allStatusesObject: {}, allStatusesObject: {},
maxId: 0, maxId: 0,
@ -30,7 +30,8 @@ export const defaultState = {
data: [], data: [],
idStore: {}, idStore: {},
loading: false, loading: false,
error: false error: false,
fetcherId: null
}, },
favorites: new Set(), favorites: new Set(),
error: false, error: false,
@ -45,7 +46,7 @@ export const defaultState = {
tag: emptyTl(), tag: emptyTl(),
dms: emptyTl() dms: emptyTl()
} }
} })
export const prepareStatus = (status) => { export const prepareStatus = (status) => {
// Set deleted flag // Set deleted flag
@ -312,9 +313,20 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
}) })
} }
const removeStatus = (state, { timeline, userId }) => {
const timelineObject = state.timelines[timeline]
if (userId) {
remove(timelineObject.statuses, { user: { id: userId } })
remove(timelineObject.visibleStatuses, { user: { id: userId } })
timelineObject.minVisibleId = timelineObject.visibleStatuses.length > 0 ? last(timelineObject.visibleStatuses).id : 0
timelineObject.maxId = timelineObject.statuses.length > 0 ? first(timelineObject.statuses).id : 0
}
}
export const mutations = { export const mutations = {
addNewStatuses, addNewStatuses,
addNewNotifications, addNewNotifications,
removeStatus,
showNewStatuses (state, { timeline }) { showNewStatuses (state, { timeline }) {
const oldTimeline = (state.timelines[timeline]) const oldTimeline = (state.timelines[timeline])
@ -324,6 +336,15 @@ export const mutations = {
oldTimeline.visibleStatusesObject = {} oldTimeline.visibleStatusesObject = {}
each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status }) each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status })
}, },
setNotificationFetcher (state, { fetcherId }) {
state.notifications.fetcherId = fetcherId
},
resetStatuses (state) {
const emptyState = defaultState()
Object.entries(emptyState).forEach(([key, value]) => {
state[key] = value
})
},
clearTimeline (state, { timeline }) { clearTimeline (state, { timeline }) {
state.timelines[timeline] = emptyTl(state.timelines[timeline].userId) state.timelines[timeline] = emptyTl(state.timelines[timeline].userId)
}, },
@ -374,7 +395,7 @@ export const mutations = {
} }
const statuses = { const statuses = {
state: defaultState, state: defaultState(),
actions: { actions: {
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId }) { addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId }) {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId }) commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId })
@ -394,6 +415,12 @@ const statuses = {
setNotificationsSilence ({ rootState, commit }, { value }) { setNotificationsSilence ({ rootState, commit }, { value }) {
commit('setNotificationsSilence', { value }) commit('setNotificationsSilence', { value })
}, },
stopFetchingNotifications ({ rootState, commit }) {
if (rootState.statuses.notifications.fetcherId) {
window.clearInterval(rootState.statuses.notifications.fetcherId)
}
commit('setNotificationFetcher', { fetcherId: null })
},
deleteStatus ({ rootState, commit }, status) { deleteStatus ({ rootState, commit }, status) {
commit('setDeleted', { status }) commit('setDeleted', { status })
apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials }) apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials })

View File

@ -295,6 +295,8 @@ const users = {
store.commit('setToken', false) store.commit('setToken', false)
store.dispatch('stopFetching', 'friends') store.dispatch('stopFetching', 'friends')
store.commit('setBackendInteractor', backendInteractorService()) store.commit('setBackendInteractor', backendInteractorService())
store.dispatch('stopFetchingNotifications')
store.commit('resetStatuses')
}, },
loginUser (store, accessToken) { loginUser (store, accessToken) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -24,7 +24,7 @@ describe('routes', () => {
const matchedComponents = router.getMatchedComponents() const matchedComponents = router.getMatchedComponents()
expect(matchedComponents[0].components.hasOwnProperty('UserCardContent')).to.eql(true) expect(matchedComponents[0].components.hasOwnProperty('UserCard')).to.eql(true)
}) })
it('user\'s profile at /users', () => { it('user\'s profile at /users', () => {
@ -32,6 +32,6 @@ describe('routes', () => {
const matchedComponents = router.getMatchedComponents() const matchedComponents = router.getMatchedComponents()
expect(matchedComponents[0].components.hasOwnProperty('UserCardContent')).to.eql(true) expect(matchedComponents[0].components.hasOwnProperty('UserCard')).to.eql(true)
}) })
}) })

View File

@ -1,4 +1,3 @@
import { cloneDeep } from 'lodash'
import { defaultState, mutations, prepareStatus } from '../../../../src/modules/statuses.js' import { defaultState, mutations, prepareStatus } from '../../../../src/modules/statuses.js'
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
@ -24,7 +23,7 @@ describe('Statuses.prepareStatus', () => {
describe('The Statuses module', () => { describe('The Statuses module', () => {
it('adds the status to allStatuses and to the given timeline', () => { it('adds the status to allStatuses and to the given timeline', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
mutations.addNewStatuses(state, { statuses: [status], timeline: 'public' }) mutations.addNewStatuses(state, { statuses: [status], timeline: 'public' })
@ -36,7 +35,7 @@ describe('The Statuses module', () => {
}) })
it('counts the status as new if it has not been seen on this timeline', () => { it('counts the status as new if it has not been seen on this timeline', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
mutations.addNewStatuses(state, { statuses: [status], timeline: 'public' }) mutations.addNewStatuses(state, { statuses: [status], timeline: 'public' })
@ -54,7 +53,7 @@ describe('The Statuses module', () => {
}) })
it('add the statuses to allStatuses if no timeline is given', () => { it('add the statuses to allStatuses if no timeline is given', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
mutations.addNewStatuses(state, { statuses: [status] }) mutations.addNewStatuses(state, { statuses: [status] })
@ -66,7 +65,7 @@ describe('The Statuses module', () => {
}) })
it('adds the status to allStatuses and to the given timeline, directly visible', () => { it('adds the status to allStatuses and to the given timeline, directly visible', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
@ -78,7 +77,7 @@ describe('The Statuses module', () => {
}) })
it('removes statuses by tag on deletion', () => { it('removes statuses by tag on deletion', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
const otherStatus = makeMockStatus({id: '3'}) const otherStatus = makeMockStatus({id: '3'})
status.uri = 'xxx' status.uri = 'xxx'
@ -96,7 +95,7 @@ describe('The Statuses module', () => {
}) })
it('does not update the maxId when the noIdUpdate flag is set', () => { it('does not update the maxId when the noIdUpdate flag is set', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
const secondStatus = makeMockStatus({id: '2'}) const secondStatus = makeMockStatus({id: '2'})
@ -110,7 +109,7 @@ describe('The Statuses module', () => {
}) })
it('keeps a descending by id order in timeline.visibleStatuses and timeline.statuses', () => { it('keeps a descending by id order in timeline.visibleStatuses and timeline.statuses', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const nonVisibleStatus = makeMockStatus({id: '1'}) const nonVisibleStatus = makeMockStatus({id: '1'})
const status = makeMockStatus({id: '3'}) const status = makeMockStatus({id: '3'})
const statusTwo = makeMockStatus({id: '2'}) const statusTwo = makeMockStatus({id: '2'})
@ -130,7 +129,7 @@ describe('The Statuses module', () => {
}) })
it('splits retweets from their status and links them', () => { it('splits retweets from their status and links them', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
const retweet = makeMockStatus({id: '2', type: 'retweet'}) const retweet = makeMockStatus({id: '2', type: 'retweet'})
const modStatus = makeMockStatus({id: '1', text: 'something else'}) const modStatus = makeMockStatus({id: '1', text: 'something else'})
@ -155,7 +154,7 @@ describe('The Statuses module', () => {
}) })
it('replaces existing statuses with the same id', () => { it('replaces existing statuses with the same id', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
const modStatus = makeMockStatus({id: '1', text: 'something else'}) const modStatus = makeMockStatus({id: '1', text: 'something else'})
@ -172,7 +171,7 @@ describe('The Statuses module', () => {
}) })
it('replaces existing statuses with the same id, coming from a retweet', () => { it('replaces existing statuses with the same id, coming from a retweet', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
const modStatus = makeMockStatus({id: '1', text: 'something else'}) const modStatus = makeMockStatus({id: '1', text: 'something else'})
const retweet = makeMockStatus({id: '2', type: 'retweet'}) const retweet = makeMockStatus({id: '2', type: 'retweet'})
@ -193,7 +192,7 @@ describe('The Statuses module', () => {
}) })
it('handles favorite actions', () => { it('handles favorite actions', () => {
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
const favorite = { const favorite = {
@ -241,7 +240,7 @@ describe('The Statuses module', () => {
}) })
it('keeps userId when clearing user timeline', () => { it('keeps userId when clearing user timeline', () => {
const state = cloneDeep(defaultState) const state = defaultState()
state.timelines.user.userId = 123 state.timelines.user.userId = 123
mutations.clearTimeline(state, { timeline: 'user' }) mutations.clearTimeline(state, { timeline: 'user' })
@ -252,7 +251,7 @@ describe('The Statuses module', () => {
describe('notifications', () => { describe('notifications', () => {
it('removes a notification when the notice gets removed', () => { it('removes a notification when the notice gets removed', () => {
const user = { id: '1' } const user = { id: '1' }
const state = cloneDeep(defaultState) const state = defaultState()
const status = makeMockStatus({id: '1'}) const status = makeMockStatus({id: '1'})
const otherStatus = makeMockStatus({id: '3'}) const otherStatus = makeMockStatus({id: '3'})
const mentionedStatus = makeMockStatus({id: '2'}) const mentionedStatus = makeMockStatus({id: '2'})