Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma-fe into issue-436-mastoapi-notifications

This commit is contained in:
dave 2019-04-02 10:07:36 -04:00
commit 8b3f037f87
47 changed files with 813 additions and 150 deletions

View file

@ -6,7 +6,7 @@ const FeaturesPanel = {
gopher: function () { return this.$store.state.instance.gopherAvailable },
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
scopeOptions: function () { return this.$store.state.instance.scopeOptionsEnabled },
minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode },
textlimit: function () { return this.$store.state.instance.textlimit }
}
}

View file

@ -12,7 +12,7 @@
<li v-if="gopher">{{$t('features_panel.gopher')}}</li>
<li v-if="whoToFollow">{{$t('features_panel.who_to_follow')}}</li>
<li v-if="mediaProxy">{{$t('features_panel.media_proxy')}}</li>
<li v-if="scopeOptions">{{$t('features_panel.scope_options')}}</li>
<li>{{$t('features_panel.scope_options')}}</li>
<li>{{$t('features_panel.text_limit')}} = {{textlimit}}</li>
</ul>
</div>

View file

@ -0,0 +1,77 @@
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
},
data: () => ({
notificationsCloseGesture: undefined,
notificationsOpen: false
}),
created () {
this.notificationsCloseGesture = GestureService.swipeGesture(
GestureService.DIRECTION_RIGHT,
this.closeMobileNotifications,
50
)
},
computed: {
currentUser () {
return this.$store.state.users.currentUser
},
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},
unseenNotificationsCount () {
return this.unseenNotifications.length
},
sitename () { return this.$store.state.instance.name }
},
methods: {
toggleMobileSidebar () {
this.$refs.sideDrawer.toggleDrawer()
},
openMobileNotifications () {
this.notificationsOpen = true
},
closeMobileNotifications () {
if (this.notificationsOpen) {
// make sure to mark notifs seen only when the notifs were open and not
// from close-calls.
this.notificationsOpen = false
this.markNotificationsAsSeen()
}
},
notificationsTouchStart (e) {
GestureService.beginSwipe(e, this.notificationsCloseGesture)
},
notificationsTouchMove (e) {
GestureService.updateSwipe(e, this.notificationsCloseGesture)
},
scrollToTop () {
window.scrollTo(0, 0)
},
logout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
},
markNotificationsAsSeen () {
this.$refs.notifications.markAsSeen()
}
},
watch: {
$route () {
// handles closing notificaitons when you press any router-link on the
// notifications.
this.closeMobileNotifications()
}
}
}
export default MobileNav

View file

@ -0,0 +1,140 @@
<template>
<nav class='nav-bar container' id="nav">
<div class='mobile-inner-nav' @click="scrollToTop()">
<div class='item'>
<a href="#" class="mobile-nav-button" @click.stop.prevent="toggleMobileSidebar()">
<i class="button-icon icon-menu"></i>
</a>
<router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link>
</div>
<div class='item right'>
<a class="mobile-nav-button" v-if="currentUser" href="#" @click.stop.prevent="openMobileNotifications()">
<i class="button-icon icon-bell-alt"></i>
<div class="alert-dot" v-if="unseenNotificationsCount"></div>
</a>
</div>
</div>
<SideDrawer ref="sideDrawer" :logout="logout"/>
<div v-if="currentUser"
class="mobile-notifications-drawer"
:class="{ 'closed': !notificationsOpen }"
@touchstart="notificationsTouchStart"
@touchmove="notificationsTouchMove"
>
<div class="mobile-notifications-header">
<span class="title">{{$t('notifications.notifications')}}</span>
<a class="mobile-nav-button" @click.stop.prevent="closeMobileNotifications()">
<i class="button-icon icon-cancel"/>
</a>
</div>
<div v-if="currentUser" class="mobile-notifications">
<Notifications ref="notifications" noHeading="true"/>
</div>
</div>
<MobilePostStatusModal />
</nav>
</template>
<script src="./mobile_nav.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.mobile-inner-nav {
width: 100%;
display: flex;
align-items: center;
}
.mobile-nav-button {
display: flex;
justify-content: center;
width: 50px;
position: relative;
cursor: pointer;
}
.alert-dot {
border-radius: 100%;
height: 8px;
width: 8px;
position: absolute;
left: calc(50% - 4px);
top: calc(50% - 4px);
margin-left: 6px;
margin-top: -6px;
background-color: $fallback--cRed;
background-color: var(--badgeNotification, $fallback--cRed);
}
.mobile-notifications-drawer {
width: 100%;
height: 100vh;
overflow-x: hidden;
position: fixed;
top: 0;
left: 0;
box-shadow: 1px 1px 4px rgba(0,0,0,.6);
box-shadow: var(--panelShadow);
transition-property: transform;
transition-duration: 0.25s;
transform: translateX(0);
&.closed {
transform: translateX(100%);
}
}
.mobile-notifications-header {
display: flex;
align-items: center;
justify-content: space-between;
z-index: 1;
width: 100%;
height: 50px;
line-height: 50px;
position: absolute;
color: var(--topBarText);
background-color: $fallback--fg;
background-color: var(--topBar, $fallback--fg);
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
box-shadow: var(--topBarShadow);
.title {
font-size: 1.3em;
margin-left: 0.6em;
}
}
.mobile-notifications {
margin-top: 50px;
width: 100vw;
height: calc(100vh - 50px);
overflow-x: hidden;
overflow-y: scroll;
color: $fallback--text;
color: var(--text, $fallback--text);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
.notifications {
padding: 0;
border-radius: 0;
box-shadow: none;
.panel {
border-radius: 0;
margin: 0;
box-shadow: none;
}
.panel:after {
border-radius: 0;
}
.panel .panel-heading {
border-radius: 0;
box-shadow: none;
}
}
}
</style>

