Merge remote-tracking branch 'upstream/develop' into notifications
* upstream/develop: (26 commits)
Update status.vue
Update retweet_button.js
Update retweet_button.vue
Use serverside html rendering in usernames and bios if available.
Update status.vue
Revert "Merge branch 'feature/hide-all-status-actions-if-not-logged-in' into 'develop'"
Hide all status actions if not logged in
hopefully, fix linter
Fixes broken custom emoji in autocomplete when proxying to remote BE
Made it so that unfocused tab doesn't autostream posts when scrolled to the top
Remove trailing whitespace
Textarea is now focused when replying
the missing piece for invites system
Fixes selects having unreadable text on some browsers/OSes. Added bonus: theme switcher select now has styled options that show preview of what theme's bg/fg colors are
fixed lint
cleanup, fixed self-highlighting in notifications, fixed incorrect hex code handling
added ability to pick the style of highlighting
post-rebase fix, backported d7d787b84c
notifs fix
maybe i should actually add myself to contributors list?
...
This commit is contained in:
commit
6454837ea4
36 changed files with 334 additions and 86 deletions
|
@ -168,6 +168,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)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Status from '../status/status.vue'
|
||||
import StillImage from '../still-image/still-image.vue'
|
||||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
||||
|
||||
const Notification = {
|
||||
data () {
|
||||
|
@ -18,6 +19,16 @@ const Notification = {
|
|||
toggleUserExpanded () {
|
||||
this.userExpanded = !this.userExpanded
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userClass () {
|
||||
return highlightClass(this.notification.action.user)
|
||||
},
|
||||
userStyle () {
|
||||
const highlight = this.$store.state.config.highlight
|
||||
const user = this.notification.action.user
|
||||
return highlightStyle(highlight[user.screen_name])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<status v-if="notification.type === 'mention'" :compact="true" :statusoid="notification.status"></status>
|
||||
<div class="non-mention" v-else>
|
||||
<div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]"v-else>
|
||||
<a class='avatar-container' :href="notification.action.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded">
|
||||
<StillImage class='avatar-compact' :src="notification.action.user.profile_image_url_original"/>
|
||||
</a>
|
||||
|
@ -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 === 'like'">
|
||||
<i class="fa icon-star lit"></i>
|
||||
<small>{{$t('notifications.favorited_you')}}</small>
|
||||
|
|
|
@ -45,8 +45,7 @@
|
|||
}
|
||||
|
||||
.unseen {
|
||||
border-left: 4px solid $fallback--cRed;
|
||||
border-left: 4px solid var(--cRed, $fallback--cRed);
|
||||
box-shadow: inset 4px 0 0 var(--cRed, $fallback--cRed);
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +55,6 @@
|
|||
display: flex;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: inherit;
|
||||
padding-left: 4px;
|
||||
|
||||
.broken-favorite {
|
||||
border-radius: $fallback--tooltipRadius;
|
||||
|
|
|
@ -31,6 +31,10 @@ const PostStatusForm = {
|
|||
},
|
||||
mounted () {
|
||||
this.resize(this.$refs.textarea)
|
||||
|
||||
if (this.replyTo) {
|
||||
this.$refs.textarea.focus()
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const preset = this.$route.query.message
|
||||
|
@ -87,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 {
|
||||
|
|
|
@ -5,17 +5,23 @@ const registration = {
|
|||
registering: false
|
||||
}),
|
||||
created () {
|
||||
if (!this.$store.state.config.registrationOpen || !!this.$store.state.users.currentUser) {
|
||||
if ((!this.$store.state.config.registrationOpen && !this.token) || !!this.$store.state.users.currentUser) {
|
||||
this.$router.push('/main/all')
|
||||
}
|
||||
// Seems like this doesn't work at first page open for some reason
|
||||
if (this.$store.state.config.registrationOpen && this.token) {
|
||||
this.$router.push('/registration')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
termsofservice () { return this.$store.state.config.tos }
|
||||
termsofservice () { return this.$store.state.config.tos },
|
||||
token () { return this.$route.params.token }
|
||||
},
|
||||
methods: {
|
||||
submit () {
|
||||
this.registering = true
|
||||
this.user.nickname = this.user.username
|
||||
this.user.token = this.token
|
||||
this.$store.state.api.backendInteractor.register(this.user).then(
|
||||
(response) => {
|
||||
if (response.ok) {
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
<input :disabled="registering" v-model='user.captcha' placeholder='Enter captcha' type='test' class='form-control' id='captcha'>
|
||||
</div>
|
||||
-->
|
||||
<div class='form-group' v-if='token' >
|
||||
<label for='token'>{{$t('registration.token')}}</label>
|
||||
<input disabled='true' v-model='token' class='form-control' id='token' type='text'>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<button :disabled="registering" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const RetweetButton = {
|
||||
props: ['status', 'loggedIn'],
|
||||
props: ['status', 'loggedIn', 'visibility'],
|
||||
data () {
|
||||
return {
|
||||
animated: false
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<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>
|
||||
|
|
|
@ -6,6 +6,7 @@ import PostStatusForm from '../post_status_form/post_status_form.vue'
|
|||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
import StillImage from '../still-image/still-image.vue'
|
||||
import { filter, find } from 'lodash'
|
||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
||||
|
||||
const Status = {
|
||||
name: 'Status',
|
||||
|
@ -34,12 +35,32 @@ const Status = {
|
|||
muteWords () {
|
||||
return this.$store.state.config.muteWords
|
||||
},
|
||||
repeaterClass () {
|
||||
const user = this.statusoid.user
|
||||
return highlightClass(user)
|
||||
},
|
||||
userClass () {
|
||||
const user = this.retweet ? (this.statusoid.retweeted_status.user) : this.statusoid.user
|
||||
return highlightClass(user)
|
||||
},
|
||||
repeaterStyle () {
|
||||
const user = this.statusoid.user
|
||||
const highlight = this.$store.state.config.highlight
|
||||
return highlightStyle(highlight[user.screen_name])
|
||||
},
|
||||
userStyle () {
|
||||
if (this.noHeading) return
|
||||
const user = this.retweet ? (this.statusoid.retweeted_status.user) : this.statusoid.user
|
||||
const highlight = this.$store.state.config.highlight
|
||||
return highlightStyle(highlight[user.screen_name])
|
||||
},
|
||||
hideAttachments () {
|
||||
return (this.$store.state.config.hideAttachments && !this.inConversation) ||
|
||||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation)
|
||||
},
|
||||
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
|
||||
|
|
|
@ -8,16 +8,17 @@
|
|||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="retweet && !noHeading" class="media container retweet-info">
|
||||
<div v-if="retweet && !noHeading" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" 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>
|
||||
</div>
|
||||
|
||||
<div class="media status">
|
||||
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet }]" :style="[ userStyle ]" class="media status">
|
||||
<div v-if="!noHeading" class="media-left">
|
||||
<a :href="status.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded">
|
||||
<StillImage class='avatar' :class="{'avatar-compact': compact}" :src="status.user.profile_image_url_original"/>
|
||||
|
@ -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>
|
||||
|
@ -315,7 +317,7 @@
|
|||
|
||||
.retweet-info {
|
||||
padding: 0.4em 0.6em 0 0.6em;
|
||||
margin: 0 0 -0.5em 0;
|
||||
margin: 0;
|
||||
.avatar {
|
||||
border-radius: $fallback--avatarAltRadius;
|
||||
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
|
||||
|
@ -427,6 +429,9 @@
|
|||
.status {
|
||||
display: flex;
|
||||
padding: 0.6em;
|
||||
&.is-retweet {
|
||||
padding-top: 0.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.status-conversation:last-child {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7,10 +7,16 @@
|
|||
<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>
|
||||
|
|
|
@ -9,11 +9,6 @@ export default {
|
|||
if (color) {
|
||||
const rgb = hex2rgb(color)
|
||||
const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)`
|
||||
console.log(rgb)
|
||||
console.log([
|
||||
`url(${this.user.cover_photo})`,
|
||||
`linear-gradient(to bottom, ${tintColor}, ${tintColor})`
|
||||
].join(', '))
|
||||
return {
|
||||
backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`,
|
||||
backgroundImage: [
|
||||
|
@ -37,6 +32,29 @@ export default {
|
|||
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)
|
||||
},
|
||||
userHighlightType: {
|
||||
get () {
|
||||
const data = this.$store.state.config.highlight[this.user.screen_name]
|
||||
return data && data.type || 'disabled'
|
||||
},
|
||||
set (type) {
|
||||
const data = this.$store.state.config.highlight[this.user.screen_name]
|
||||
if (type !== 'disabled') {
|
||||
this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: data && data.color || '#FFFFFF', type })
|
||||
} else {
|
||||
this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: undefined })
|
||||
}
|
||||
}
|
||||
},
|
||||
userHighlightColor: {
|
||||
get () {
|
||||
const data = this.$store.state.config.highlight[this.user.screen_name]
|
||||
return data && data.color
|
||||
},
|
||||
set (color) {
|
||||
this.$store.dispatch('setHighlight', { user: this.user.screen_name, color })
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -1,29 +1,46 @@
|
|||
<template>
|
||||
<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>
|
||||
<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>
|
||||
<a :href="user.statusnet_profile_url" target="_blank" class="floater" v-if="isOtherUser">
|
||||
<i class="icon-link-ext usersettings"></i>
|
||||
</a>
|
||||
<div class='container'>
|
||||
<router-link :to="{ name: 'user-profile', params: { id: user.id } }">
|
||||
<StillImage class="avatar" :src="user.profile_image_url_original"/>
|
||||
</router-link>
|
||||
<a :href="user.statusnet_profile_url" target="_blank" style="float: right; margin-top:16px;" v-if="isOtherUser">
|
||||
<i class="icon-link-ext usersettings"></i>
|
||||
</a>
|
||||
<div class='container'>
|
||||
<router-link :to="{ name: 'user-profile', params: { id: user.id } }">
|
||||
<StillImage class="avatar" :src="user.profile_image_url_original"/>
|
||||
<div class="name-and-screen-name">
|
||||
<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 v-if="user.locked"><i class="icon icon-lock"></i></span>
|
||||
<span class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span>
|
||||
</router-link>
|
||||
<div class="name-and-screen-name">
|
||||
<div :title="user.name" class='user-name'>{{user.name}}</div>
|
||||
<router-link class='user-screen-name':to="{ name: 'user-profile', params: { id: user.id } }">
|
||||
<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>
|
||||
</div>
|
||||
<div v-if="isOtherUser" class="user-interactions">
|
||||
<div v-if="user.follows_you && loggedIn" class="following">
|
||||
{{ $t('user_card.follows_you') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-meta">
|
||||
<div v-if="user.follows_you && loggedIn && isOtherUser" class="following">
|
||||
{{ $t('user_card.follows_you') }}
|
||||
</div>
|
||||
<div class="floater" v-if="switcher || isOtherUser">
|
||||
<!-- id's need to be unique, otherwise vue confuses which user-card checkbox belongs to -->
|
||||
<input class="userHighlightText" type="text" :id="'userHighlightColorTx'+user.id" v-if="userHighlightType !== 'disabled'" v-model="userHighlightColor"/>
|
||||
<input class="userHighlightCl" type="color" :id="'userHighlightColor'+user.id" v-if="userHighlightType !== 'disabled'" v-model="userHighlightColor"/>
|
||||
<label for="style-switcher" class='userHighlightSel select'>
|
||||
<select class="userHighlightSel" :id="'userHighlightSel'+user.id" v-model="userHighlightType">
|
||||
<option value="disabled">No highlight</option>
|
||||
<option value="solid">Solid bg</option>
|
||||
<option value="striped">Striped bg</option>
|
||||
<option value="side">Side stripe</option>
|
||||
</select>
|
||||
<i class="icon-down-open"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isOtherUser" class="user-interactions">
|
||||
<div class="follow" v-if="loggedIn">
|
||||
<span v-if="user.following">
|
||||
<!--Following them!-->
|
||||
|
@ -88,7 +105,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>
|
||||
|
@ -179,6 +197,27 @@
|
|||
padding-right: 0.1em;
|
||||
}
|
||||
|
||||
.user-meta {
|
||||
margin-bottom: .4em;
|
||||
|
||||
.following {
|
||||
font-size: 14px;
|
||||
flex: 0 0 100%;
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
text-align: left;
|
||||
float: left;
|
||||
}
|
||||
.floater {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: '';
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
.user-interactions {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
@ -188,14 +227,6 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.following {
|
||||
font-size: 14px;
|
||||
flex: 0 0 100%;
|
||||
margin: 0 0 .4em 0;
|
||||
padding-left: 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mute {
|
||||
max-width: 220px;
|
||||
min-height: 28px;
|
||||
|
@ -278,4 +309,33 @@
|
|||
font-size: 0.7em;
|
||||
color: #CCC;
|
||||
}
|
||||
.floater {
|
||||
float: right;
|
||||
margin-top: 16px;
|
||||
|
||||
.userHighlightCl {
|
||||
padding: 2px 10px;
|
||||
}
|
||||
.userHighlightSel,
|
||||
.userHighlightSel.select {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.userHighlightSel.select i {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.userHighlightText {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.userHighlightCl,
|
||||
.userHighlightText,
|
||||
.userHighlightSel,
|
||||
.userHighlightSel.select {
|
||||
height: 22px;
|
||||
vertical-align: top;
|
||||
margin-right: 0
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -355,7 +355,8 @@ const en = {
|
|||
fullname: 'Display name',
|
||||
email: 'Email',
|
||||
bio: 'Bio',
|
||||
password_confirm: 'Password confirmation'
|
||||
password_confirm: 'Password confirmation',
|
||||
token: 'Invite token'
|
||||
},
|
||||
post_status: {
|
||||
posting: 'Posting',
|
||||
|
@ -1645,7 +1646,8 @@ const ru = {
|
|||
fullname: 'Отображаемое имя',
|
||||
email: 'Email',
|
||||
bio: 'Описание',
|
||||
password_confirm: 'Подтверждение пароля'
|
||||
password_confirm: 'Подтверждение пароля',
|
||||
token: 'Код приглашения'
|
||||
},
|
||||
post_status: {
|
||||
posting: 'Отправляется',
|
||||
|
|
|
@ -53,6 +53,7 @@ const persistedStateOptions = {
|
|||
'config.streaming',
|
||||
'config.muteWords',
|
||||
'config.customTheme',
|
||||
'config.highlight',
|
||||
'users.lastLoginName',
|
||||
'statuses.notifications.maxSavedId'
|
||||
]
|
||||
|
@ -80,11 +81,12 @@ const i18n = new VueI18n({
|
|||
window.fetch('/api/statusnet/config.json')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const {name, closed: registrationClosed, textlimit} = data.site
|
||||
const {name, closed: registrationClosed, textlimit, server} = data.site
|
||||
|
||||
store.dispatch('setOption', { name: 'name', value: name })
|
||||
store.dispatch('setOption', { name: 'registrationOpen', value: (registrationClosed === '0') })
|
||||
store.dispatch('setOption', { name: 'textlimit', value: parseInt(textlimit) })
|
||||
store.dispatch('setOption', { name: 'server', value: server })
|
||||
})
|
||||
|
||||
window.fetch('/static/config.json')
|
||||
|
@ -120,6 +122,7 @@ window.fetch('/static/config.json')
|
|||
{ name: 'mentions', path: '/:username/mentions', component: Mentions },
|
||||
{ name: 'settings', path: '/settings', component: Settings },
|
||||
{ name: 'registration', path: '/registration', component: Registration },
|
||||
{ name: 'registration', path: '/registration/:token', component: Registration },
|
||||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests },
|
||||
{ name: 'user-settings', path: '/user-settings', component: UserSettings }
|
||||
]
|
||||
|
@ -183,4 +186,3 @@ window.fetch('/instance/panel.html')
|
|||
.then((html) => {
|
||||
store.dispatch('setOption', { name: 'instanceSpecificPanelContent', value: html })
|
||||
})
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { set } from 'vue'
|
||||
import { set, delete as del } from 'vue'
|
||||
import StyleSetter from '../services/style_setter/style_setter.js'
|
||||
|
||||
const defaultState = {
|
||||
|
@ -10,7 +10,8 @@ const defaultState = {
|
|||
autoLoad: true,
|
||||
streaming: false,
|
||||
hoverPreview: true,
|
||||
muteWords: []
|
||||
muteWords: [],
|
||||
highlight: {}
|
||||
}
|
||||
|
||||
const config = {
|
||||
|
@ -18,12 +19,23 @@ const config = {
|
|||
mutations: {
|
||||
setOption (state, { name, value }) {
|
||||
set(state, name, value)
|
||||
},
|
||||
setHighlight (state, { user, color, type }) {
|
||||
const data = this.state.config.highlight[user]
|
||||
if (color || type) {
|
||||
set(state.highlight, user, { color: color || data.color, type: type || data.type })
|
||||
} else {
|
||||
del(state.highlight, user)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setPageTitle ({state}, option = '') {
|
||||
document.title = `${option} ${state.name}`
|
||||
},
|
||||
setHighlight ({ commit, dispatch }, { user, color, type }) {
|
||||
commit('setHighlight', {user, color, type})
|
||||
},
|
||||
setOption ({ commit, dispatch }, { name, value }) {
|
||||
commit('setOption', {name, value})
|
||||
switch (name) {
|
||||
|
|
|
@ -42,6 +42,10 @@ export const mutations = {
|
|||
},
|
||||
setUserForStatus (state, status) {
|
||||
status.user = state.usersObject[status.user.id]
|
||||
},
|
||||
setColor (state, { user: {id}, highlighted }) {
|
||||
const user = state.usersObject[id]
|
||||
set(user, 'highlight', highlighted)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -160,6 +160,7 @@ const updateProfile = ({credentials, params}) => {
|
|||
// bio
|
||||
// homepage
|
||||
// location
|
||||
// token
|
||||
const register = (params) => {
|
||||
const form = new FormData()
|
||||
|
||||
|
|
48
src/services/user_highlighter/user_highlighter.js
Normal file
48
src/services/user_highlighter/user_highlighter.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { hex2rgb } from '../color_convert/color_convert.js'
|
||||
const highlightStyle = (prefs) => {
|
||||
if (prefs === undefined) return
|
||||
const {color, type} = prefs
|
||||
if (typeof color !== 'string') return
|
||||
const rgb = hex2rgb(color)
|
||||
if (rgb == null) return
|
||||
const solidColor = `rgb(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)})`
|
||||
const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .1)`
|
||||
const tintColor2 = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .2)`
|
||||
if (type === 'striped') {
|
||||
return {
|
||||
backgroundImage: [
|
||||
'repeating-linear-gradient(-45deg,',
|
||||
`${tintColor} ,`,
|
||||
`${tintColor} 20px,`,
|
||||
`${tintColor2} 20px,`,
|
||||
`${tintColor2} 40px`
|
||||
].join(' '),
|
||||
backgroundPosition: '0 0'
|
||||
}
|
||||
} else if (type === 'solid') {
|
||||
return {
|
||||
backgroundColor: tintColor2
|
||||
}
|
||||
} else if (type === 'side') {
|
||||
return {
|
||||
backgroundImage: [
|
||||
'linear-gradient(to right,',
|
||||
`${solidColor} ,`,
|
||||
`${solidColor} 2px,`,
|
||||
`transparent 6px`
|
||||
].join(' '),
|
||||
backgroundPosition: '0 0'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const highlightClass = (user) => {
|
||||
return 'USER____' + user.screen_name
|
||||
.replace(/\./g, '_')
|
||||
.replace(/@/g, '_AT_')
|
||||
}
|
||||
|
||||
export {
|
||||
highlightClass,
|
||||
highlightStyle
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue