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:
Henry Jameson 2018-08-16 13:59:01 +03:00
commit 6454837ea4
36 changed files with 334 additions and 86 deletions

View file

@ -8,3 +8,4 @@ Contributors of this project.
- hakui (hakui@freezepeach.xyz): CSS and styling - hakui (hakui@freezepeach.xyz): CSS and styling
- shpuld (shpuld@shitposter.club): CSS and styling - shpuld (shpuld@shitposter.club): CSS and styling
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images. - Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
- hj (hj@shigusegubu.club): Code

View file

@ -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-] { i[class*=icon-] {
color: $fallback--icon; color: $fallback--icon;
color: var(--icon, $fallback--icon) color: var(--icon, $fallback--icon)

View file

@ -1,6 +1,7 @@
import Status from '../status/status.vue' import Status from '../status/status.vue'
import StillImage from '../still-image/still-image.vue' import StillImage from '../still-image/still-image.vue'
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCardContent from '../user_card_content/user_card_content.vue'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
const Notification = { const Notification = {
data () { data () {
@ -18,6 +19,16 @@ const Notification = {
toggleUserExpanded () { toggleUserExpanded () {
this.userExpanded = !this.userExpanded 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])
}
} }
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<status v-if="notification.type === 'mention'" :compact="true" :statusoid="notification.status"></status> <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"> <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"/> <StillImage class='avatar-compact' :src="notification.action.user.profile_image_url_original"/>
</a> </a>
@ -10,7 +10,8 @@
</div> </div>
<span class="notification-details"> <span class="notification-details">
<div class="name-and-action"> <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'"> <span v-if="notification.type === 'like'">
<i class="fa icon-star lit"></i> <i class="fa icon-star lit"></i>
<small>{{$t('notifications.favorited_you')}}</small> <small>{{$t('notifications.favorited_you')}}</small>

View file

@ -45,8 +45,7 @@
} }
.unseen { .unseen {
border-left: 4px solid $fallback--cRed; box-shadow: inset 4px 0 0 var(--cRed, $fallback--cRed);
border-left: 4px solid var(--cRed, $fallback--cRed);
padding-left: 0; padding-left: 0;
} }
} }
@ -56,7 +55,6 @@
display: flex; display: flex;
border-bottom: 1px solid; border-bottom: 1px solid;
border-bottom-color: inherit; border-bottom-color: inherit;
padding-left: 4px;
.broken-favorite { .broken-favorite {
border-radius: $fallback--tooltipRadius; border-radius: $fallback--tooltipRadius;

View file

@ -31,6 +31,10 @@ const PostStatusForm = {
}, },
mounted () { mounted () {
this.resize(this.$refs.textarea) this.resize(this.$refs.textarea)
if (this.replyTo) {
this.$refs.textarea.focus()
}
}, },
data () { data () {
const preset = this.$route.query.message const preset = this.$route.query.message
@ -87,11 +91,11 @@ const PostStatusForm = {
return false return false
} }
return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}, index) => ({ return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}, index) => ({
// eslint-disable-next-line camelcase
screen_name: `:${shortcode}:`, screen_name: `:${shortcode}:`,
name: '', name: '',
utf: utf || '', utf: utf || '',
img: image_url, // eslint-disable-next-line camelcase
img: utf ? '' : this.$store.state.config.server + image_url,
highlighted: index === this.highlighted highlighted: index === this.highlighted
})) }))
} else { } else {

View file

@ -5,17 +5,23 @@ const registration = {
registering: false registering: false
}), }),
created () { 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') 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: { computed: {
termsofservice () { return this.$store.state.config.tos } termsofservice () { return this.$store.state.config.tos },
token () { return this.$route.params.token }
}, },
methods: { methods: {
submit () { submit () {
this.registering = true this.registering = true
this.user.nickname = this.user.username this.user.nickname = this.user.username
this.user.token = this.token
this.$store.state.api.backendInteractor.register(this.user).then( this.$store.state.api.backendInteractor.register(this.user).then(
(response) => { (response) => {
if (response.ok) { if (response.ok) {

View file

@ -38,6 +38,10 @@
<input :disabled="registering" v-model='user.captcha' placeholder='Enter captcha' type='test' class='form-control' id='captcha'> <input :disabled="registering" v-model='user.captcha' placeholder='Enter captcha' type='test' class='form-control' id='captcha'>
</div> </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'> <div class='form-group'>
<button :disabled="registering" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button> <button :disabled="registering" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button>
</div> </div>

View file

@ -1,5 +1,5 @@
const RetweetButton = { const RetweetButton = {
props: ['status', 'loggedIn'], props: ['status', 'loggedIn', 'visibility'],
data () { data () {
return { return {
animated: false animated: false

View file

@ -1,9 +1,9 @@
<template> <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> <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> <span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
</div> </div>
<div v-else> <div v-else-if="!loggedIn">
<i :class='classes' class='icon-retweet'></i> <i :class='classes' class='icon-retweet'></i>
<span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span> <span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
</div> </div>

View file

@ -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 UserCardContent from '../user_card_content/user_card_content.vue'
import StillImage from '../still-image/still-image.vue' import StillImage from '../still-image/still-image.vue'
import { filter, find } from 'lodash' import { filter, find } from 'lodash'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
const Status = { const Status = {
name: 'Status', name: 'Status',
@ -34,12 +35,32 @@ const Status = {
muteWords () { muteWords () {
return this.$store.state.config.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 () { hideAttachments () {
return (this.$store.state.config.hideAttachments && !this.inConversation) || return (this.$store.state.config.hideAttachments && !this.inConversation) ||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation) (this.$store.state.config.hideAttachmentsInConv && this.inConversation)
}, },
retweet () { return !!this.statusoid.retweeted_status }, retweet () { return !!this.statusoid.retweeted_status },
retweeter () { return this.statusoid.user.name }, retweeter () { return this.statusoid.user.name },
retweeterHtml () { return this.statusoid.user.name_html },
status () { status () {
if (this.retweet) { if (this.retweet) {
return this.statusoid.retweeted_status return this.statusoid.retweeted_status

View file

@ -8,16 +8,17 @@
</div> </div>
</template> </template>
<template v-else> <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"/> <StillImage v-if="retweet" class='avatar' :src="statusoid.user.profile_image_url_original"/>
<div class="media-body faint"> <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> <i class='fa icon-retweet retweeted'></i>
{{$t('timeline.repeated')}} {{$t('timeline.repeated')}}
</div> </div>
</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"> <div v-if="!noHeading" class="media-left">
<a :href="status.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded"> <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"/> <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 v-if="!noHeading" class="media-body container media-heading">
<div class="media-heading-left"> <div class="media-heading-left">
<div class="name-and-links"> <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"> <span class="links">
<router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link> <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"> <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> <i class="icon-reply" :class="{'icon-reply-active': replying}"></i>
</a> </a>
</div> </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> <favorite-button :loggedIn='loggedIn' :status='status'></favorite-button>
<delete-button :status='status'></delete-button> <delete-button :status='status'></delete-button>
</div> </div>
@ -315,7 +317,7 @@
.retweet-info { .retweet-info {
padding: 0.4em 0.6em 0 0.6em; padding: 0.4em 0.6em 0 0.6em;
margin: 0 0 -0.5em 0; margin: 0;
.avatar { .avatar {
border-radius: $fallback--avatarAltRadius; border-radius: $fallback--avatarAltRadius;
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
@ -427,6 +429,9 @@
.status { .status {
display: flex; display: flex;
padding: 0.6em; padding: 0.6em;
&.is-retweet {
padding-top: 0.1em;
}
} }
.status-conversation:last-child { .status-conversation:last-child {

View file

@ -3,7 +3,10 @@
<div>{{$t('settings.presets')}} <div>{{$t('settings.presets')}}
<label for="style-switcher" class='select'> <label for="style-switcher" class='select'>
<select id="style-switcher" v-model="selected" class="style-switcher"> <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> </select>
<i class="icon-down-open"/> <i class="icon-down-open"/>
</label> </label>

View file

@ -13,7 +13,8 @@ const Timeline = {
], ],
data () { data () {
return { return {
paused: false paused: false,
unfocused: false
} }
}, },
computed: { computed: {
@ -65,8 +66,15 @@ const Timeline = {
this.fetchFollowers() this.fetchFollowers()
} }
}, },
mounted () {
if (typeof document.hidden !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
this.unfocused = document.hidden
}
},
destroyed () { destroyed () {
window.removeEventListener('scroll', this.scrollLoad) 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 }) this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
}, },
methods: { methods: {
@ -113,6 +121,9 @@ const Timeline = {
(window.innerHeight + window.pageYOffset) >= (height - 750)) { (window.innerHeight + window.pageYOffset) >= (height - 750)) {
this.fetchOlderStatuses() this.fetchOlderStatuses()
} }
},
handleVisibilityChange () {
this.unfocused = document.hidden
} }
}, },
watch: { watch: {
@ -122,7 +133,7 @@ const Timeline = {
} }
if (count > 0) { if (count > 0) {
// only 'stream' them when you're scrolled to the top // 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() this.showNewStatuses()
} else { } else {
this.paused = true this.paused = true

View file

@ -7,7 +7,13 @@
<user-card-content :user="user" :switcher="false"></user-card-content> <user-card-content :user="user" :switcher="false"></user-card-content>
</div> </div>
<div class="name-and-screen-name" v-else> <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 }} {{ user.name }}
<span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you"> <span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you">
{{ $t('user_card.follows_you') }} {{ $t('user_card.follows_you') }}

View file

@ -9,11 +9,6 @@ export default {
if (color) { if (color) {
const rgb = hex2rgb(color) const rgb = hex2rgb(color)
const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)` 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 { return {
backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`, backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`,
backgroundImage: [ backgroundImage: [
@ -37,6 +32,29 @@ export default {
dailyAvg () { dailyAvg () {
const days = Math.ceil((new Date() - new Date(this.user.created_at)) / (60 * 60 * 24 * 1000)) const days = Math.ceil((new Date() - new Date(this.user.created_at)) / (60 * 60 * 24 * 1000))
return Math.round(this.user.statuses_count / days) 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: { components: {

View file

@ -5,7 +5,7 @@
<router-link to='/user-settings' style="float: right; margin-top:16px;" v-if="!isOtherUser"> <router-link to='/user-settings' style="float: right; margin-top:16px;" v-if="!isOtherUser">
<i class="icon-cog usersettings"></i> <i class="icon-cog usersettings"></i>
</router-link> </router-link>
<a :href="user.statusnet_profile_url" target="_blank" style="float: right; margin-top:16px;" v-if="isOtherUser"> <a :href="user.statusnet_profile_url" target="_blank" class="floater" v-if="isOtherUser">
<i class="icon-link-ext usersettings"></i> <i class="icon-link-ext usersettings"></i>
</a> </a>
<div class='container'> <div class='container'>
@ -13,17 +13,34 @@
<StillImage class="avatar" :src="user.profile_image_url_original"/> <StillImage class="avatar" :src="user.profile_image_url_original"/>
</router-link> </router-link>
<div class="name-and-screen-name"> <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 } }"> <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>@{{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> <span class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span>
</router-link> </router-link>
</div> </div>
</div> </div>
<div v-if="isOtherUser" class="user-interactions"> <div class="user-meta">
<div v-if="user.follows_you && loggedIn" class="following"> <div v-if="user.follows_you && loggedIn && isOtherUser" class="following">
{{ $t('user_card.follows_you') }} {{ $t('user_card.follows_you') }}
</div> </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"> <div class="follow" v-if="loggedIn">
<span v-if="user.following"> <span v-if="user.following">
<!--Following them!--> <!--Following them!-->
@ -88,7 +105,8 @@
<span>{{user.followers_count}}</span> <span>{{user.followers_count}}</span>
</div> </div>
</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>
</div> </div>
</template> </template>
@ -179,6 +197,27 @@
padding-right: 0.1em; 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 { .user-interactions {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
@ -188,14 +227,6 @@
flex: 1; flex: 1;
} }
.following {
font-size: 14px;
flex: 0 0 100%;
margin: 0 0 .4em 0;
padding-left: 16px;
text-align: left;
}
.mute { .mute {
max-width: 220px; max-width: 220px;
min-height: 28px; min-height: 28px;
@ -278,4 +309,33 @@
font-size: 0.7em; font-size: 0.7em;
color: #CCC; 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> </style>

View file

@ -355,7 +355,8 @@ const en = {
fullname: 'Display name', fullname: 'Display name',
email: 'Email', email: 'Email',
bio: 'Bio', bio: 'Bio',
password_confirm: 'Password confirmation' password_confirm: 'Password confirmation',
token: 'Invite token'
}, },
post_status: { post_status: {
posting: 'Posting', posting: 'Posting',
@ -1645,7 +1646,8 @@ const ru = {
fullname: 'Отображаемое имя', fullname: 'Отображаемое имя',
email: 'Email', email: 'Email',
bio: 'Описание', bio: 'Описание',
password_confirm: 'Подтверждение пароля' password_confirm: 'Подтверждение пароля',
token: 'Код приглашения'
}, },
post_status: { post_status: {
posting: 'Отправляется', posting: 'Отправляется',

View file

@ -53,6 +53,7 @@ const persistedStateOptions = {
'config.streaming', 'config.streaming',
'config.muteWords', 'config.muteWords',
'config.customTheme', 'config.customTheme',
'config.highlight',
'users.lastLoginName', 'users.lastLoginName',
'statuses.notifications.maxSavedId' 'statuses.notifications.maxSavedId'
] ]
@ -80,11 +81,12 @@ const i18n = new VueI18n({
window.fetch('/api/statusnet/config.json') window.fetch('/api/statusnet/config.json')
.then((res) => res.json()) .then((res) => res.json())
.then((data) => { .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: 'name', value: name })
store.dispatch('setOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) store.dispatch('setOption', { name: 'registrationOpen', value: (registrationClosed === '0') })
store.dispatch('setOption', { name: 'textlimit', value: parseInt(textlimit) }) store.dispatch('setOption', { name: 'textlimit', value: parseInt(textlimit) })
store.dispatch('setOption', { name: 'server', value: server })
}) })
window.fetch('/static/config.json') window.fetch('/static/config.json')
@ -120,6 +122,7 @@ window.fetch('/static/config.json')
{ name: 'mentions', path: '/:username/mentions', component: Mentions }, { name: 'mentions', path: '/:username/mentions', component: Mentions },
{ name: 'settings', path: '/settings', component: Settings }, { name: 'settings', path: '/settings', component: Settings },
{ name: 'registration', path: '/registration', component: Registration }, { name: 'registration', path: '/registration', component: Registration },
{ name: 'registration', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests }, { name: 'friend-requests', path: '/friend-requests', component: FollowRequests },
{ name: 'user-settings', path: '/user-settings', component: UserSettings } { name: 'user-settings', path: '/user-settings', component: UserSettings }
] ]
@ -183,4 +186,3 @@ window.fetch('/instance/panel.html')
.then((html) => { .then((html) => {
store.dispatch('setOption', { name: 'instanceSpecificPanelContent', value: html }) store.dispatch('setOption', { name: 'instanceSpecificPanelContent', value: html })
}) })

View file

@ -1,4 +1,4 @@
import { set } from 'vue' import { set, delete as del } from 'vue'
import StyleSetter from '../services/style_setter/style_setter.js' import StyleSetter from '../services/style_setter/style_setter.js'
const defaultState = { const defaultState = {
@ -10,7 +10,8 @@ const defaultState = {
autoLoad: true, autoLoad: true,
streaming: false, streaming: false,
hoverPreview: true, hoverPreview: true,
muteWords: [] muteWords: [],
highlight: {}
} }
const config = { const config = {
@ -18,12 +19,23 @@ const config = {
mutations: { mutations: {
setOption (state, { name, value }) { setOption (state, { name, value }) {
set(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: { actions: {
setPageTitle ({state}, option = '') { setPageTitle ({state}, option = '') {
document.title = `${option} ${state.name}` document.title = `${option} ${state.name}`
}, },
setHighlight ({ commit, dispatch }, { user, color, type }) {
commit('setHighlight', {user, color, type})
},
setOption ({ commit, dispatch }, { name, value }) { setOption ({ commit, dispatch }, { name, value }) {
commit('setOption', {name, value}) commit('setOption', {name, value})
switch (name) { switch (name) {

View file

@ -42,6 +42,10 @@ export const mutations = {
}, },
setUserForStatus (state, status) { setUserForStatus (state, status) {
status.user = state.usersObject[status.user.id] status.user = state.usersObject[status.user.id]
},
setColor (state, { user: {id}, highlighted }) {
const user = state.usersObject[id]
set(user, 'highlight', highlighted)
} }
} }

View file

@ -160,6 +160,7 @@ const updateProfile = ({credentials, params}) => {
// bio // bio
// homepage // homepage
// location // location
// token
const register = (params) => { const register = (params) => {
const form = new FormData() const form = new FormData()

View 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
}

View file

@ -19,6 +19,15 @@ Font license info
Homepage: http://www.entypo.com Homepage: http://www.entypo.com
## Iconic
Copyright (C) 2012 by P.J. Onori
Author: P.J. Onori
License: SIL (http://scripts.sil.org/OFL)
Homepage: http://somerandomdude.com/work/iconic/
## Fontelico ## Fontelico
Copyright (C) 2012 by Fontello project Copyright (C) 2012 by Fontello project

View file

@ -179,6 +179,12 @@
"css": "globe", "css": "globe",
"code": 59410, "code": 59410,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "b3a9e2dab4d19ea3b2f628242c926bfe",
"css": "brush",
"code": 59411,
"src": "iconic"
} }
] ]
} }

View file

@ -18,6 +18,7 @@
.icon-bell:before { content: '\e810'; } /* '' */ .icon-bell:before { content: '\e810'; } /* '' */
.icon-lock:before { content: '\e811'; } /* '' */ .icon-lock:before { content: '\e811'; } /* '' */
.icon-globe:before { content: '\e812'; } /* '' */ .icon-globe:before { content: '\e812'; } /* '' */
.icon-brush:before { content: '\e813'; } /* '' */
.icon-spin3:before { content: '\e832'; } /* '' */ .icon-spin3:before { content: '\e832'; } /* '' */
.icon-spin4:before { content: '\e834'; } /* '' */ .icon-spin4:before { content: '\e834'; } /* '' */
.icon-link-ext:before { content: '\f08e'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */

File diff suppressed because one or more lines are too long

View file

@ -18,6 +18,7 @@
.icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); } .icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); } .icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); } .icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
.icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }
.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); } .icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); } .icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); } .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }

View file

@ -29,6 +29,7 @@
.icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); } .icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); } .icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); } .icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
.icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }
.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); } .icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); } .icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); } .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }

View file

@ -1,11 +1,11 @@
@font-face { @font-face {
font-family: 'fontello'; font-family: 'fontello';
src: url('../font/fontello.eot?92801760'); src: url('../font/fontello.eot?70327001');
src: url('../font/fontello.eot?92801760#iefix') format('embedded-opentype'), src: url('../font/fontello.eot?70327001#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?92801760') format('woff2'), url('../font/fontello.woff2?70327001') format('woff2'),
url('../font/fontello.woff?92801760') format('woff'), url('../font/fontello.woff?70327001') format('woff'),
url('../font/fontello.ttf?92801760') format('truetype'), url('../font/fontello.ttf?70327001') format('truetype'),
url('../font/fontello.svg?92801760#fontello') format('svg'); url('../font/fontello.svg?70327001#fontello') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) { @media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face { @font-face {
font-family: 'fontello'; font-family: 'fontello';
src: url('../font/fontello.svg?92801760#fontello') format('svg'); src: url('../font/fontello.svg?70327001#fontello') format('svg');
} }
} }
*/ */
@ -74,6 +74,7 @@
.icon-bell:before { content: '\e810'; } /* '' */ .icon-bell:before { content: '\e810'; } /* '' */
.icon-lock:before { content: '\e811'; } /* '' */ .icon-lock:before { content: '\e811'; } /* '' */
.icon-globe:before { content: '\e812'; } /* '' */ .icon-globe:before { content: '\e812'; } /* '' */
.icon-brush:before { content: '\e813'; } /* '' */
.icon-spin3:before { content: '\e832'; } /* '' */ .icon-spin3:before { content: '\e832'; } /* '' */
.icon-spin4:before { content: '\e834'; } /* '' */ .icon-spin4:before { content: '\e834'; } /* '' */
.icon-link-ext:before { content: '\f08e'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */

View file

@ -229,11 +229,11 @@ body {
} }
@font-face { @font-face {
font-family: 'fontello'; font-family: 'fontello';
src: url('./font/fontello.eot?48963108'); src: url('./font/fontello.eot?31375268');
src: url('./font/fontello.eot?48963108#iefix') format('embedded-opentype'), src: url('./font/fontello.eot?31375268#iefix') format('embedded-opentype'),
url('./font/fontello.woff?48963108') format('woff'), url('./font/fontello.woff?31375268') format('woff'),
url('./font/fontello.ttf?48963108') format('truetype'), url('./font/fontello.ttf?31375268') format('truetype'),
url('./font/fontello.svg?48963108#fontello') format('svg'); url('./font/fontello.svg?31375268#fontello') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@ -325,21 +325,22 @@ body {
<div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-bell">&#xe810;</i> <span class="i-name">icon-bell</span><span class="i-code">0xe810</span></div> <div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-bell">&#xe810;</i> <span class="i-name">icon-bell</span><span class="i-code">0xe810</span></div>
<div class="the-icons span3" title="Code: 0xe811"><i class="demo-icon icon-lock">&#xe811;</i> <span class="i-name">icon-lock</span><span class="i-code">0xe811</span></div> <div class="the-icons span3" title="Code: 0xe811"><i class="demo-icon icon-lock">&#xe811;</i> <span class="i-name">icon-lock</span><span class="i-code">0xe811</span></div>
<div class="the-icons span3" title="Code: 0xe812"><i class="demo-icon icon-globe">&#xe812;</i> <span class="i-name">icon-globe</span><span class="i-code">0xe812</span></div> <div class="the-icons span3" title="Code: 0xe812"><i class="demo-icon icon-globe">&#xe812;</i> <span class="i-name">icon-globe</span><span class="i-code">0xe812</span></div>
<div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div> <div class="the-icons span3" title="Code: 0xe813"><i class="demo-icon icon-brush">&#xe813;</i> <span class="i-name">icon-brush</span><span class="i-code">0xe813</span></div>
</div> </div>
<div class="row"> <div class="row">
<div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div>
<div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div> <div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div>
<div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div> <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div>
<div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div> <div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div>
<div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div>
</div> </div>
<div class="row"> <div class="row">
<div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div>
<div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div> <div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div>
<div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div> <div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div>
<div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div> <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div>
<div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
</div> </div>
<div class="row"> <div class="row">
<div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
<div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus">&#xf234;</i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div> <div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus">&#xf234;</i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div>
</div> </div>
</div> </div>

Binary file not shown.

View file

@ -44,6 +44,8 @@
<glyph glyph-name="globe" unicode="&#xe812;" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" /> <glyph glyph-name="globe" unicode="&#xe812;" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" />
<glyph glyph-name="brush" unicode="&#xe813;" d="M464 202q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" />
<glyph glyph-name="spin3" unicode="&#xe832;" d="M494 850c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" /> <glyph glyph-name="spin3" unicode="&#xe832;" d="M494 850c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
<glyph glyph-name="spin4" unicode="&#xe834;" d="M498 850c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" /> <glyph glyph-name="spin4" unicode="&#xe834;" d="M498 850c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.