View file

@ -7,6 +7,9 @@ import {
} from '../../services/notification_utils/notification_utils.js'
const Notifications = {
props: [
'noHeading'
],
created () {
const store = this.$store
const credentials = store.state.users.currentUser.credentials

View file

@ -1,7 +1,7 @@
<template>
<div class="notifications">
<div class="panel panel-default">
<div class="panel-heading">
<div v-if="!noHeading" class="panel-heading">
<div class="title">
{{$t('notifications.notifications')}}
<span class="badge badge-notification unseen-count" v-if="unseenCount">{{unseenCount}}</span>

View file

@ -1,5 +1,6 @@
import statusPoster from '../../services/status_poster/status_poster.service.js'
import MediaUpload from '../media_upload/media_upload.vue'
import ScopeSelector from '../scope_selector/scope_selector.vue'
import EmojiInput from '../emoji-input/emoji-input.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import Completion from '../../services/completion/completion.js'
@ -30,6 +31,7 @@ const PostStatusForm = {
],
components: {
MediaUpload,
ScopeSelector,
EmojiInput
},
mounted () {
@ -80,14 +82,6 @@ const PostStatusForm = {
}
},
computed: {
vis () {
return {
public: { selected: this.newStatus.visibility === 'public' },
unlisted: { selected: this.newStatus.visibility === 'unlisted' },
private: { selected: this.newStatus.visibility === 'private' },
direct: { selected: this.newStatus.visibility === 'direct' }
}
},
candidates () {
const firstchar = this.textAtCaret.charAt(0)
if (firstchar === '@') {
@ -135,6 +129,15 @@ const PostStatusForm = {
users () {
return this.$store.state.users.users
},
userDefaultScope () {
return this.$store.state.users.currentUser.default_scope
},
showAllScopes () {
const minimalScopesMode = typeof this.$store.state.config.minimalScopesMode === 'undefined'
? this.$store.state.instance.minimalScopesMode
: this.$store.state.config.minimalScopesMode
return !minimalScopesMode
},
emoji () {
return this.$store.state.instance.emoji || []
},
@ -159,8 +162,8 @@ const PostStatusForm = {
isOverLengthLimit () {
return this.hasStatusLengthLimit && (this.charactersLeft < 0)
},
scopeOptionsEnabled () {
return this.$store.state.instance.scopeOptionsEnabled
minimalScopesMode () {
return this.$store.state.instance.minimalScopesMode
},
alwaysShowSubject () {
if (typeof this.$store.state.config.alwaysShowSubjectInput !== 'undefined') {
@ -168,7 +171,7 @@ const PostStatusForm = {
} else if (typeof this.$store.state.instance.alwaysShowSubjectInput !== 'undefined') {
return this.$store.state.instance.alwaysShowSubjectInput
} else {
return this.$store.state.instance.scopeOptionsEnabled
return true
}
},
formattingOptionsEnabled () {

View file

@ -48,12 +48,12 @@
</label>
</span>
<div v-if="scopeOptionsEnabled">
<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>
<scope-selector
:showAll="showAllScopes"
:userDefault="userDefaultScope"
:originalScope="copyMessageScope"
:initialScope="newStatus.visibility"
:onScopeChange="changeVis"/>
</div>
</div>
<div class="autocomplete-panel" v-if="candidates">

View file

@ -0,0 +1,54 @@
const ScopeSelector = {
props: [
'showAll',
'userDefault',
'originalScope',
'initialScope',
'onScopeChange'
],
data () {
return {
currentScope: this.initialScope
}
},
computed: {
showNothing () {
return !this.showPublic && !this.showUnlisted && !this.showPrivate && !this.showDirect
},
showPublic () {
return this.originalScope !== 'direct' && this.shouldShow('public')
},
showUnlisted () {
return this.originalScope !== 'direct' && this.shouldShow('unlisted')
},
showPrivate () {
return this.originalScope !== 'direct' && this.shouldShow('private')
},
showDirect () {
return this.shouldShow('direct')
},
css () {
return {
public: {selected: this.currentScope === 'public'},
unlisted: {selected: this.currentScope === 'unlisted'},
private: {selected: this.currentScope === 'private'},
direct: {selected: this.currentScope === 'direct'}
}
}
},
methods: {
shouldShow (scope) {
return this.showAll ||
this.currentScope === scope ||
this.originalScope === scope ||
this.userDefault === scope ||
scope === 'direct'
},
changeVis (scope) {
this.currentScope = scope
this.onScopeChange && this.onScopeChange(scope)
}
}
}
export default ScopeSelector

View file

@ -0,0 +1,30 @@
<template>
<div v-if="!showNothing">
<i class="icon-mail-alt"
:class="css.direct"
:title="$t('post_status.scope.direct')"
v-if="showDirect"
@click="changeVis('direct')">
</i>
<i class="icon-lock"
:class="css.private"
:title="$t('post_status.scope.private')"
v-if="showPrivate"
v-on:click="changeVis('private')">
</i>
<i class="icon-lock-open-alt"
:class="css.unlisted"
:title="$t('post_status.scope.unlisted')"
v-if="showUnlisted"
@click="changeVis('unlisted')">
</i>
<i class="icon-globe"
:class="css.public"
:title="$t('post_status.scope.public')"
v-if="showPublic"
@click="changeVis('public')">
</i>
</div>
</template>
<script src="./scope_selector.js"></script>

View file

@ -70,13 +70,18 @@ const settings = {
alwaysShowSubjectInputLocal: typeof user.alwaysShowSubjectInput === 'undefined'
? instance.alwaysShowSubjectInput
: user.alwaysShowSubjectInput,
alwaysShowSubjectInputDefault: instance.alwaysShowSubjectInput,
alwaysShowSubjectInputDefault: this.$t('settings.values.' + instance.alwaysShowSubjectInput),
scopeCopyLocal: typeof user.scopeCopy === 'undefined'
? instance.scopeCopy
: user.scopeCopy,
scopeCopyDefault: this.$t('settings.values.' + instance.scopeCopy),
minimalScopesModeLocal: typeof user.minimalScopesMode === 'undefined'
? instance.minimalScopesMode
: user.minimalScopesMode,
minimalScopesModeDefault: this.$t('settings.values.' + instance.minimalScopesMode),
stopGifs: user.stopGifs,
webPushNotificationsLocal: user.webPushNotifications,
loopVideoSilentOnlyLocal: user.loopVideosSilentOnly,
@ -200,6 +205,9 @@ const settings = {
postContentTypeLocal (value) {
this.$store.dispatch('setOption', { name: 'postContentType', value })
},
minimalScopesModeLocal (value) {
this.$store.dispatch('setOption', { name: 'minimalScopesMode', value })
},
stopGifs (value) {
this.$store.dispatch('setOption', { name: 'stopGifs', value })
},

View file

@ -118,6 +118,12 @@
</label>
</div>
</li>
<li>
<input type="checkbox" id="minimalScopesMode" v-model="minimalScopesModeLocal">
<label for="minimalScopesMode">
{{$t('settings.minimal_scopes_mode')}} {{$t('settings.instance_default', { value: minimalScopesModeDefault })}}
</label>
</li>
</ul>
</div>

View file

@ -21,11 +21,6 @@
{{ $t("login.login") }}
</router-link>
</li>
<li v-if="currentUser" @click="toggleDrawer">
<router-link :to="{ name: 'notifications', params: { username: currentUser.screen_name } }">
{{ $t("notifications.notifications") }} {{ unseenNotificationsCount > 0 ? `(${unseenNotificationsCount})` : '' }}
</router-link>
</li>
<li v-if="currentUser" @click="toggleDrawer">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }}

View file

@ -251,6 +251,12 @@ const Status = {
},
maxThumbnails () {
return this.$store.state.config.maxThumbnails
},
contentHtml () {
if (!this.status.summary_html) {
return this.status.statusnet_html
}
return this.status.summary_html + '<br />' + this.status.statusnet_html
}
},
components: {

View file

@ -98,16 +98,16 @@
</div>
<div class="status-content-wrapper" :class="{ 'tall-status': !showingLongSubject }" v-if="longSubject">
<a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="!showingLongSubject" href="#" @click.prevent="showingLongSubject=true">Show more</a>
<div @click.prevent="linkClicked" class="status-content media-body" v-html="status.statusnet_html"></div>
<a v-if="showingLongSubject" href="#" class="status-unhider" @click.prevent="showingLongSubject=false">Show less</a>
<a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="!showingLongSubject" href="#" @click.prevent="showingLongSubject=true">{{$t("general.show_more")}}</a>
<div @click.prevent="linkClicked" class="status-content media-body" v-html="contentHtml"></div>
<a v-if="showingLongSubject" href="#" class="status-unhider" @click.prevent="showingLongSubject=false">{{$t("general.show_less")}}</a>
</div>
<div :class="{'tall-status': hideTallStatus}" class="status-content-wrapper" v-else>
<a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="hideTallStatus" href="#" @click.prevent="toggleShowMore">Show more</a>
<div @click.prevent="linkClicked" class="status-content media-body" v-html="status.statusnet_html" v-if="!hideSubjectStatus"></div>
<a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="hideTallStatus" href="#" @click.prevent="toggleShowMore">{{$t("general.show_more")}}</a>
<div @click.prevent="linkClicked" class="status-content media-body" v-html="contentHtml" v-if="!hideSubjectStatus"></div>
<div @click.prevent="linkClicked" class="status-content media-body" v-html="status.summary_html" v-else></div>
<a v-if="hideSubjectStatus" href="#" class="cw-status-hider" @click.prevent="toggleShowMore">Show more</a>
<a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">Show less</a>
<a v-if="hideSubjectStatus" href="#" class="cw-status-hider" @click.prevent="toggleShowMore">{{$t("general.show_more")}}</a>
<a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">{{$t("general.show_less")}}</a>
</div>
<div v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)" class="attachments media-body">

View file

@ -4,6 +4,7 @@ import get from 'lodash/get'
import TabSwitcher from '../tab_switcher/tab_switcher.js'
import ImageCropper from '../image_cropper/image_cropper.vue'
import StyleSwitcher from '../style_switcher/style_switcher.vue'
import ScopeSelector from '../scope_selector/scope_selector.vue'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
import BlockCard from '../block_card/block_card.vue'
import MuteCard from '../mute_card/mute_card.vue'
@ -67,6 +68,7 @@ const UserSettings = {
},
components: {
StyleSwitcher,
ScopeSelector,
TabSwitcher,
ImageCropper,
BlockList,
@ -80,8 +82,8 @@ const UserSettings = {
pleromaBackend () {
return this.$store.state.instance.pleromaBackend
},
scopeOptionsEnabled () {
return this.$store.state.instance.scopeOptionsEnabled
minimalScopesMode () {
return this.$store.state.instance.minimalScopesMode
},
vis () {
return {

View file

@ -38,13 +38,13 @@
<input type="checkbox" v-model="newLocked" id="account-locked">
<label for="account-locked">{{$t('settings.lock_account_description')}}</label>
</p>
<div v-if="scopeOptionsEnabled">
<div>
<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" :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>
<scope-selector
:showAll="true"
:userDefault="newDefaultScope"
:onScopeChange="changeVis"/>
</div>
</div>
<p>