Merge branch 'develop' into feature/normal-emoji-completion

This commit is contained in:
eal 2017-12-23 19:52:16 +02:00
commit b67c506062
36 changed files with 457 additions and 196 deletions

View file

@ -34,6 +34,13 @@
display: flex;
flex-wrap: wrap;
margin-right: -0.7em;
.attachment.media-upload-container {
flex: 0 0 auto;
max-height: 300px;
max-width: 100%;
}
.attachment {
flex: 1 0 30%;
margin: 0.5em 0.7em 0.6em 0.0em;
@ -82,9 +89,7 @@
img.media-upload {
margin-bottom: -2px;
max-height: 300px;
width: 100%;
height: 100%;
flex: 1;
max-width: 100%;
}
.oembed {
@ -126,6 +131,7 @@
width: 100%;
height: 100%; /* If this isn't here, chrome will stretch the images */
max-height: 500px;
image-orientation: from-image;
}
}
}

View file

@ -0,0 +1,21 @@
const chat = {
data () {
return {
currentMessage: '',
channel: null
}
},
computed: {
messages () {
return this.$store.state.chat.messages
}
},
methods: {
submit (message) {
this.$store.state.chat.channel.push('new_msg', {text: message}, 10000)
this.currentMessage = ''
}
}
}
export default chat

View file

@ -0,0 +1,59 @@
<template>
<div class="chat-panel panel panel-default">
<div class="panel-heading timeline-heading base02-background base04">
<div class="title">
{{$t('chat.title')}}
</div>
</div>
<div class="panel-body base01-background">
<div class="chat-window">
<div class="chat-message" v-for="message in messages" :key="message.id">
<span class="chat-avatar">
<img :src="message.author.avatar" />
{{message.author.username}}:
</span>
<span class="chat-text">
{{message.text}}
</span>
</div>
</div>
<div class="chat-input">
<form @submit.prevent="submit(currentMessage)">
<input v-model="currentMessage" type="text" >
</form>
</div>
</div>
</div>
</template>
<script src="./chat.js"></script>
<style lang="scss">
.chat-window {
max-height: 80vh;
overflow-y: auto;
overflow-x: hidden;
}
.chat-message {
padding: 0.2em 0.5em
}
.chat-avatar {
img {
height: 32px;
width: 32px;
border-radius: 5px;
margin-right: 0.5em;
}
}
.chat-input {
display: flex;
form {
flex: auto;
input {
margin: 0.5em;
width: fill-available;
}
}
}
</style>

View file

@ -1,7 +1,7 @@
<template>
<div v-if="canDelete">
<a href="#" v-on:click.prevent="deleteStatus()">
<i class='fa icon-cancel delete-status'></i>
<i class='base09 icon-cancel delete-status'></i>
</a>
</div>
</template>

View file

