Merge remote-tracking branch 'upstream/develop' into feature/theming2
* upstream/develop: Fix iOS Safari from making videos play fullscreen by default added PR comments resolved the lint used the deleted data param as condition in status template Switch to "timeline" when pressing user-settings Added user setting tooltip made links in user bio always open in new tabs addressed PR comments added tooltip Add userId property to timelines so that we don't overwrite user timeline meant for another user Added option to auto-hide subject field when it's empty. removes hacks from notifications storage, adds api call to let server update is_seen attribute fixes vimium not giving retweet button a hint Do not use underscore at the beginning of the method Logout user on password change Route user to the correct profile URL Typo Fix filetype detection Switch to settings when touching settings Switch to timeline on nav panel actions
This commit is contained in:
commit
51cf4dc298
35 changed files with 134 additions and 52 deletions
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
<div class='item right'>
|
||||
<user-finder class="nav-icon"></user-finder>
|
||||
<router-link :to="{ name: 'settings'}"><i class="icon-cog nav-icon"></i></router-link>
|
||||
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'settings'}"><i class="icon-cog nav-icon" :title="$t('nav.preferences')"></i></router-link>
|
||||
<a href="#" v-if="currentUser" @click.prevent="logout"><i class="icon-logout nav-icon" :title="$t('login.logout')"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -25,8 +25,8 @@
|
|||
<div class="sidebar-bounds">
|
||||
<div class="sidebar-scroller">
|
||||
<div class="sidebar">
|
||||
<user-panel></user-panel>
|
||||
<nav-panel></nav-panel>
|
||||
<user-panel :activatePanel="activatePanel"></user-panel>
|
||||
<nav-panel :activatePanel="activatePanel"></nav-panel>
|
||||
<instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel>
|
||||
<features-panel v-if="!currentUser"></features-panel>
|
||||
<who-to-follow-panel v-if="currentUser && suggestionsEnabled"></who-to-follow-panel>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<StillImage :class="{'small': isSmall}" referrerpolicy="no-referrer" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url"/>
|
||||
</a>
|
||||
|
||||
<video :class="{'small': isSmall}" v-if="type === 'video' && !hidden" @loadeddata="onVideoDataLoad" :src="attachment.url" controls :loop="loopVideo"></video>
|
||||
<video :class="{'small': isSmall}" v-if="type === 'video' && !hidden" @loadeddata="onVideoDataLoad" :src="attachment.url" controls :loop="loopVideo" playsinline></video>
|
||||
|
||||
<audio v-if="type === 'audio'" :src="attachment.url" controls></audio>
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div v-if="loggedIn">
|
||||
<i :class='classes' class='favorite-button fav-active' @click.prevent='favorite()'/>
|
||||
<i :class='classes' class='favorite-button fav-active' @click.prevent='favorite()' :title="$t('tool_tip.favorite')"/>
|
||||
<span v-if='!hidePostStatsLocal && status.fave_num > 0'>{{status.fave_num}}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<i :class='classes' class='favorite-button'/>
|
||||
<i :class='classes' class='favorite-button' :title="$t('tool_tip.favorite')"/>
|
||||
<span v-if='!hidePostStatsLocal && status.fave_num > 0'>{{status.fave_num}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="media-upload" @drop.prevent @dragover.prevent="fileDrag" @drop="fileDrop">
|
||||
<label class="btn btn-default">
|
||||
<label class="btn btn-default" :title="$t('tool_tip.media_upload')">
|
||||
<i class="icon-spin4 animate-spin" v-if="uploading"></i>
|
||||
<i class="icon-upload" v-if="!uploading"></i>
|
||||
<input type="file" style="position: fixed; top: -100em" multiple="true"></input>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const NavPanel = {
|
||||
props: [ 'activatePanel' ],
|
||||
computed: {
|
||||
currentUser () {
|
||||
return this.$store.state.users.currentUser
|
||||
|
|
|
@ -3,32 +3,32 @@
|
|||
<div class="panel panel-default">
|
||||
<ul>
|
||||
<li v-if='currentUser'>
|
||||
<router-link to='/main/friends'>
|
||||
<router-link @click.native="activatePanel('timeline')" to='/main/friends'>
|
||||
{{ $t("nav.timeline") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if='currentUser'>
|
||||
<router-link :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
|
||||
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
|
||||
{{ $t("nav.mentions") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if='currentUser'>
|
||||
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
|
||||
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
|
||||
{{ $t("nav.dms") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if='currentUser && currentUser.locked'>
|
||||
<router-link to='/friend-requests'>
|
||||
<router-link @click.native="activatePanel('timeline')" to='/friend-requests'>
|
||||
{{ $t("nav.friend_requests") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to='/main/public'>
|
||||
<router-link @click.native="activatePanel('timeline')" to='/main/public'>
|
||||
{{ $t("nav.public_tl") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to='/main/all'>
|
||||
<router-link @click.native="activatePanel('timeline')" to='/main/all'>
|
||||
{{ $t("nav.twkn") }}
|
||||
</router-link>
|
||||
</li>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<small>{{$t('notifications.favorited_you')}}</small>
|
||||
</span>
|
||||
<span v-if="notification.type === 'repeat'">
|
||||
<i class="fa icon-retweet lit"></i>
|
||||
<i class="fa icon-retweet lit" :title="$t('tool_tip.repeat')"></i>
|
||||
<small>{{$t('notifications.repeated_you')}}</small>
|
||||
</span>
|
||||
<span v-if="notification.type === 'follow'">
|
||||
|
|
|
@ -52,7 +52,7 @@ const Notifications = {
|
|||
},
|
||||
methods: {
|
||||
markAsSeen () {
|
||||
this.$store.commit('markNotificationsAsSeen', this.visibleNotifications)
|
||||
this.$store.dispatch('markNotificationsAsSeen', this.visibleNotifications)
|
||||
},
|
||||
fetchOlderNotifications () {
|
||||
const store = this.$store
|
||||
|
|
|
@ -150,6 +150,15 @@ const PostStatusForm = {
|
|||
scopeOptionsEnabled () {
|
||||
return this.$store.state.instance.scopeOptionsEnabled
|
||||
},
|
||||
alwaysShowSubject () {
|
||||
if (typeof this.$store.state.config.alwaysShowSubjectInput !== 'undefined') {
|
||||
return this.$store.state.config.alwaysShowSubjectInput
|
||||
} else if (typeof this.$store.state.instance.alwaysShowSubjectInput !== 'undefined') {
|
||||
return this.$store.state.instance.alwaysShowSubjectInput
|
||||
} else {
|
||||
return this.$store.state.instance.scopeOptionsEnabled
|
||||
}
|
||||
},
|
||||
formattingOptionsEnabled () {
|
||||
return this.$store.state.instance.formattingOptionsEnabled
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</i18n>
|
||||
<p v-if="this.newStatus.visibility == 'direct'" class="visibility-notice">{{ $t('post_status.direct_warning') }}</p>
|
||||
<input
|
||||
v-if="scopeOptionsEnabled"
|
||||
v-if="newStatus.spoilerText || alwaysShowSubject"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.content_warning')"
|
||||
v-model="newStatus.spoilerText"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div v-if="loggedIn">
|
||||
<template v-if="visibility !== 'private' && visibility !== 'direct'">
|
||||
<i :class='classes' class='icon-retweet rt-active' v-on:click.prevent='retweet()'></i>
|
||||
<i :class='classes' class='retweet-button icon-retweet rt-active' v-on:click.prevent='retweet()' :title="$t('tool_tip.repeat')"></i>
|
||||
<span v-if='!hidePostStatsLocal && status.repeat_num > 0'>{{status.repeat_num}}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
@ -9,7 +9,7 @@
|
|||
</template>
|
||||
</div>
|
||||
<div v-else-if="!loggedIn">
|
||||
<i :class='classes' class='icon-retweet'></i>
|
||||
<i :class='classes' class='icon-retweet' :title="$t('tool_tip.repeat')"></i>
|
||||
<span v-if='!hidePostStatsLocal && status.repeat_num > 0'>{{status.repeat_num}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -38,6 +38,10 @@ const settings = {
|
|||
? instance.subjectLineBehavior
|
||||
: user.subjectLineBehavior,
|
||||
subjectLineBehaviorDefault: instance.subjectLineBehavior,
|
||||
alwaysShowSubjectInputLocal: typeof user.alwaysShowSubjectInput === 'undefined'
|
||||
? instance.alwaysShowSubjectInput
|
||||
: user.alwaysShowSubjectInput,
|
||||
alwaysShowSubjectInputDefault: instance.alwaysShowSubjectInput,
|
||||
scopeCopyLocal: user.scopeCopy,
|
||||
scopeCopyDefault: this.$t('settings.values.' + instance.scopeCopy),
|
||||
stopGifs: user.stopGifs,
|
||||
|
@ -122,6 +126,9 @@ const settings = {
|
|||
scopeCopyLocal (value) {
|
||||
this.$store.dispatch('setOption', { name: 'scopeCopy', value })
|
||||
},
|
||||
alwaysShowSubjectInputLocal (value) {
|
||||
this.$store.dispatch('setOption', { name: 'alwaysShowSubjectInput', value })
|
||||
},
|
||||
subjectLineBehaviorLocal (value) {
|
||||
this.$store.dispatch('setOption', { name: 'subjectLineBehavior', value })
|
||||
},
|
||||
|
|
|
@ -64,6 +64,12 @@
|
|||
{{$t('settings.scope_copy')}} {{$t('settings.instance_default', { value: scopeCopyDefault })}}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="subjectHide" v-model="alwaysShowSubjectInputLocal">
|
||||
<label for="subjectHide">
|
||||
{{$t('settings.subject_input_always_show')}} {{$t('settings.instance_default', { value: alwaysShowSubjectInputDefault })}}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
{{$t('settings.subject_line_behavior')}}
|
||||
|
|
|
@ -54,6 +54,9 @@ const Status = {
|
|||
const user = this.retweet ? (this.statusoid.retweeted_status.user) : this.statusoid.user
|
||||
return highlightClass(user)
|
||||
},
|
||||
deleted () {
|
||||
return this.statusoid.deleted
|
||||
},
|
||||
repeaterStyle () {
|
||||
const user = this.statusoid.user
|
||||
const highlight = this.$store.state.config.highlight
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="status-el" v-if="!hideReply" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
|
||||
<div class="status-el" v-if="!hideReply && !deleted" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
|
||||
<template v-if="muted && !noReplyLinks">
|
||||
<div class="media status container muted">
|
||||
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<div class="media-body faint">
|
||||
<a v-if="retweeterHtml" :href="statusoid.user.statusnet_profile_url" class="user-name" :title="'@'+statusoid.user.screen_name" v-html="retweeterHtml"></a>
|
||||
<a v-else :href="statusoid.user.statusnet_profile_url" class="user-name" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a>
|
||||
<i class='fa icon-retweet retweeted'></i>
|
||||
<i class='fa icon-retweet retweeted' :title="$t('tool_tip.repeat')"></i>
|
||||
{{$t('timeline.repeated')}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -41,7 +41,7 @@
|
|||
{{status.in_reply_to_screen_name}}
|
||||
</router-link>
|
||||
</span>
|
||||
<a v-if="isReply && !noReplyLinks" href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)">
|
||||
<a v-if="isReply && !noReplyLinks" href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)" :title="$t('tool_tip.reply')">
|
||||
<i class="icon-reply" @mouseenter="replyEnter(status.in_reply_to_status_id, $event)" @mouseout="replyLeave()"></i>
|
||||
</a>
|
||||
</span>
|
||||
|
@ -94,7 +94,7 @@
|
|||
|
||||
<div v-if="!noHeading && !noReplyLinks" class='status-actions media-body'>
|
||||
<div v-if="loggedIn">
|
||||
<a href="#" v-on:click.prevent="toggleReplying">
|
||||
<a href="#" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')">
|
||||
<i class="icon-reply" :class="{'icon-reply-active': replying}"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
{{ $t('user_card.follows_you') }}
|
||||
</span>
|
||||
</div>
|
||||
<a :href="user.statusnet_profile_url" target="blank"><div class="user-screen-name">@{{ user.screen_name }}</div></a>
|
||||
<router-link class='user-screen-name' :to="{ name: 'user-profile', params: { id: user.id } }">
|
||||
@{{user.screen_name}}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="approval" v-if="showApproval">
|
||||
<button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
|
||||
|
|
|
@ -2,7 +2,7 @@ import StillImage from '../still-image/still-image.vue'
|
|||
import { hex2rgb } from '../../services/color_convert/color_convert.js'
|
||||
|
||||
export default {
|
||||
props: [ 'user', 'switcher', 'selected', 'hideBio' ],
|
||||
props: [ 'user', 'switcher', 'selected', 'hideBio', 'activatePanel' ],
|
||||
data () {
|
||||
return {
|
||||
hideUserStatsLocal: typeof this.$store.state.config.hideUserStats === 'undefined'
|
||||
|
@ -102,6 +102,14 @@ export default {
|
|||
const store = this.$store
|
||||
store.commit('setProfileView', { v })
|
||||
}
|
||||
},
|
||||
linkClicked ({target}) {
|
||||
if (target.tagName === 'SPAN') {
|
||||
target = target.parentNode
|
||||
}
|
||||
if (target.tagName === 'A') {
|
||||
window.open(target.href, '_blank')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<div id="heading" class="profile-panel-background" :style="headingStyle">
|
||||
<div class="panel-heading text-center">
|
||||
<div class='user-info'>
|
||||
<router-link to='/user-settings' style="float: right; margin-top:16px;" v-if="!isOtherUser">
|
||||
<i class="icon-cog usersettings"></i>
|
||||
<router-link @click.native="activatePanel('timeline')" to='/user-settings' style="float: right; margin-top:16px;" v-if="!isOtherUser">
|
||||
<i class="icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
</router-link>
|
||||
<a :href="user.statusnet_profile_url" target="_blank" class="floater" v-if="isOtherUser">
|
||||
<i class="icon-link-ext usersettings"></i>
|
||||
|
@ -105,7 +105,7 @@
|
|||
<span v-if="!hideUserStatsLocal">{{user.followers_count}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p 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="profile-bio" v-html="user.description_html"></p>
|
||||
<p v-else-if="!hideBio" class="profile-bio">{{ user.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<span class="user-finder-container">
|
||||
<i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" />
|
||||
<a href="#" v-if="hidden"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden"/></a>
|
||||
<a href="#" v-if="hidden" :title="$t('finder.find_user')" ><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden" /></a>
|
||||
<span v-else>
|
||||
<input class="user-finder-input" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/>
|
||||
<i class="icon-cancel user-finder-icon" @click.prevent.stop="toggleHidden"/>
|
||||
|
|
|
@ -3,6 +3,7 @@ import PostStatusForm from '../post_status_form/post_status_form.vue'
|
|||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
|
||||
const UserPanel = {
|
||||
props: [ 'activatePanel' ],
|
||||
computed: {
|
||||
user () { return this.$store.state.users.currentUser }
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="user-panel">
|
||||
<div v-if='user' class="panel panel-default" style="overflow: visible;">
|
||||
<user-card-content :user="user" :switcher="false" :hideBio="true"></user-card-content>
|
||||
<user-card-content :activatePanel="activatePanel" :user="user" :switcher="false" :hideBio="true"></user-card-content>
|
||||
<div class="panel-footer">
|
||||
<post-status-form v-if='user'></post-status-form>
|
||||
</div>
|
||||
|
|
|
@ -27,6 +27,7 @@ const UserProfile = {
|
|||
},
|
||||
watch: {
|
||||
userId () {
|
||||
this.$store.dispatch('stopFetching', 'user')
|
||||
this.$store.commit('clearTimeline', { timeline: 'user' })
|
||||
this.$store.dispatch('startFetching', ['user', this.userId])
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="user-seach panel panel-default">
|
||||
<div class="user-search panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{{$t('nav.user_search')}}
|
||||
</div>
|
||||
|
|
|
@ -235,6 +235,7 @@ const UserSettings = {
|
|||
if (res.status === 'success') {
|
||||
this.changedPassword = true
|
||||
this.changePasswordError = false
|
||||
this.logout()
|
||||
} else {
|
||||
this.changedPassword = false
|
||||
this.changePasswordError = res.error
|
||||
|
@ -243,6 +244,10 @@ const UserSettings = {
|
|||
},
|
||||
activateTab (tabName) {
|
||||
this.activeTab = tabName
|
||||
},
|
||||
logout () {
|
||||
this.$store.dispatch('logout')
|
||||
this.$router.replace('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
<div v-if="scopeOptionsEnabled">
|
||||
<label for="default-vis">{{$t('settings.default_vis')}}</label>
|
||||
<div id="default-vis" class="visibility-tray">
|
||||
<i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct"></i>
|
||||
<i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private"></i>
|
||||
<i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted"></i>
|
||||
<i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public"></i>
|
||||
<i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct" :title="$t('post_status.scope.direct')" ></i>
|
||||
<i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private" :title="$t('post_status.scope.private')"></i>
|
||||
<i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted" :title="$t('post_status.scope.unlisted')"></i>
|
||||
<i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public" :title="$t('post_status.scope.public')"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
"public_tl": "Public Timeline",
|
||||
"timeline": "Timeline",
|
||||
"twkn": "The Whole Known Network",
|
||||
"user_search": "User Search"
|
||||
"user_search": "User Search",
|
||||
"preferences": "Preferences"
|
||||
},
|
||||
"notifications": {
|
||||
"broken_favorite": "Unknown status, searching for it...",
|
||||
|
@ -160,6 +161,7 @@
|
|||
"set_new_profile_background": "Set new profile background",
|
||||
"set_new_profile_banner": "Set new profile banner",
|
||||
"settings": "Settings",
|
||||
"subject_input_always_show": "Always show subject field",
|
||||
"subject_line_behavior": "Copy subject when replying",
|
||||
"subject_line_email": "Like email: \"re: subject\"",
|
||||
"subject_line_mastodon": "Like mastodon: copy as is",
|
||||
|
@ -318,5 +320,12 @@
|
|||
"who_to_follow": {
|
||||
"more": "More",
|
||||
"who_to_follow": "Who to follow"
|
||||
},
|
||||
"tool_tip": {
|
||||
"media_upload": "Upload Media",
|
||||
"repeat": "Repeat",
|
||||
"reply": "Reply",
|
||||
"favorite": "Favorite",
|
||||
"user_settings": "User Settings"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,6 +134,7 @@
|
|||
"set_new_profile_background": "Загрузить новый фон профиля",
|
||||
"set_new_profile_banner": "Загрузить новый баннер профиля",
|
||||
"settings": "Настройки",
|
||||
"subject_input_always_show": "Всегда показывать поле ввода темы",
|
||||
"stop_gifs": "Проигрывать GIF анимации только при наведении",
|
||||
"streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
|
||||
"text": "Текст",
|
||||
|
|
|
@ -47,7 +47,6 @@ const persistedStateOptions = {
|
|||
paths: [
|
||||
'config',
|
||||
'users.lastLoginName',
|
||||
'statuses.notifications.maxSavedId',
|
||||
'oauth'
|
||||
]
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ const defaultState = {
|
|||
highlight: {},
|
||||
interfaceLanguage: browserLocale,
|
||||
scopeCopy: undefined, // instance default
|
||||
subjectLineBehavior: undefined // instance default
|
||||
subjectLineBehavior: undefined, // instance default
|
||||
alwaysShowSubjectInput: undefined // instance default
|
||||
}
|
||||
|
||||
const config = {
|
||||
|
|
|
@ -17,6 +17,7 @@ const defaultState = {
|
|||
showInstanceSpecificPanel: false,
|
||||
scopeOptionsEnabled: true,
|
||||
formattingOptionsEnabled: false,
|
||||
alwaysShowSubjectInput: true,
|
||||
collapseMessageWithSubject: false,
|
||||
hidePostStats: false,
|
||||
hideUserStats: false,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { includes, remove, slice, sortBy, toInteger, each, find, flatten, maxBy, minBy, merge, last, isArray } from 'lodash'
|
||||
import { set } from 'vue'
|
||||
import apiService from '../services/api/api.service.js'
|
||||
// import parse from '../services/status_parser/status_parser.js'
|
||||
|
||||
|
@ -16,6 +15,7 @@ const emptyTl = () => ({
|
|||
followers: [],
|
||||
friends: [],
|
||||
viewing: 'statuses',
|
||||
userId: 0,
|
||||
flushMarker: 0
|
||||
})
|
||||
|
||||
|
@ -26,7 +26,6 @@ export const defaultState = {
|
|||
notifications: {
|
||||
desktopNotificationSilence: true,
|
||||
maxId: 0,
|
||||
maxSavedId: 0,
|
||||
minId: Number.POSITIVE_INFINITY,
|
||||
data: [],
|
||||
error: false,
|
||||
|
@ -132,7 +131,7 @@ const sortTimeline = (timeline) => {
|
|||
return timeline
|
||||
}
|
||||
|
||||
const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {}, noIdUpdate = false }) => {
|
||||
const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {}, noIdUpdate = false, userId }) => {
|
||||
// Sanity check
|
||||
if (!isArray(statuses)) {
|
||||
return false
|
||||
|
@ -149,6 +148,13 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
timelineObject.maxId = maxNew
|
||||
}
|
||||
|
||||
// This makes sure that user timeline won't get data meant for other
|
||||
// user. I.e. opening different user profiles makes request which could
|
||||
// return data late after user already viewing different user profile
|
||||
if (timeline === 'user' && timelineObject.userId !== userId) {
|
||||
return
|
||||
}
|
||||
|
||||
const addStatus = (status, showImmediately, addToTimeline = true) => {
|
||||
const result = mergeOrAdd(allStatuses, allStatusesObject, status)
|
||||
status = result.item
|
||||
|
@ -297,7 +303,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
|||
state.notifications.maxId = Math.max(notification.id, state.notifications.maxId)
|
||||
state.notifications.minId = Math.min(notification.id, state.notifications.minId)
|
||||
|
||||
const fresh = !older && !notification.is_seen && notification.id > state.notifications.maxSavedId
|
||||
const fresh = !notification.is_seen
|
||||
const status = notification.ntype === 'like'
|
||||
? find(allStatuses, { id: action.in_reply_to_status_id })
|
||||
: action
|
||||
|
@ -306,7 +312,6 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
|||
type: notification.ntype,
|
||||
status,
|
||||
action,
|
||||
// Always assume older notifications as seen
|
||||
seen: !fresh
|
||||
}
|
||||
|
||||
|
@ -404,9 +409,8 @@ export const mutations = {
|
|||
addFollowers (state, { followers }) {
|
||||
state.timelines['user'].followers = followers
|
||||
},
|
||||
markNotificationsAsSeen (state, notifications) {
|
||||
set(state.notifications, 'maxSavedId', state.notifications.maxId)
|
||||
each(notifications, (notification) => {
|
||||
markNotificationsAsSeen (state) {
|
||||
each(state.notifications.data, (notification) => {
|
||||
notification.seen = true
|
||||
})
|
||||
},
|
||||
|
@ -418,8 +422,8 @@ export const mutations = {
|
|||
const statuses = {
|
||||
state: defaultState,
|
||||
actions: {
|
||||
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false }) {
|
||||
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser })
|
||||
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId }) {
|
||||
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId })
|
||||
},
|
||||
addNewNotifications ({ rootState, commit, dispatch }, { notifications, older }) {
|
||||
commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older })
|
||||
|
@ -484,6 +488,13 @@ const statuses = {
|
|||
},
|
||||
queueFlush ({ rootState, commit }, { timeline, id }) {
|
||||
commit('queueFlush', { timeline, id })
|
||||
},
|
||||
markNotificationsAsSeen ({ rootState, commit }) {
|
||||
commit('markNotificationsAsSeen')
|
||||
apiService.markNotificationsAsSeen({
|
||||
id: rootState.statuses.notifications.maxId,
|
||||
credentials: rootState.users.currentUser.credentials
|
||||
})
|
||||
}
|
||||
},
|
||||
mutations
|
||||
|
|
|
@ -29,6 +29,7 @@ const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
|
|||
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
||||
const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json'
|
||||
const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json'
|
||||
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
||||
const BLOCKING_URL = '/api/blocks/create.json'
|
||||
const UNBLOCKING_URL = '/api/blocks/destroy.json'
|
||||
const USER_URL = '/api/users/show.json'
|
||||
|
@ -460,6 +461,18 @@ const suggestions = ({credentials}) => {
|
|||
}).then((data) => data.json())
|
||||
}
|
||||
|
||||
const markNotificationsAsSeen = ({id, credentials}) => {
|
||||
const body = new FormData()
|
||||
|
||||
body.append('latest_id', id)
|
||||
|
||||
return fetch(QVITTER_USER_NOTIFICATIONS_READ_URL, {
|
||||
body,
|
||||
headers: authHeaders(credentials),
|
||||
method: 'POST'
|
||||
}).then((data) => data.json())
|
||||
}
|
||||
|
||||
const apiService = {
|
||||
verifyCredentials,
|
||||
fetchTimeline,
|
||||
|
@ -494,7 +507,8 @@ const apiService = {
|
|||
fetchFollowRequests,
|
||||
approveUser,
|
||||
denyUser,
|
||||
suggestions
|
||||
suggestions,
|
||||
markNotificationsAsSeen
|
||||
}
|
||||
|
||||
export default apiService
|
||||
|
|
|
@ -9,11 +9,11 @@ const fileType = (typeString) => {
|
|||
type = 'image'
|
||||
}
|
||||
|
||||
if (typeString.match(/video\/(webm|mp4)/)) {
|
||||
if (typeString.match(/video/)) {
|
||||
type = 'video'
|
||||
}
|
||||
|
||||
if (typeString.match(/audio|ogg/)) {
|
||||
if (typeString.match(/audio/)) {
|
||||
type = 'audio'
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,14 @@ import { camelCase } from 'lodash'
|
|||
|
||||
import apiService from '../api/api.service.js'
|
||||
|
||||
const update = ({store, statuses, timeline, showImmediately}) => {
|
||||
const update = ({store, statuses, timeline, showImmediately, userId}) => {
|
||||
const ccTimeline = camelCase(timeline)
|
||||
|
||||
store.dispatch('setError', { value: false })
|
||||
|
||||
store.dispatch('addNewStatuses', {
|
||||
timeline: ccTimeline,
|
||||
userId,
|
||||
statuses,
|
||||
showImmediately
|
||||
})
|
||||
|
@ -33,7 +34,7 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
|
|||
if (!older && statuses.length >= 20 && !timelineData.loading) {
|
||||
store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
|
||||
}
|
||||
update({store, statuses, timeline, showImmediately})
|
||||
update({store, statuses, timeline, showImmediately, userId})
|
||||
}, () => store.dispatch('setError', { value: true }))
|
||||
}
|
||||
|
||||
|
@ -41,6 +42,7 @@ const startFetching = ({timeline = 'friends', credentials, store, userId = false
|
|||
const rootState = store.rootState || store.state
|
||||
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
|
||||
const showImmediately = timelineData.visibleStatuses.length === 0
|
||||
timelineData.userId = userId
|
||||
fetchAndUpdate({timeline, credentials, store, showImmediately, userId, tag})
|
||||
const boundFetchAndUpdate = () => fetchAndUpdate({ timeline, credentials, store, userId, tag })
|
||||
return setInterval(boundFetchAndUpdate, 10000)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"collapseMessageWithSubject": false,
|
||||
"scopeCopy": false,
|
||||
"subjectLineBehavior": "email",
|
||||
"alwaysShowSubjectInput": true,
|
||||
"hidePostStats": false,
|
||||
"hideUserStats": false,
|
||||
"loginMethod": "password"
|
||||
|
|
Loading…
Reference in a new issue