Commit 339c115a authored by Wim Vanderbauwhede's avatar Wim Vanderbauwhede

Merge remote-tracking branch 'upstream/develop' into develop

parents 892af831 db6ff482
Pipeline #3007 failed with stages
in 59 seconds
......@@ -29,4 +29,6 @@ npm run build
npm run unit
```
For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
# Configuration
Edit config.json for configuration. scopeOptionsEnabled gives you input fields for CWs and the scope settings.
unset http_proxy
CC=gcc CXX=g++ PYTHON=/opt/local/bin/python2.7 yarn
npm run build
......@@ -23,12 +23,12 @@ module.exports = {
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'htts://localhost:4000/',
target: 'http://localhost:4000/',
changeOrigin: true,
cookieDomainRewrite: 'localhost'
},
'/socket': {
target: 'htts://localhost:4000/',
target: 'http://localhost:4000/',
changeOrigin: true,
cookieDomainRewrite: 'localhost',
ws: true
......
......@@ -169,6 +169,13 @@ input, textarea, .select {
}
}
option {
color: $fallback--fg;
color: var(--fg, $fallback--fg);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
}
i[class*=icon-] {
color: $fallback--icon;
color: var(--icon, $fallback--icon)
......
import UserCard from '../user_card/user_card.vue'
const FollowRequests = {
components: {
UserCard
},
created () {
this.updateRequests()
},
computed: {
requests () {
return this.$store.state.api.followRequests
}
},
methods: {
updateRequests () {
this.$store.state.api.backendInteractor.fetchFollowRequests()
.then((requests) => { this.$store.commit('setFollowRequests', requests) })
}
}
}
export default FollowRequests
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
{{$t('nav.friend_requests')}}
</div>
<div class="panel-body">
<user-card v-for="request in requests" :key="request.id" :user="request" :showFollows="false" :showApproval="true"></user-card>
</div>
</div>
</template>
<script src="./follow_requests.js"></script>
......@@ -8,7 +8,7 @@
<form v-on:submit.prevent='submit(user)' class='login-form'>
<div class='form-group'>
<label for='username'>{{$t('login.username')}}</label>
<input :disabled="loggingIn" v-model='user.username' class='form-control' id='username' placeholder=''>
<input :disabled="loggingIn" v-model='user.username' class='form-control' id='username' v-bind:placeholder="$t('login.placeholder')">
</div>
<div class='form-group'>
<label for='password'>{{$t('login.password')}}</label>
......
......@@ -12,6 +12,11 @@
{{ $t("nav.mentions") }}
</router-link>
</li>
<li v-if='currentUser && currentUser.locked'>
<router-link to='/friend-requests'>
{{ $t("nav.friend_requests") }}
</router-link>
</li>
<li>
<router-link to='/main/public'>
{{ $t("nav.public_tl") }}
......
......@@ -10,7 +10,8 @@
</div>
<span class="notification-details">
<div class="name-and-action">
<span class="username" :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</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>
<span class="username" v-else :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
<span v-if="notification.type === 'favorite'">
<i class="fa icon-star lit"></i>
<small>{{$t('notifications.favorited_you')}}</small>
......
......@@ -23,13 +23,18 @@ const PostStatusForm = {
props: [
'replyTo',
'repliedUser',
'attentions'
'attentions',
'messageScope'
],
components: {
MediaUpload
},
mounted () {
this.resize(this.$refs.textarea)
if (this.replyTo) {
this.$refs.textarea.focus()
}
},
data () {
const preset = this.$route.query.message
......@@ -48,12 +53,21 @@ const PostStatusForm = {
highlighted: 0,
newStatus: {
status: statusText,
files: []
files: [],
visibility: this.messageScope || 'public'
},
caret: 0
}
},
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 === '@') {
......@@ -77,11 +91,11 @@ const PostStatusForm = {
return false
}
return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}, index) => ({
// eslint-disable-next-line camelcase
screen_name: `:${shortcode}:`,
name: '',
utf: utf || '',
img: image_url,
// eslint-disable-next-line camelcase
img: utf ? '' : this.$store.state.config.server + image_url,
highlighted: index === this.highlighted
}))
} else {
......@@ -118,6 +132,9 @@ const PostStatusForm = {
},
isOverLengthLimit () {
return this.hasStatusLengthLimit && (this.statusLength > this.statusLengthLimit)
},
scopeOptionsEnabled () {
return this.$store.state.config.scopeOptionsEnabled
}
},
methods: {
......@@ -185,6 +202,8 @@ const PostStatusForm = {
this.posting = true
statusPoster.postStatus({
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
visibility: newStatus.visibility,
media: newStatus.files,
store: this.$store,
inReplyToStatusId: this.replyTo
......@@ -192,7 +211,8 @@ const PostStatusForm = {
if (!data.error) {
this.newStatus = {
status: '',
files: []
files: [],
visibility: newStatus.visibility
}
this.$emit('posted')
let el = this.$el.querySelector('textarea')
......@@ -239,6 +259,7 @@ const PostStatusForm = {
e.dataTransfer.dropEffect = 'copy'
},
resize (e) {
if (!e.target) { return }
const vertPadding = Number(window.getComputedStyle(e.target)['padding-top'].substr(0, 1)) +
Number(window.getComputedStyle(e.target)['padding-bottom'].substr(0, 1))
e.target.style.height = 'auto'
......@@ -249,6 +270,9 @@ const PostStatusForm = {
},
clearError () {
this.error = null
},
changeVis (visibility) {
this.newStatus.visibility = visibility
}
}
}
......
......@@ -2,6 +2,20 @@
<div class="post-status-form">
<form @submit.prevent="postStatus(newStatus)">
<div class="form-group" >
<i18n
v-if="!this.$store.state.users.currentUser.locked && this.newStatus.visibility == 'private'"
path="post_status.account_not_locked_warning"
tag="p"
class="visibility-notice">
<router-link to="/user-settings">{{ $t('post_status.account_not_locked_warning_link') }}</router-link>
</i18n>
<p v-if="this.newStatus.visibility == 'direct'" class="visibility-notice">{{ $t('post_status.direct_warning') }}</p>
<input
v-if="scopeOptionsEnabled"
type="text"
:placeholder="$t('post_status.content_warning')"
v-model="newStatus.spoilerText"
class="form-cw">
<textarea
ref="textarea"
@click="setCaret"
......@@ -18,6 +32,12 @@
@input="resize"
@paste="paste">
</textarea>
<div v-if="scopeOptionsEnabled" 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>
</div>
</div>
<div style="position:relative;" v-if="candidates">
<div class="autocomplete-panel">
......@@ -79,6 +99,25 @@
}
}
.post-status-form .visibility-tray {
font-size: 1.2em;
padding: 3px;
cursor: pointer;
.selected {
color: $fallback--lightFg;
color: var(--lightFg, $fallback--lightFg);
}
}
.visibility-notice {
padding: .5em;
border: 1px solid $fallback--faint;
border: 1px solid var(--faint, $fallback--faint);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
}
.post-status-form, .login {
.form-bottom {
display: flex;
......@@ -143,7 +182,15 @@
line-height:24px;
}
form textarea {
form textarea.form-cw {
line-height:16px;
resize: none;
overflow: hidden;
transition: min-height 200ms 100ms;
min-height: 1px;
}
form textarea.form-control {
line-height:16px;
resize: none;
overflow: hidden;
......@@ -152,7 +199,7 @@
box-sizing: content-box;
}
form textarea:focus {
form textarea.form-control:focus {
min-height: 48px;
}
......
const RetweetButton = {
props: ['status', 'loggedIn'],
props: ['status', 'loggedIn', 'visibility'],
data () {
return {
animated: false
......@@ -9,6 +9,8 @@ const RetweetButton = {
retweet () {
if (!this.status.repeated) {
this.$store.dispatch('retweet', {id: this.status.id})
} else {
this.$store.dispatch('unretweet', {id: this.status.id})
}
this.animated = true
setTimeout(() => {
......@@ -20,6 +22,7 @@ const RetweetButton = {
classes () {
return {
'retweeted': this.status.repeated,
'retweeted-empty': !this.status.repeated,
'animate-spin': this.animated
}
}
......
<template>
<div v-if="loggedIn">
<div v-if="loggedIn && visibility !== 'private' && visibility !== 'direct'">
<i :class='classes' class='icon-retweet rt-active' v-on:click.prevent='retweet()'></i>
<span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
</div>
<div v-else>
<div v-else-if="!loggedIn">
<i :class='classes' class='icon-retweet'></i>
<span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
</div>
......
......@@ -57,7 +57,10 @@
@import '../../_variables.scss';
.setting-item {
border-bottom: 2px solid var(--btn, $fallback--btn);
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
textarea {
width: 100%;
......
......@@ -40,6 +40,7 @@ const Status = {
},
retweet () { return !!this.statusoid.retweeted_status },
retweeter () { return this.statusoid.user.name },
retweeterHtml () { return this.statusoid.user.name_html },
status () {
if (this.retweet) {
return this.statusoid.retweeted_status
......@@ -104,16 +105,16 @@ const Status = {
StillImage
},
methods: {
visibilityIcon(visibility) {
switch(visibility) {
case "private":
return "icon-lock"
case "unlisted":
return "icon-lock-open-alt"
case "direct":
return "icon-mail-alt"
default:
return "icon-globe"
visibilityIcon (visibility) {
switch (visibility) {
case 'private':
return 'icon-lock'
case 'unlisted':
return 'icon-lock-open-alt'
case 'direct':
return 'icon-mail-alt'
default:
return 'icon-globe'
}
},
linkClicked ({target}) {
......
......@@ -11,7 +11,8 @@
<div v-if="retweet && !noHeading" class="media container retweet-info">
<StillImage v-if="retweet" class='avatar' :src="statusoid.user.profile_image_url_original"/>
<div class="media-body faint">
<a :href="statusoid.user.statusnet_profile_url" style="font-weight: bold;" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a>
<a v-if="retweeterHtml" :href="statusoid.user.statusnet_profile_url" style="font-weight: bold;" :title="'@'+statusoid.user.screen_name" v-html="retweeterHtml"></a>
<a v-else :href="statusoid.user.statusnet_profile_url" style="font-weight: bold;" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a>
<i class='fa icon-retweet retweeted'></i>
{{$t('timeline.repeated')}}
</div>
......@@ -30,7 +31,8 @@
<div v-if="!noHeading" class="media-body container media-heading">
<div class="media-heading-left">
<div class="name-and-links">
<h4 class="user-name">{{status.user.name}}</h4>
<h4 class="user-name" v-if="status.user.name_html" v-html="status.user.name_html"></h4>
<h4 class="user-name" v-else>{{status.user.name}}</h4>
<span class="links">
<router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link>
<span v-if="status.in_reply_to_screen_name" class="faint reply-info">
......@@ -88,7 +90,7 @@
<i class="icon-reply" :class="{'icon-reply-active': replying}"></i>
</a>
</div>
<retweet-button :loggedIn='loggedIn' :status='status'></retweet-button>
<retweet-button :visibility='status.visibility' :loggedIn='loggedIn' :status='status'></retweet-button>
<favorite-button :loggedIn='loggedIn' :status='status'></favorite-button>
<delete-button :status='status'></delete-button>
</div>
......@@ -96,7 +98,7 @@
</div>
<div class="container" v-if="replying">
<div class="reply-left"/>
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying"/>
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" :message-scope="status.visibility" v-on:posted="toggleReplying"/>
</div>
</template>
</div>
......
......@@ -3,7 +3,10 @@
<div>{{$t('settings.presets')}}
<label for="style-switcher" class='select'>
<select id="style-switcher" v-model="selected" class="style-switcher">
<option v-for="style in availableStyles" :value="style">{{style[0]}}</option>
<option v-for="style in availableStyles" :value="style" :style="{
backgroundColor: style[1],
color: style[3]
}">{{style[0]}}</option>
</select>
<i class="icon-down-open"/>
</label>
......
......@@ -13,7 +13,8 @@ const Timeline = {
],
data () {
return {
paused: false
paused: false,
unfocused: false
}
},
computed: {
......@@ -65,8 +66,15 @@ const Timeline = {
this.fetchFollowers()
}
},
mounted () {
if (typeof document.hidden !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
this.unfocused = document.hidden
}
},
destroyed () {
window.removeEventListener('scroll', this.scrollLoad)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
},
methods: {
......@@ -113,6 +121,9 @@ const Timeline = {
(window.innerHeight + window.pageYOffset) >= (height - 750)) {
this.fetchOlderStatuses()
}
},
handleVisibilityChange () {
this.unfocused = document.hidden
}
},
watch: {
......@@ -122,7 +133,7 @@ const Timeline = {
}
if (count > 0) {
// only 'stream' them when you're scrolled to the top
if (window.pageYOffset < 15 && !this.paused) {
if (window.pageYOffset < 15 && !this.paused && !this.unfocused) {
this.showNewStatuses()
} else {
this.paused = true
......
......@@ -3,7 +3,8 @@ import UserCardContent from '../user_card_content/user_card_content.vue'
const UserCard = {
props: [
'user',
'showFollows'
'showFollows',
'showApproval'
],
data () {
return {
......@@ -16,6 +17,14 @@ const UserCard = {
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
approveUser () {
this.$store.state.api.backendInteractor.approveUser(this.user.id)
this.$store.dispatch('removeFollowRequest', this.user)
},
denyUser () {
this.$store.state.api.backendInteractor.denyUser(this.user.id)
this.$store.dispatch('removeFollowRequest', this.user)
}
}
}
......
......@@ -7,14 +7,24 @@
<user-card-content :user="user" :switcher="false"></user-card-content>
</div>
<div class="name-and-screen-name" v-else>
<div :title="user.name" class="user-name">
<div :title="user.name" v-if="user.name_html" class="user-name">
<span v-html="user.name_html"></span>
<span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you">
{{ $t('user_card.follows_you') }}
</span>
</div>
<div :title="user.name" v-else class="user-name">
{{ user.name }}
<span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you">
{{ $t('user_card.follows_you') }}
{{ $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>
</div>
<div class="approval" v-if="showApproval">
<button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
<button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
</div>
</div>
</template>
......@@ -75,4 +85,11 @@
margin-bottom: 0;
}
}
.approval {
button {
width: 100%;
margin-bottom: 0.5em;
}
}
</style>
......@@ -13,9 +13,10 @@
<StillImage class="avatar" :src="user.profile_image_url_original"/>
</router-link>
<div class="name-and-screen-name">
<div :title="user.name" class='user-name'>{{user.name}}</div>
<div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div>
<div :title="user.name" class='user-name' v-else>{{user.name}}</div>
<router-link class='user-screen-name':to="{ name: 'user-profile', params: { id: user.id } }">
<span>@{{user.screen_name}}</span>
<span>@{{user.screen_name}}</span><span v-if="user.locked"><i class="icon icon-lock"></i></span>
<span class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span>
</router-link>
</div>
......@@ -88,7 +89,8 @@
<span>{{user.followers_count}}</span>
</div>
</div>
<p v-if="!hideBio">{{user.description}}</p>
<p v-if="!hideBio && user.description_html" v-html="user.description_html"></p>
<p v-else-if="!hideBio">{{ user.description }}</p>
</div>
</div>
</template>
......
......@@ -5,6 +5,7 @@ const UserSettings = {
return {
newname: this.$store.state.users.currentUser.name,
newbio: this.$store.state.users.currentUser.description,
newlocked: this.$store.state.users.currentUser.locked,
followList: null,
followImportError: false,
followsImported: false,
......@@ -13,7 +14,10 @@ const UserSettings = {
previews: [ null, null, null ],
deletingAccount: false,
deleteAccountConfirmPasswordInput: '',
deleteAccountError: false
deleteAccountError: false,
changePasswordInputs: [ '', '', '' ],
changedPassword: false,
changePasswordError: false
}
},
components: {
......@@ -31,7 +35,8 @@ const UserSettings = {
updateProfile () {
const name = this.newname
const description = this.newbio
this.$store.state.api.backendInteractor.updateProfile({params: {name, description}}).then((user) => {
const locked = this.newlocked
this.$store.state.api.backendInteractor.updateProfile({params: {name, description, locked}}).then((user) => {