@ -1,6 +1,6 @@
<template>
<div>
<i :class='classes' class='favorite-button fa' @click.prevent='favorite()'/>
<i :class='classes' class='favorite-button base09' @click.prevent='favorite()'/>
<span v-if='status.fave_num > 0'>{{status.fave_num}}</span>
</div>
</template>
@ -15,7 +15,7 @@
color: orange;
}
}
.icon-star {
.favorite-button.icon-star {
color: orange;
}

View file

@ -1,8 +1,8 @@
<template>
<div class="media-upload" @drop.prevent @dragover.prevent="fileDrag" @drop="fileDrop">
<label class="btn btn-default">
<i class="fa icon-spin4 animate-spin" v-if="uploading"></i>
<i class="fa icon-upload" v-if="!uploading"></i>
<i class="base09 icon-spin4 animate-spin" v-if="uploading"></i>
<i class="base09 icon-upload" v-if="!uploading"></i>
<input type=file style="position: fixed; top: -100em"></input>
</label>
</div>

View file

@ -2,6 +2,9 @@ const NavPanel = {
computed: {
currentUser () {
return this.$store.state.users.currentUser
},
chat () {
return this.$store.state.chat.channel
}
}
}

View file

@ -7,6 +7,11 @@
{{ $t("nav.timeline") }}
</router-link>
</li>
<li v-if='chat && currentUser'>
<router-link class="base00-background" to='/chat'>
{{ $t("nav.chat") }}
</router-link>
</li>
<li v-if='currentUser'>
<router-link class="base00-background" :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
{{ $t("nav.mentions") }}

View file

@ -59,6 +59,10 @@
color: $blue;
}
.icon-star.lit {
color: orange;
}
.status-content {
margin: 0;
max-height: 300px;

View file

@ -4,7 +4,7 @@
<div class="panel-heading base02-background base04">
<span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span>
{{$t('notifications.notifications')}}
<button @click.prevent="markAsSeen" class="base04 base02-background read-button">{{$t('notifications.read')}}</button>
<button v-if="unseenCount" @click.prevent="markAsSeen" class="base04 base02-background read-button">{{$t('notifications.read')}}</button>
</div>
<div class="panel-body base03-border">
<div v-for="notification in visibleNotifications" :key="notification" class="notification" :class='{"unseen": !notification.seen}'>
@ -17,7 +17,7 @@
<div v-if="notification.type === 'favorite'">
<h1>
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
<i class="fa icon-star"></i>
<i class="fa icon-star lit"></i>
<small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
</h1>
<div class="notification-gradient" :style="hiderStyle"></div>

View file

@ -157,6 +157,14 @@ const PostStatusForm = {
type (fileInfo) {
return fileTypeService.fileType(fileInfo.mimetype)
},
paste (e) {
if (e.clipboardData.files.length > 0) {
// Strangely, files property gets emptied after event propagation
// Trying to wrap it in array doesn't work. Plus I doubt it's possible
// to hold more than one file in clipboard.
this.dropFiles = [e.clipboardData.files[0]]
}
},
fileDrop (e) {
if (e.dataTransfer.files.length > 0) {
e.preventDefault() // allow dropping text like before

View file

@ -2,7 +2,7 @@
<div class="post-status-form">
<form @submit.prevent="postStatus(newStatus)">
<div class="form-group base03-border" >
<textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize"></textarea>
<textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize" @paste="paste"></textarea>
</div>
<div style="position:relative;" v-if="candidates">
<div class="autocomplete-panel base05-background">
@ -26,7 +26,7 @@
<i class="icon-cancel" @click="clearError"></i>
</div>
<div class="attachments">
<div class="attachment base03-border" v-for="file in newStatus.files">
<div class="media-upload-container attachment base03-border" v-for="file in newStatus.files">
<i class="fa icon-cancel" @click="removeMediaFile(file)"></i>
<img class="thumbnail media-upload" :src="file.image" v-if="type(file) === 'image'"></img>
<video v-if="type(file) === 'video'" :src="file.image" controls></video>
@ -41,6 +41,7 @@
<script src="./post_status_form.js"></script>
<style lang="scss">
.tribute-container {
ul {
padding: 0px;

View file

@ -1,6 +1,6 @@
<template>
<div>
<i :class='classes' class='icon-retweet fa' v-on:click.prevent='retweet()'></i>
<i :class='classes' class='icon-retweet base09' v-on:click.prevent='retweet()'></i>
<span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
</div>
</template>
@ -16,7 +16,7 @@
color: $green;
}
}
.retweeted {
.icon-retweet.retweeted {
color: $green;
}
</style>

View file

@ -5,7 +5,7 @@
<div class='status-actions'>
<div>
<a href="#" v-on:click.prevent="toggleReplying">
<i class="fa icon-reply" :class="{'icon-reply-active': replying}"></i>
<i class="base09 icon-reply" :class="{'icon-reply-active': replying}"></i>
</a>
</div>
<retweet-button :status=status></retweet-button>
@ -19,7 +19,7 @@
<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>
<small class="muteWords">{{muteWordHits.join(', ')}}</small>
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="fa icon-eye-off"></i></a>
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="base09 icon-eye-off"></i></a>
</div>
</template>
<template v-if="!muted">
@ -75,10 +75,10 @@
</h4>
</div>
<div class="heading-icons">
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="fa icon-eye-off"></i></a>
<a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="fa icon-binoculars"></i></a>
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="base09 icon-eye-off"></i></a>
<a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="base09 icon-binoculars"></i></a>
<template v-if="expandable">
<a href="#" @click.prevent="toggleExpanded" class="expand"><i class="fa icon-plus-squared"></i></a>
<a href="#" @click.prevent="toggleExpanded" class="expand"><i class="base09 icon-plus-squared"></i></a>
</template>
</div>
</div>
@ -94,7 +94,7 @@
</div>
</div>
<div class="status-preview status-preview-loading base00-background base03-border" v-else-if="showPreview">
<i class="fa icon-spin4 animate-spin"></i>
<i class="base09 icon-spin4 animate-spin"></i>
</div>
<div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div>
@ -109,7 +109,7 @@
<div class='status-actions'>
<div>
<a href="#" v-on:click.prevent="toggleReplying">
<i class="fa icon-reply" :class="{'icon-reply-active': replying}"></i>
<i class="base09 icon-reply" :class="{'icon-reply-active': replying}"></i>
</a>
</div>
<retweet-button :status=status></retweet-button>
@ -324,7 +324,7 @@
color: $blue;
}
.icon-reply-active {
.icon-reply.icon-reply-active {
color: $blue;
}

View file

@ -1,3 +1,5 @@
import { rgbstr2hex } from '../../services/color_convert/color_convert.js'
export default {
data () {
return {
@ -19,13 +21,6 @@ export default {
})
},
mounted () {
const rgbstr2hex = (rgb) => {
if (rgb[0] === '#') {
return rgb
}
rgb = rgb.match(/\d+/g)
return `#${((Number(rgb[0]) << 16) + (Number(rgb[1]) << 8) + Number(rgb[2])).toString(16)}`
}
this.bgColorLocal = rgbstr2hex(this.$store.state.config.colors['base00'])
this.fgColorLocal = rgbstr2hex(this.$store.state.config.colors['base02'])
this.textColorLocal = rgbstr2hex(this.$store.state.config.colors['base05'])

View file

@ -29,6 +29,13 @@ const Timeline = {
},
newStatusCount () {
return this.timeline.newStatusCount
},
newStatusCountStr () {
if (this.timeline.flushMarker !== 0) {
return ''
} else {
return ` (${this.newStatusCount})`
}
}
},
components: {
@ -64,8 +71,14 @@ const Timeline = {
},
methods: {
showNewStatuses () {
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
this.paused = false
if (this.timeline.flushMarker !== 0) {
this.$store.commit('clearTimeline', { timeline: this.timelineName })
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
this.fetchOlderStatuses()
} else {
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
this.paused = false
}
},
fetchOlderStatuses () {
const store = this.$store

View file

@ -5,7 +5,7 @@
{{title}}
</div>
<button @click.prevent="showNewStatuses" class="base05 base02-background loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError">
{{$t('timeline.show_new')}} ({{timeline.newStatusCount}})
{{$t('timeline.show_new')}}{{newStatusCountStr}}
</button>
<div @click.prevent class="base06 error loadmore-text" v-if="timelineError">
{{$t('timeline.error_fetching')}}

View file

@ -59,13 +59,16 @@
}
.usercard {
width: -webkit-fill-available;
width: -moz-webkit-fill-available;
stretch: fill;
width: fill-available;
margin: 0.2em 0 0.7em 0;
border-radius: 5px;
border-radius: 10px;
border-style: solid;
border-color: inherit;
border-width: 1px;
overflow: hidden;
p {
margin-bottom: 0;
}
}
</style>

View file

@ -0,0 +1,64 @@
import { hex2rgb } from '../../services/color_convert/color_convert.js'
export default {
props: [ 'user', 'switcher' ],
computed: {
headingStyle () {
const color = this.$store.state.config.colors['base00']
if (color) {
const rgb = hex2rgb(color)
console.log(rgb)
return {
backgroundColor: `rgb(${Math.floor(rgb[0] * 0.53)}, ${Math.floor(rgb[1] * 0.56)}, ${Math.floor(rgb[2] * 0.59)})`,
backgroundImage: `url(${this.user.cover_photo})`
}
}
},
bodyStyle () {
return {
background: `linear-gradient(to bottom, rgba(0, 0, 0, 0), ${this.$store.state.config.colors['base00']} 80%)`
}
},
isOtherUser () {
return this.user.id !== this.$store.state.users.currentUser.id
},
loggedIn () {
return this.$store.state.users.currentUser
},
dailyAvg () {
const days = Math.ceil((new Date() - new Date(this.user.created_at)) / (60 * 60 * 24 * 1000))
return Math.round(this.user.statuses_count / days)
}
},
methods: {
followUser () {
const store = this.$store
store.state.api.backendInteractor.followUser(this.user.id)
.then((followedUser) => store.commit('addNewUsers', [followedUser]))
},
unfollowUser () {
const store = this.$store
store.state.api.backendInteractor.unfollowUser(this.user.id)
.then((unfollowedUser) => store.commit('addNewUsers', [unfollowedUser]))
},
blockUser () {
const store = this.$store
store.state.api.backendInteractor.blockUser(this.user.id)
.then((blockedUser) => store.commit('addNewUsers', [blockedUser]))
},
unblockUser () {
const store = this.$store
store.state.api.backendInteractor.unblockUser(this.user.id)
.then((unblockedUser) => store.commit('addNewUsers', [unblockedUser]))
},
toggleMute () {
const store = this.$store
store.commit('setMuted', {user: this.user, muted: !this.user.muted})
store.state.api.backendInteractor.setUserMute(this.user)
},
setProfileView (v) {
const store = this.$store
store.commit('setProfileView', { v })
}
}
}

View file

@ -84,69 +84,7 @@
</div>
</template>
<script>
export default {
props: [ 'user', 'switcher' ],
computed: {
headingStyle () {
let color = this.$store.state.config.colors['base00']
if (color) {
let rgb = this.$store.state.config.colors['base00'].match(/\d+/g)
return {
backgroundColor: `rgb(${Math.floor(rgb[0] * 0.53)}, ${Math.floor(rgb[1] * 0.56)}, ${Math.floor(rgb[2] * 0.59)})`,
backgroundImage: `url(${this.user.cover_photo})`
}
}
},
bodyStyle () {
return {
background: `linear-gradient(to bottom, rgba(0, 0, 0, 0), ${this.$store.state.config.colors['base00']} 80%)`
}
},
isOtherUser () {
return this.user.id !== this.$store.state.users.currentUser.id
},
loggedIn () {
return this.$store.state.users.currentUser
},
dailyAvg () {
const days = Math.ceil((new Date() - new Date(this.user.created_at)) / (60 * 60 * 24 * 1000))
return Math.round(this.user.statuses_count / days)
}
},
methods: {
followUser () {
const store = this.$store
store.state.api.backendInteractor.followUser(this.user.id)
.then((followedUser) => store.commit('addNewUsers', [followedUser]))
},
unfollowUser () {
const store = this.$store
store.state.api.backendInteractor.unfollowUser(this.user.id)
.then((unfollowedUser) => store.commit('addNewUsers', [unfollowedUser]))
},
blockUser () {
const store = this.$store
store.state.api.backendInteractor.blockUser(this.user.id)
.then((blockedUser) => store.commit('addNewUsers', [blockedUser]))
},
unblockUser () {
const store = this.$store
store.state.api.backendInteractor.unblockUser(this.user.id)
.then((unblockedUser) => store.commit('addNewUsers', [unblockedUser]))
},
toggleMute () {
const store = this.$store
store.commit('setMuted', {user: this.user, muted: !this.user.muted})
store.state.api.backendInteractor.setUserMute(this.user)
},
setProfileView (v) {
const store = this.$store
store.commit('setProfileView', { v })
}
}
}
</script>
<script src="./user_card_content.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@ -164,7 +102,6 @@
.profile-panel-body {
top: -0em;
padding-top: 4em;
word-wrap: break-word;
}

View file

@ -22,7 +22,7 @@
<div>
<input type="file" @change="uploadFile(0, $event)" ></input>
</div>
<i class="fa icon-spin4 animate-spin" v-if="uploading[0]"></i>
<i class="base09 icon-spin4 animate-spin" v-if="uploading[0]"></i>
<button class="btn btn-default base05 base02-background" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button>
</div>
<div class="setting-item">
@ -35,7 +35,7 @@
<div>
<input type="file" @change="uploadFile(1, $event)" ></input>
</div>
<i class="fa icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
<i class="base09 icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
<button class="btn btn-default base05 base02-background" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button>
</div>
<div class="setting-item">
@ -46,7 +46,7 @@
<div>
<input type="file" @change="uploadFile(2, $event)" ></input>
</div>
<i class="fa icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
<i class="base09 icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
<button class="btn btn-default base05 base02-background" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button>
</div>
</div>