commit: 958acbab8d43b7514369f4bc69bc2fe0353eddf2
parent: 48391a45ba8924e20f280868bca78d77a32c3ddd
Author: kaniini <nenolod@gmail.com>
Date: Fri, 31 Aug 2018 04:10:17 +0000
Merge branch 'polish' into 'develop'
Another one of those MR that fixes many many small-to-medium things
Closes #92, #75, #122, #52, #72, and #87
See merge request pleroma/pleroma-fe!324
Diffstat:
33 files changed, 867 insertions(+), 451 deletions(-)
diff --git a/.babelrc b/.babelrc
@@ -1,5 +1,5 @@
{
- "presets": ["es2015", "stage-2"],
- "plugins": ["transform-runtime", "lodash"],
+ "presets": ["es2015", "stage-2", "env"],
+ "plugins": ["transform-runtime", "lodash", "transform-vue-jsx"],
"comments": false
}
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
@@ -54,7 +54,7 @@ module.exports = {
loader: 'vue'
},
{
- test: /\.js$/,
+ test: /\.jsx?$/,
loader: 'babel',
include: projectRoot,
exclude: /node_modules\/(?!tributejs)/
diff --git a/package.json b/package.json
@@ -37,8 +37,12 @@
"autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^7.0.0",
+ "babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^6.0.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.0.0",
+ "babel-plugin-transform-vue-jsx": "3",
+ "babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.0.0",
"babel-preset-stage-2": "^6.0.0",
"babel-register": "^6.0.0",
diff --git a/src/App.js b/src/App.js
@@ -18,7 +18,14 @@ export default {
ChatPanel
},
data: () => ({
- mobileActivePanel: 'timeline'
+ mobileActivePanel: 'timeline',
+ supportsMask: window.CSS && window.CSS.supports && (
+ window.CSS.supports('mask-size', 'contain') ||
+ window.CSS.supports('-webkit-mask-size', 'contain') ||
+ window.CSS.supports('-moz-mask-size', 'contain') ||
+ window.CSS.supports('-ms-mask-size', 'contain') ||
+ window.CSS.supports('-o-mask-size', 'contain')
+ )
}),
created () {
// Load the locale from the storage
@@ -29,7 +36,27 @@ export default {
background () {
return this.currentUser.background_image || this.$store.state.config.background
},
- logoStyle () { return { 'background-image': `url(${this.$store.state.config.logo})` } },
+ enableMask () { return this.supportsMask && this.$store.state.config.logoMask },
+ logoStyle () {
+ return {
+ 'visibility': this.enableMask ? 'hidden' : 'visible'
+ }
+ },
+ logoMaskStyle () {
+ return this.enableMask ? {
+ 'mask-image': `url(${this.$store.state.config.logo})`
+ } : {
+ 'background-color': this.enableMask ? '' : 'transparent'
+ }
+ },
+ logoBgStyle () {
+ return Object.assign({
+ 'margin': `${this.$store.state.config.logoMargin} 0`
+ }, this.enableMask ? {} : {
+ 'background-color': this.enableMask ? '' : 'transparent'
+ })
+ },
+ logo () { return this.$store.state.config.logo },
style () { return { 'background-image': `url(${this.background})` } },
sitename () { return this.$store.state.config.name },
chat () { return this.$store.state.chat.channel.state === 'joined' },
diff --git a/src/App.scss b/src/App.scss
@@ -48,7 +48,7 @@ a {
color: var(--link, $fallback--link);
}
-button{
+button {
user-select: none;
color: $fallback--fg;
color: var(--fg, $fallback--fg);
@@ -64,10 +64,19 @@ button{
font-size: 14px;
font-family: sans-serif;
+ &::-moz-focus-inner {
+ border: none;
+ }
+
&:hover {
box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3);
}
+ &:active {
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+ border-top: 1px solid rgba(0, 0, 0, 0.2);
+ }
+
&:disabled {
cursor: not-allowed;
opacity: 0.5;
@@ -105,6 +114,7 @@ input, textarea, .select {
position: relative;
height: 29px;
line-height: 16px;
+ hyphens: none;
.icon-down-open {
position: absolute;
@@ -226,6 +236,36 @@ nav {
position: fixed;
height: 50px;
+ .logo {
+ display: flex;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+
+ align-items: stretch;
+ justify-content: center;
+ flex: 0 0 auto;
+ z-index: -1;
+ .mask {
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: contain;
+ background-color: $fallback--fg;
+ background-color: var(--fg, $fallback--fg);
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+ img {
+ display: block;
+ flex: 0;
+ }
+ }
+
.inner-nav {
padding-left: 20px;
padding-right: 20px;
@@ -234,9 +274,6 @@ nav {
flex-basis: 970px;
margin: auto;
height: 50px;
- background-repeat: no-repeat;
- background-position: center;
- background-size: auto 80%;
a i {
color: $fallback--link;
@@ -282,15 +319,25 @@ main-router {
}
.panel-heading {
+ display: flex;
border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
background-size: cover;
- padding: 0.6em 1.0em;
+ padding: .6em .6em;
text-align: left;
font-size: 1.3em;
line-height: 24px;
background-color: $fallback--btn;
background-color: var(--btn, $fallback--btn);
+ align-items: baseline;
+
+ .title {
+ flex: 1 0 auto;
+ }
+
+ button {
+ height: 100%;
+ }
}
.panel-heading.stub {
@@ -464,4 +511,3 @@ nav {
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
}
-
diff --git a/src/App.vue b/src/App.vue
@@ -1,7 +1,11 @@
<template>
<div id="app" v-bind:style="style">
<nav class='container' @click="scrollToTop()" id="nav">
- <div class='inner-nav' :style="logoStyle">
+ <div class='logo' :style='logoBgStyle'>
+ <div class='mask' :style='logoMaskStyle'></div>
+ <img :src='logo' :style='logoStyle'>
+ </div>
+ <div class='inner-nav'>
<div class='item'>
<router-link :to="{ name: 'root'}">{{sitename}}</router-link>
</div>
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
@@ -16,7 +16,7 @@ const Attachment = {
loopVideo: this.$store.state.config.loopVideo,
showHidden: false,
loading: false,
- img: this.type === 'image' && document.createElement('img')
+ img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img')
}
},
components: {
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
@@ -51,6 +51,10 @@
.nsfw-placeholder {
cursor: pointer;
+
+ &.loading {
+ cursor: progress;
+ }
}
.small-attachment {
@@ -61,6 +65,7 @@
}
.attachment {
+ position: relative;
flex: 1 0 30%;
margin: 0.5em 0.7em 0.6em 0.0em;
align-self: flex-start;
@@ -88,10 +93,6 @@
display: flex;
}
- &.loading {
- cursor: progress;
- }
-
.hider {
position: absolute;
margin: 10px;
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
@@ -1,8 +1,8 @@
<template>
<div class="timeline panel panel-default">
<div class="panel-heading conversation-heading">
- {{ $t('timeline.conversation') }}
- <span v-if="collapsable" style="float:right;">
+ <span class="title"> {{ $t('timeline.conversation') }} </span>
+ <span v-if="collapsable">
<small><a href="#" @click.prevent="$emit('toggleExpanded')">{{ $t('timeline.collapse') }}</a></small>
</span>
</div>
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
@@ -6,8 +6,10 @@ const mediaUpload = {
const input = this.$el.querySelector('input')
input.addEventListener('change', ({target}) => {
- const file = target.files[0]
- this.uploadFile(file)
+ for (var i = 0; i < target.files.length; i++) {
+ let file = target.files[i]
+ this.uploadFile(file)
+ }
})
},
data () {
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
@@ -3,7 +3,7 @@
<label class="btn btn-default">
<i class="icon-spin4 animate-spin" v-if="uploading"></i>
<i class="icon-upload" v-if="!uploading"></i>
- <input type=file style="position: fixed; top: -100em"></input>
+ <input type="file" style="position: fixed; top: -100em" multiple="true"></input>
</label>
</div>
</template>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
@@ -11,6 +11,14 @@ const Notifications = {
notificationsFetcher.startFetching({ store, credentials })
},
computed: {
+ visibleTypes () {
+ return [
+ this.$store.state.config.notificationVisibility.likes && 'like',
+ this.$store.state.config.notificationVisibility.mentions && 'mention',
+ this.$store.state.config.notificationVisibility.repeats && 'repeat',
+ this.$store.state.config.notificationVisibility.follows && 'follow'
+ ].filter(_ => _)
+ },
notifications () {
return this.$store.state.statuses.notifications.data
},
@@ -18,13 +26,13 @@ const Notifications = {
return this.$store.state.statuses.notifications.error
},
unseenNotifications () {
- return filter(this.notifications, ({seen}) => !seen)
+ return filter(this.visibleNotifications, ({seen}) => !seen)
},
visibleNotifications () {
// Don't know why, but sortBy([seen, -action.id]) doesn't work.
let sortedNotifications = sortBy(this.notifications, ({action}) => -action.id)
sortedNotifications = sortBy(sortedNotifications, 'seen')
- return sortedNotifications
+ return sortedNotifications.filter((notification) => this.visibleTypes.includes(notification.type))
},
unseenCount () {
return this.unseenNotifications.length
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
@@ -4,58 +4,25 @@
// a bit of a hack to allow scrolling below notifications
padding-bottom: 15em;
- .title {
- display: inline-block;
- }
-
- .panel {
- background: $fallback--bg;
- background: var(--bg, $fallback--bg)
- }
-
- .panel-body {
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border)
- }
-
- .panel-heading {
- // force the text to stay centered, while keeping
- // the button in the right side of the panel heading
- position: relative;
- background: $fallback--btn;
- background: var(--btn, $fallback--btn);
- color: $fallback--fg;
- color: var(--fg, $fallback--fg);
- display: flex;
- align-items: baseline;
- .read-button {
- position: absolute;
- right: 0.7em;
- height: 1.8em;
- line-height: 100%;
- }
- }
-
.unseen-count {
display: inline-block;
background-color: $fallback--cRed;
background-color: var(--cRed, $fallback--cRed);
text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.5);
- min-width: 1.3em;
- border-radius: 1.3em;
- margin: 0 0.2em 0 -0.4em;
+ border-radius: 99px;
+ min-width: 22px;
+ max-width: 22px;
+ min-height: 22px;
+ max-height: 22px;
color: white;
- font-size: 0.9em;
+ font-size: 15px;
+ line-height: 22px;
text-align: center;
- line-height: 1.3em;
+ vertical-align: middle
}
.loadmore-error {
- position: absolute;
- right: 0.6em;
- font-size: 14px;
min-width: 6em;
- font-family: sans-serif;
text-align: center;
padding: 0 0.25em 0 0.25em;
margin: 0;
@@ -73,7 +40,8 @@
box-sizing: border-box;
display: flex;
border-bottom: 1px solid;
- border-bottom-color: inherit;
+ border-color: $fallback--border;
+ border-color: var(--border, $fallback--border);
.broken-favorite {
border-radius: $fallback--tooltipRadius;
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
@@ -2,8 +2,10 @@
<div class="notifications">
<div class="panel panel-default">
<div class="panel-heading">
- <span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span>
- <div class="title"> {{$t('notifications.notifications')}}</div>
+ <div class="title">
+ {{$t('notifications.notifications')}}
+ <span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span>
+ </div>
<div @click.prevent class="loadmore-error alert error" v-if="error">
{{$t('timeline.error_fetching')}}
</div>
diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue
@@ -1,7 +1,12 @@
<template>
- <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 v-if="loggedIn">
+ <template v-if="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>
+ </template>
+ <template v-else>
+ <i :class='classes' class='icon-lock' :title="$t('timeline.no_retweet_hint')"></i>
+ </template>
</div>
<div v-else-if="!loggedIn">
<i :class='classes' class='icon-retweet'></i>
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
@@ -1,4 +1,5 @@
/* eslint-env browser */
+import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
import StyleSwitcher from '../style_switcher/style_switcher.vue'
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
import { filter, trim } from 'lodash'
@@ -9,6 +10,7 @@ const settings = {
hideAttachmentsLocal: this.$store.state.config.hideAttachments,
hideAttachmentsInConvLocal: this.$store.state.config.hideAttachmentsInConv,
hideNsfwLocal: this.$store.state.config.hideNsfw,
+ notificationVisibilityLocal: this.$store.state.config.notificationVisibility,
replyVisibilityLocal: this.$store.state.config.replyVisibility,
loopVideoLocal: this.$store.state.config.loopVideo,
loopVideoSilentOnlyLocal: this.$store.state.config.loopVideoSilentOnly,
@@ -29,6 +31,7 @@ const settings = {
}
},
components: {
+ TabSwitcher,
StyleSwitcher,
InterfaceLanguageSwitcher
},
@@ -47,6 +50,18 @@ const settings = {
hideNsfwLocal (value) {
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
},
+ 'notificationVisibilityLocal.likes' (value) {
+ this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
+ },
+ 'notificationVisibilityLocal.follows' (value) {
+ this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
+ },
+ 'notificationVisibilityLocal.repeats' (value) {
+ this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
+ },
+ 'notificationVisibilityLocal.mentions' (value) {
+ this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
+ },
replyVisibilityLocal (value) {
this.$store.dispatch('setOption', { name: 'replyVisibility', value })
},
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
@@ -4,90 +4,132 @@
{{$t('settings.settings')}}
</div>
<div class="panel-body">
- <div class="setting-item">
- <h2>{{$t('settings.theme')}}</h2>
- <style-switcher></style-switcher>
- </div>
- <div class="setting-item">
- <h2>{{$t('settings.filtering')}}</h2>
- <p>{{$t('settings.filtering_explanation')}}</p>
- <textarea id="muteWords" v-model="muteWordsString"></textarea>
- </div>
- <div class="setting-item">
- <h2>{{$t('nav.timeline')}}</h2>
- <ul class="setting-list">
- <li>
- <input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
- <label for="collapseMessageWithSubject">{{$t('settings.collapse_subject')}}</label>
- </li>
- <li>
- <input type="checkbox" id="streaming" v-model="streamingLocal">
- <label for="streaming">{{$t('settings.streaming')}}</label>
- <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
+ <tab-switcher>
+ <div :label="$t('settings.general')" >
+ <div class="setting-item">
+ <h2>{{ $t('settings.interfaceLanguage') }}</h2>
+ <interface-language-switcher />
+ </div>
+ <div class="setting-item">
+ <h2>{{$t('nav.timeline')}}</h2>
+ <ul class="setting-list">
<li>
- <input :disabled="!streamingLocal" type="checkbox" id="pauseOnUnfocused" v-model="pauseOnUnfocusedLocal">
- <label for="pauseOnUnfocused">{{$t('settings.pause_on_unfocused')}}</label>
+ <input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
+ <label for="collapseMessageWithSubject">{{$t('settings.collapse_subject')}}</label>
+ </li>
+ <li>
+ <input type="checkbox" id="streaming" v-model="streamingLocal">
+ <label for="streaming">{{$t('settings.streaming')}}</label>
+ <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
+ <li>
+ <input :disabled="!streamingLocal" type="checkbox" id="pauseOnUnfocused" v-model="pauseOnUnfocusedLocal">
+ <label for="pauseOnUnfocused">{{$t('settings.pause_on_unfocused')}}</label>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <input type="checkbox" id="autoload" v-model="autoLoadLocal">
+ <label for="autoload">{{$t('settings.autoload')}}</label>
+ </li>
+ <li>
+ <input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
+ <label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
</li>
</ul>
- </li>
- <li>
- <input type="checkbox" id="autoload" v-model="autoLoadLocal">
- <label for="autoload">{{$t('settings.autoload')}}</label>
- </li>
- <li>
- <input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
- <label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
- </li>
- <li>
- <label for="replyVisibility" class="select">
- <select id="replyVisibility" v-model="replyVisibilityLocal">
- <option value="all" selected>{{$t('settings.reply_visibility_all')}}</option>
- <option value="following">{{$t('settings.reply_visibility_following')}}</option>
- <option value="self">{{$t('settings.reply_visibility_self')}}</option>
- </select>
- <i class="icon-down-open"/>
- </label>
- </li>
- </ul>
- </div>
- <div class="setting-item">
- <h2>{{$t('settings.attachments')}}</h2>
- <ul class="setting-list">
- <li>
- <input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal">
- <label for="hideAttachments">{{$t('settings.hide_attachments_in_tl')}}</label>
- </li>
- <li>
- <input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
- <label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
- </li>
- <li>
- <input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
- <label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
- </li>
- <li>
- <input type="checkbox" id="stopGifs" v-model="stopGifs">
- <label for="stopGifs">{{$t('settings.stop_gifs')}}</label>
- </li>
- <li>
- <input type="checkbox" id="loopVideo" v-model="loopVideoLocal">
- <label for="loopVideo">{{$t('settings.loop_video')}}</label>
- <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
+ </div>
+ <div class="setting-item">
+ <h2>{{$t('settings.attachments')}}</h2>
+ <ul class="setting-list">
+ <li>
+ <input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal">
+ <label for="hideAttachments">{{$t('settings.hide_attachments_in_tl')}}</label>
+ </li>
<li>
- <input :disabled="!loopVideoLocal || !loopSilentAvailable" type="checkbox" id="loopVideoSilentOnly" v-model="loopVideoSilentOnlyLocal">
- <label for="loopVideoSilentOnly">{{$t('settings.loop_video_silent_only')}}</label>
- <div v-if="!loopSilentAvailable" class="unavailable">
- <i class="icon-globe"/>! {{$t('settings.limited_availability')}}
- </div>
+ <input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
+ <label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
+ </li>
+ <li>
+ <input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
+ <label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
+ </li>
+ <li>
+ <input type="checkbox" id="stopGifs" v-model="stopGifs">
+ <label for="stopGifs">{{$t('settings.stop_gifs')}}</label>
+ </li>
+ <li>
+ <input type="checkbox" id="loopVideo" v-model="loopVideoLocal">
+ <label for="loopVideo">{{$t('settings.loop_video')}}</label>
+ <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
+ <li>
+ <input :disabled="!loopVideoLocal || !loopSilentAvailable" type="checkbox" id="loopVideoSilentOnly" v-model="loopVideoSilentOnlyLocal">
+ <label for="loopVideoSilentOnly">{{$t('settings.loop_video_silent_only')}}</label>
+ <div v-if="!loopSilentAvailable" class="unavailable">
+ <i class="icon-globe"/>! {{$t('settings.limited_availability')}}
+ </div>
+ </li>
+ </ul>
</li>
</ul>
- </li>
- </ul>
- </div>
- <div class="setting-item">
- <h2>{{ $t('settings.interfaceLanguage') }}</h2>
- <interface-language-switcher />
- </div>
+ </div>
+ </div>
+
+ <div :label="$t('settings.theme')" >
+ <div class="setting-item">
+ <style-switcher></style-switcher>
+ </div>
+ </div>
+
+ <div :label="$t('settings.filtering')" >
+ <div class="setting-item">
+ <div class="select-multiple">
+ <span class="label">{{$t('settings.notification_visibility')}}</span>
+ <ul class="option-list">
+ <li>
+ <input type="checkbox" id="notification-visibility-likes" v-model="notificationVisibilityLocal.likes">
+ <label for="notification-visibility-likes">
+ {{$t('settings.notification_visibility_likes')}}
+ </label>
+ </li>
+ <li>
+ <input type="checkbox" id="notification-visibility-repeats" v-model="notificationVisibilityLocal.repeats">
+ <label for="notification-visibility-repeats">
+ {{$t('settings.notification_visibility_repeats')}}
+ </label>
+ </li>
+ <li>
+ <input type="checkbox" id="notification-visibility-follows" v-model="notificationVisibilityLocal.follows">
+ <label for="notification-visibility-follows">
+ {{$t('settings.notification_visibility_follows')}}
+ </label>
+ </li>
+ <li>
+ <input type="checkbox" id="notification-visibility-mentions" v-model="notificationVisibilityLocal.mentions">
+ <label for="notification-visibility-mentions">
+ {{$t('settings.notification_visibility_mentions')}}
+ </label>
+ </li>
+ </ul>
+ </label>
+ </div>
+ <div>
+ {{$t('settings.replies_in_timeline')}}
+ <label for="replyVisibility" class="select">
+ <select id="replyVisibility" v-model="replyVisibilityLocal">
+ <option value="all" selected>{{$t('settings.reply_visibility_all')}}</option>
+ <option value="following">{{$t('settings.reply_visibility_following')}}</option>
+ <option value="self">{{$t('settings.reply_visibility_self')}}</option>
+ </select>
+ <i class="icon-down-open"/>
+ </label>
+ </div>
+ </div>
+ <div class="setting-item">
+ <p>{{$t('settings.filtering_explanation')}}</p>
+ <textarea id="muteWords" v-model="muteWordsString"></textarea>
+ </div>
+ </div>
+
+ </tab-switcher>
</div>
</div>
</template>
@@ -103,6 +145,23 @@
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
+ > div {
+ margin-bottom: .5em;
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ &:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+ margin-bottom: 1em;
+ }
+
+ select {
+ min-width: 10em;
+ }
+
textarea {
width: 100%;
@@ -130,12 +189,24 @@
}
.btn {
- margin-top: 1em;
min-height: 28px;
+ }
+
+ .submit {
+ margin-top: 1em;
+ min-height: 30px;
width: 10em;
}
}
-.setting-list {
+.select-multiple {
+ display: flex;
+ .option-list {
+ margin: 0;
+ padding-left: .5em;
+ }
+}
+.setting-list,
+.option-list{
list-style-type: none;
padding-left: 2em;
li {
diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js
@@ -18,7 +18,11 @@ const StillImage = {
onLoad () {
const canvas = this.$refs.canvas
if (!canvas) return
- canvas.getContext('2d').drawImage(this.$refs.src, 1, 1, canvas.width, canvas.height)
+ const width = this.$refs.src.naturalWidth
+ const height = this.$refs.src.naturalHeight
+ canvas.width = width
+ canvas.height = height
+ canvas.getContext('2d').drawImage(this.$refs.src, 0, 0, width, height)
}
}
}
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
@@ -60,6 +60,7 @@
right: 0;
width: 100%;
height: 100%;
+ object-fit: contain;
}
}
</style>
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
@@ -1,102 +1,30 @@
<template>
- <div>
- <div>{{$t('settings.presets')}}
+<div>
+ <div class="presets-container">
+ <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="{
- backgroundColor: style[1],
- color: style[3]
- }">{{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>
</div>
- <div>
+ <div class="import-export">
<button class="btn" @click="exportCurrentTheme">{{ $t('settings.export_theme') }}</button>
<button class="btn" @click="importTheme">{{ $t('settings.import_theme') }}</button>
<p v-if="invalidThemeImported" class="import-warning">{{ $t('settings.invalid_theme_imported') }}</p>
</div>
- <div class="color-container">
- <p>{{$t('settings.theme_help')}}</p>
- <div class="color-item">
- <label for="bgcolor" class="theme-color-lb">{{$t('settings.background')}}</label>
- <input id="bgcolor" class="theme-color-cl" type="color" v-model="bgColorLocal">
- <input id="bgcolor-t" class="theme-color-in" type="text" v-model="bgColorLocal">
- </div>
- <div class="color-item">
- <label for="fgcolor" class="theme-color-lb">{{$t('settings.foreground')}}</label>
- <input id="fgcolor" class="theme-color-cl" type="color" v-model="btnColorLocal">
- <input id="fgcolor-t" class="theme-color-in" type="text" v-model="btnColorLocal">
- </div>
- <div class="color-item">
- <label for="textcolor" class="theme-color-lb">{{$t('settings.text')}}</label>
- <input id="textcolor" class="theme-color-cl" type="color" v-model="textColorLocal">
- <input id="textcolor-t" class="theme-color-in" type="text" v-model="textColorLocal">
- </div>
- <div class="color-item">
- <label for="linkcolor" class="theme-color-lb">{{$t('settings.links')}}</label>
- <input id="linkcolor" class="theme-color-cl" type="color" v-model="linkColorLocal">
- <input id="linkcolor-t" class="theme-color-in" type="text" v-model="linkColorLocal">
- </div>
- <div class="color-item">
- <label for="redcolor" class="theme-color-lb">{{$t('settings.cRed')}}</label>
- <input id="redcolor" class="theme-color-cl" type="color" v-model="redColorLocal">
- <input id="redcolor-t" class="theme-color-in" type="text" v-model="redColorLocal">
- </div>
- <div class="color-item">
- <label for="bluecolor" class="theme-color-lb">{{$t('settings.cBlue')}}</label>
- <input id="bluecolor" class="theme-color-cl" type="color" v-model="blueColorLocal">
- <input id="bluecolor-t" class="theme-color-in" type="text" v-model="blueColorLocal">
- </div>
- <div class="color-item">
- <label for="greencolor" class="theme-color-lb">{{$t('settings.cGreen')}}</label>
- <input id="greencolor" class="theme-color-cl" type="color" v-model="greenColorLocal">
- <input id="greencolor-t" class="theme-color-in" type="green" v-model="greenColorLocal">
- </div>
- <div class="color-item">
- <label for="orangecolor" class="theme-color-lb">{{$t('settings.cOrange')}}</label>
- <input id="orangecolor" class="theme-color-cl" type="color" v-model="orangeColorLocal">
- <input id="orangecolor-t" class="theme-color-in" type="text" v-model="orangeColorLocal">
- </div>
- </div>
- <div class="radius-container">
- <p>{{$t('settings.radii_help')}}</p>
- <div class="radius-item">
- <label for="btnradius" class="theme-radius-lb">{{$t('settings.btnRadius')}}</label>
- <input id="btnradius" class="theme-radius-rn" type="range" v-model="btnRadiusLocal" max="16">
- <input id="btnradius-t" class="theme-radius-in" type="text" v-model="btnRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="inputradius" class="theme-radius-lb">{{$t('settings.inputRadius')}}</label>
- <input id="inputradius" class="theme-radius-rn" type="range" v-model="inputRadiusLocal" max="16">
- <input id="inputradius-t" class="theme-radius-in" type="text" v-model="inputRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="panelradius" class="theme-radius-lb">{{$t('settings.panelRadius')}}</label>
- <input id="panelradius" class="theme-radius-rn" type="range" v-model="panelRadiusLocal" max="50">
- <input id="panelradius-t" class="theme-radius-in" type="text" v-model="panelRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="avatarradius" class="theme-radius-lb">{{$t('settings.avatarRadius')}}</label>
- <input id="avatarradius" class="theme-radius-rn" type="range" v-model="avatarRadiusLocal" max="28">
- <input id="avatarradius-t" class="theme-radius-in" type="green" v-model="avatarRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="avataraltradius" class="theme-radius-lb">{{$t('settings.avatarAltRadius')}}</label>
- <input id="avataraltradius" class="theme-radius-rn" type="range" v-model="avatarAltRadiusLocal" max="28">
- <input id="avataraltradius-t" class="theme-radius-in" type="text" v-model="avatarAltRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="attachmentradius" class="theme-radius-lb">{{$t('settings.attachmentRadius')}}</label>
- <input id="attachmentrradius" class="theme-radius-rn" type="range" v-model="attachmentRadiusLocal" max="50">
- <input id="attachmentradius-t" class="theme-radius-in" type="text" v-model="attachmentRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="tooltipradius" class="theme-radius-lb">{{$t('settings.tooltipRadius')}}</label>
- <input id="tooltipradius" class="theme-radius-rn" type="range" v-model="tooltipRadiusLocal" max="20">
- <input id="tooltipradius-t" class="theme-radius-in" type="text" v-model="tooltipRadiusLocal">
- </div>
- </div>
+ </div>
+
+ <div class="preview-container">
<div :style="{
'--btnRadius': btnRadiusLocal + 'px',
'--inputRadius': inputRadiusLocal + 'px',
@@ -127,8 +55,95 @@
</div>
</div>
</div>
- <button class="btn" @click="setCustomTheme">{{$t('general.apply')}}</button>
</div>
+
+ <div class="color-container">
+ <p>{{$t('settings.theme_help')}}</p>
+ <div class="color-item">
+ <label for="bgcolor" class="theme-color-lb">{{$t('settings.background')}}</label>
+ <input id="bgcolor" class="theme-color-cl" type="color" v-model="bgColorLocal">
+ <input id="bgcolor-t" class="theme-color-in" type="text" v-model="bgColorLocal">
+ </div>
+ <div class="color-item">
+ <label for="fgcolor" class="theme-color-lb">{{$t('settings.foreground')}}</label>
+ <input id="fgcolor" class="theme-color-cl" type="color" v-model="btnColorLocal">
+ <input id="fgcolor-t" class="theme-color-in" type="text" v-model="btnColorLocal">
+ </div>
+ <div class="color-item">
+ <label for="textcolor" class="theme-color-lb">{{$t('settings.text')}}</label>
+ <input id="textcolor" class="theme-color-cl" type="color" v-model="textColorLocal">
+ <input id="textcolor-t" class="theme-color-in" type="text" v-model="textColorLocal">
+ </div>
+ <div class="color-item">
+ <label for="linkcolor" class="theme-color-lb">{{$t('settings.links')}}</label>
+ <input id="linkcolor" class="theme-color-cl" type="color" v-model="linkColorLocal">
+ <input id="linkcolor-t" class="theme-color-in" type="text" v-model="linkColorLocal">
+ </div>
+ <div class="color-item">
+ <label for="redcolor" class="theme-color-lb">{{$t('settings.cRed')}}</label>
+ <input id="redcolor" class="theme-color-cl" type="color" v-model="redColorLocal">
+ <input id="redcolor-t" class="theme-color-in" type="text" v-model="redColorLocal">
+ </div>
+ <div class="color-item">
+ <label for="bluecolor" class="theme-color-lb">{{$t('settings.cBlue')}}</label>
+ <input id="bluecolor" class="theme-color-cl" type="color" v-model="blueColorLocal">
+ <input id="bluecolor-t" class="theme-color-in" type="text" v-model="blueColorLocal">
+ </div>
+ <div class="color-item">
+ <label for="greencolor" class="theme-color-lb">{{$t('settings.cGreen')}}</label>
+ <input id="greencolor" class="theme-color-cl" type="color" v-model="greenColorLocal">
+ <input id="greencolor-t" class="theme-color-in" type="green" v-model="greenColorLocal">
+ </div>
+ <div class="color-item">
+ <label for="orangecolor" class="theme-color-lb">{{$t('settings.cOrange')}}</label>
+ <input id="orangecolor" class="theme-color-cl" type="color" v-model="orangeColorLocal">
+ <input id="orangecolor-t" class="theme-color-in" type="text" v-model="orangeColorLocal">
+ </div>
+ </div>
+
+ <div class="radius-container">
+ <p>{{$t('settings.radii_help')}}</p>
+ <div class="radius-item">
+ <label for="btnradius" class="theme-radius-lb">{{$t('settings.btnRadius')}}</label>
+ <input id="btnradius" class="theme-radius-rn" type="range" v-model="btnRadiusLocal" max="16">
+ <input id="btnradius-t" class="theme-radius-in" type="text" v-model="btnRadiusLocal">
+ </div>
+ <div class="radius-item">
+ <label for="inputradius" class="theme-radius-lb">{{$t('settings.inputRadius')}}</label>
+ <input id="inputradius" class="theme-radius-rn" type="range" v-model="inputRadiusLocal" max="16">
+ <input id="inputradius-t" class="theme-radius-in" type="text" v-model="inputRadiusLocal">
+ </div>
+ <div class="radius-item">
+ <label for="panelradius" class="theme-radius-lb">{{$t('settings.panelRadius')}}</label>
+ <input id="panelradius" class="theme-radius-rn" type="range" v-model="panelRadiusLocal" max="50">
+ <input id="panelradius-t" class="theme-radius-in" type="text" v-model="panelRadiusLocal">
+ </div>
+ <div class="radius-item">
+ <label for="avatarradius" class="theme-radius-lb">{{$t('settings.avatarRadius')}}</label>
+ <input id="avatarradius" class="theme-radius-rn" type="range" v-model="avatarRadiusLocal" max="28">
+ <input id="avatarradius-t" class="theme-radius-in" type="green" v-model="avatarRadiusLocal">
+ </div>
+ <div class="radius-item">
+ <label for="avataraltradius" class="theme-radius-lb">{{$t('settings.avatarAltRadius')}}</label>
+ <input id="avataraltradius" class="theme-radius-rn" type="range" v-model="avatarAltRadiusLocal" max="28">
+ <input id="avataraltradius-t" class="theme-radius-in" type="text" v-model="avatarAltRadiusLocal">
+ </div>
+ <div class="radius-item">
+ <label for="attachmentradius" class="theme-radius-lb">{{$t('settings.attachmentRadius')}}</label>
+ <input id="attachmentrradius" class="theme-radius-rn" type="range" v-model="attachmentRadiusLocal" max="50">
+ <input id="attachmentradius-t" class="theme-radius-in" type="text" v-model="attachmentRadiusLocal">
+ </div>
+ <div class="radius-item">
+ <label for="tooltipradius" class="theme-radius-lb">{{$t('settings.tooltipRadius')}}</label>
+ <input id="tooltipradius" class="theme-radius-rn" type="range" v-model="tooltipRadiusLocal" max="20">
+ <input id="tooltipradius-t" class="theme-radius-in" type="text" v-model="tooltipRadiusLocal">
+ </div>
+ </div>
+
+ <div class="apply-container">
+ <button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button>
+ </div>
+</div>
</template>
<script src="./style_switcher.js"></script>
@@ -144,15 +159,19 @@
color: var(--cRed, $fallback--cRed);
}
+.apply-container,
.radius-container,
-.color-container {
+.color-container,
+.presets-container {
display: flex;
p {
+ flex: 2 0 100%;
margin-top: 2em;
margin-bottom: .5em;
}
}
+
.radius-container {
flex-direction: column;
}
@@ -162,6 +181,36 @@
justify-content: space-between;
}
+.presets-container {
+ justify-content: center;
+ .import-export {
+ display: flex;
+
+ .btn {
+ margin-left: .5em;
+ }
+ }
+}
+
+.preview-container {
+ border-top: 1px dashed;
+ border-bottom: 1px dashed;
+ border-color: $fallback--border;
+ border-color: var(--border, $fallback--border);
+ margin: 1em -1em 0;
+ padding: 1em;
+
+ .btn {
+ margin-top: 1em;
+ min-height: 30px;
+ width: 10em;
+ }
+}
+
+.apply-container {
+ justify-content: center;
+}
+
.radius-item,
.color-item {
min-width: 20em;
@@ -229,6 +278,7 @@
flex: 0;
min-width: 2em;
cursor: pointer;
+ max-height: 29px;
}
.theme-preview-content {
diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx
@@ -0,0 +1,44 @@
+import Vue from 'vue'
+
+import './tab_switcher.scss'
+
+export default Vue.component('tab-switcher', {
+ name: 'TabSwitcher',
+ data () {
+ return {
+ active: 0
+ }
+ },
+ methods: {
+ activateTab(index) {
+ return () => this.active = index;
+ }
+ },
+ render(h) {
+ const tabs = this.$slots.default
+ .filter(slot => slot.data)
+ .map((slot, index) => {
+ const classes = ['tab']
+
+ if (index === this.active) {
+ classes.push('active')
+ }
+ return (<button onClick={this.activateTab(index)} class={ classes.join(' ') }>{slot.data.attrs.label}</button>)
+ });
+ const contents = (
+ <div>
+ {this.$slots.default.filter(slot => slot.data)[this.active]}
+ </div>
+ );
+ return (
+ <div class="tab-switcher">
+ <div class="tabs">
+ {tabs}
+ </div>
+ <div class="contents">
+ {contents}
+ </div>
+ </div>
+ )
+ }
+})
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
@@ -0,0 +1,43 @@
+@import '../../_variables.scss';
+
+.tab-switcher {
+ .tabs {
+ display: flex;
+ position: relative;
+ justify-content: center;
+ width: 100%;
+ overflow: hidden;
+ padding-top: 5px;
+
+ &::after, &::before {
+ display: block;
+ content: '';
+ flex: 1 1 auto;
+ }
+
+ .tab, &::after, &::before {
+ border-bottom: 1px solid;
+ border-bottom-color: $fallback--btn;
+ border-bottom-color: var(--btn, $fallback--btn);
+ }
+
+ .tab {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ padding: .3em 1em;
+
+ &:not(.active) {
+ border-bottom: 1px solid;
+ border-bottom-color: $fallback--btn;
+ border-bottom-color: var(--btn, $fallback--btn);
+ z-index: 4;
+ }
+
+ &.active {
+ background: transparent;
+ border-bottom: none;
+ z-index: 5;
+ }
+ }
+ }
+}
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
@@ -57,36 +57,8 @@
@import '../../_variables.scss';
.timeline {
- .timeline-heading {
- position: relative;
- display: flex;
- }
-
- .title {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 70%;
- }
-
- .loadmore-button {
- position: absolute;
- right: 0.6em;
- font-size: 14px;
-
- min-width: 6em;
- height: 1.8em;
- line-height: 100%;
- }
-
.loadmore-text {
- position: absolute;
- right: 0.6em;
font-size: 14px;
- min-width: 6em;
- font-family: sans-serif;
- text-align: center;
- padding: 0 0.5em 0 0.5em;
opacity: 0.8;
background-color: transparent;
color: $fallback--faint;
@@ -94,11 +66,8 @@
}
.loadmore-error {
- position: absolute;
- right: 0.6em;
font-size: 14px;
min-width: 6em;
- font-family: sans-serif;
text-align: center;
padding: 0 0.25em 0 0.25em;
margin: 0;
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
@@ -17,6 +17,8 @@
padding-bottom: 10px;
.panel-heading {
background: transparent;
+ flex-direction: column;
+ align-items: stretch;
}
}
</style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
@@ -1,3 +1,4 @@
+import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
import StyleSwitcher from '../style_switcher/style_switcher.vue'
const UserSettings = {
@@ -23,7 +24,8 @@ const UserSettings = {
}
},
components: {
- StyleSwitcher
+ StyleSwitcher,
+ TabSwitcher
},
computed: {
user () {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
@@ -4,126 +4,131 @@
{{$t('settings.user_settings')}}
</div>
<div class="panel-body profile-edit">
- <div class="tab-switcher">
- <button class="btn btn-default" @click="activateTab('profile')">{{$t('settings.profile_tab')}}</button>
- <button class="btn btn-default" @click="activateTab('security')">{{$t('settings.security_tab')}}</button>
- <button class="btn btn-default" @click="activateTab('data_import_export')" v-if="pleromaBackend">{{$t('settings.data_import_export_tab')}}</button>
- </div>
- <div class="setting-item" v-if="activeTab == 'profile'">
- <h2>{{$t('settings.name_bio')}}</h2>
- <p>{{$t('settings.name')}}</p>
- <input class='name-changer' id='username' v-model="newname"></input>
- <p>{{$t('settings.bio')}}</p>
- <textarea class="bio" v-model="newbio"></textarea>
- <p>
- <input type="checkbox" v-model="newlocked" id="account-locked">
- <label for="account-locked">{{$t('settings.lock_account_description')}}</label>
- </p>
- <div v-if="scopeOptionsEnabled">
- <label for="default-vis">{{$t('settings.default_vis')}}</label>
- <div id="default-vis" class="visibility-tray">
- <i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct"></i>
- <i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private"></i>
- <i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted"></i>
- <i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public"></i>
+ <tab-switcher>
+ <div :label="$t('settings.profile_tab')">
+ <div class="setting-item" >
+ <h2>{{$t('settings.name_bio')}}</h2>
+ <p>{{$t('settings.name')}}</p>
+ <input class='name-changer' id='username' v-model="newname"></input>
+ <p>{{$t('settings.bio')}}</p>
+ <textarea class="bio" v-model="newbio"></textarea>
+ <p>
+ <input type="checkbox" v-model="newlocked" id="account-locked">
+ <label for="account-locked">{{$t('settings.lock_account_description')}}</label>
+ </p>
+ <div v-if="scopeOptionsEnabled">
+ <label for="default-vis">{{$t('settings.default_vis')}}</label>
+ <div id="default-vis" class="visibility-tray">
+ <i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct"></i>
+ <i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private"></i>
+ <i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted"></i>
+ <i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public"></i>
+ </div>
+ </div>
+ <button :disabled='newname.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button>
+ </div>
+ <div class="setting-item">
+ <h2>{{$t('settings.avatar')}}</h2>
+ <p>{{$t('settings.current_avatar')}}</p>
+ <img :src="user.profile_image_url_original" class="old-avatar"></img>
+ <p>{{$t('settings.set_new_avatar')}}</p>
+ <img class="new-avatar" v-bind:src="previews[0]" v-if="previews[0]">
+ </img>
+ <div>
+ <input type="file" @change="uploadFile(0, $event)" ></input>
+ </div>
+ <i class="icon-spin4 animate-spin" v-if="uploading[0]"></i>
+ <button class="btn btn-default" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button>
+ </div>
+ <div class="setting-item">
+ <h2>{{$t('settings.profile_banner')}}</h2>
+ <p>{{$t('settings.current_profile_banner')}}</p>
+ <img :src="user.cover_photo" class="banner"></img>
+ <p>{{$t('settings.set_new_profile_banner')}}</p>
+ <img class="banner" v-bind:src="previews[1]" v-if="previews[1]">
+ </img>
+ <div>
+ <input type="file" @change="uploadFile(1, $event)" ></input>
+ </div>
+ <i class=" icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
+ <button class="btn btn-default" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button>
+ </div>
+ <div class="setting-item">
+ <h2>{{$t('settings.profile_background')}}</h2>
+ <p>{{$t('settings.set_new_profile_background')}}</p>
+ <img class="bg" v-bind:src="previews[2]" v-if="previews[2]">
+ </img>
+ <div>
+ <input type="file" @change="uploadFile(2, $event)" ></input>
+ </div>
+ <i class=" icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
+ <button class="btn btn-default" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button>
</div>
</div>
- <button :disabled='newname.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button>
- </div>
- <div class="setting-item" v-if="activeTab == 'profile'">
- <h2>{{$t('settings.avatar')}}</h2>
- <p>{{$t('settings.current_avatar')}}</p>
- <img :src="user.profile_image_url_original" class="old-avatar"></img>
- <p>{{$t('settings.set_new_avatar')}}</p>
- <img class="new-avatar" v-bind:src="previews[0]" v-if="previews[0]">
- </img>
- <div>
- <input type="file" @change="uploadFile(0, $event)" ></input>
- </div>
- <i class="icon-spin4 animate-spin" v-if="uploading[0]"></i>
- <button class="btn btn-default" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button>
- </div>
- <div class="setting-item" v-if="activeTab == 'profile'">
- <h2>{{$t('settings.profile_banner')}}</h2>
- <p>{{$t('settings.current_profile_banner')}}</p>
- <img :src="user.cover_photo" class="banner"></img>
- <p>{{$t('settings.set_new_profile_banner')}}</p>
- <img class="banner" v-bind:src="previews[1]" v-if="previews[1]">
- </img>
- <div>
- <input type="file" @change="uploadFile(1, $event)" ></input>
- </div>
- <i class=" icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
- <button class="btn btn-default" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button>
- </div>
- <div class="setting-item" v-if="activeTab == 'profile'">
- <h2>{{$t('settings.profile_background')}}</h2>
- <p>{{$t('settings.set_new_profile_background')}}</p>
- <img class="bg" v-bind:src="previews[2]" v-if="previews[2]">
- </img>
- <div>
- <input type="file" @change="uploadFile(2, $event)" ></input>
- </div>
- <i class=" icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
- <button class="btn btn-default" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button>
- </div>
- <div class="setting-item" v-if="activeTab == 'security'">
- <h2>{{$t('settings.change_password')}}</h2>
- <div>
- <p>{{$t('settings.current_password')}}</p>
- <input type="password" v-model="changePasswordInputs[0]">
- </div>
- <div>
- <p>{{$t('settings.new_password')}}</p>
- <input type="password" v-model="changePasswordInputs[1]">
- </div>
- <div>
- <p>{{$t('settings.confirm_new_password')}}</p>
- <input type="password" v-model="changePasswordInputs[2]">
- </div>
- <button class="btn btn-default" @click="changePassword">{{$t('general.submit')}}</button>
- <p v-if="changedPassword">{{$t('settings.changed_password')}}</p>
- <p v-else-if="changePasswordError !== false">{{$t('settings.change_password_error')}}</p>
- <p v-if="changePasswordError">{{changePasswordError}}</p>
- </div>
- <div class="setting-item" v-if="pleromaBackend && activeTab == 'data_import_export'">
- <h2>{{$t('settings.follow_import')}}</h2>
- <p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
- <form v-model="followImportForm">
- <input type="file" ref="followlist" v-on:change="followListChange"></input>
- </form>
- <i class=" icon-spin4 animate-spin uploading" v-if="uploading[3]"></i>
- <button class="btn btn-default" v-else @click="importFollows">{{$t('general.submit')}}</button>
- <div v-if="followsImported">
- <i class="icon-cross" @click="dismissImported"></i>
- <p>{{$t('settings.follows_imported')}}</p>
- </div>
- <div v-else-if="followImportError">
- <i class="icon-cross" @click="dismissImported"></i>
- <p>{{$t('settings.follow_import_error')}}</p>
+
+ <div :label="$t('settings.security_tab')">
+ <div class="setting-item">
+ <h2>{{$t('settings.change_password')}}</h2>
+ <div>
+ <p>{{$t('settings.current_password')}}</p>
+ <input type="password" v-model="changePasswordInputs[0]">
+ </div>
+ <div>
+ <p>{{$t('settings.new_password')}}</p>
+ <input type="password" v-model="changePasswordInputs[1]">
+ </div>
+ <div>
+ <p>{{$t('settings.confirm_new_password')}}</p>
+ <input type="password" v-model="changePasswordInputs[2]">
+ </div>
+ <button class="btn btn-default" @click="changePassword">{{$t('general.submit')}}</button>
+ <p v-if="changedPassword">{{$t('settings.changed_password')}}</p>
+ <p v-else-if="changePasswordError !== false">{{$t('settings.change_password_error')}}</p>
+ <p v-if="changePasswordError">{{changePasswordError}}</p>
+ </div>
+
+ <div class="setting-item">
+ <h2>{{$t('settings.delete_account')}}</h2>
+ <p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
+ <div v-if="deletingAccount">
+ <p>{{$t('settings.delete_account_instructions')}}</p>
+ <p>{{$t('login.password')}}</p>
+ <input type="password" v-model="deleteAccountConfirmPasswordInput">
+ <button class="btn btn-default" @click="deleteAccount">{{$t('settings.delete_account')}}</button>
+ </div>
+ <p v-if="deleteAccountError !== false">{{$t('settings.delete_account_error')}}</p>
+ <p v-if="deleteAccountError">{{deleteAccountError}}</p>
+ <button class="btn btn-default" v-if="!deletingAccount" @click="confirmDelete">{{$t('general.submit')}}</button>
+ </div>
</div>
- </div>
- <div class="setting-item" v-if="enableFollowsExport && activeTab == 'data_import_export'">
- <h2>{{$t('settings.follow_export')}}</h2>
- <button class="btn btn-default" @click="exportFollows">{{$t('settings.follow_export_button')}}</button>
- </div>
- <div class="setting-item" v-else-if="activeTab == 'data_import_export'">
- <h2>{{$t('settings.follow_export_processing')}}</h2>
- </div>
- <hr>
- <div class="setting-item" v-if="activeTab == 'security'">
- <h2>{{$t('settings.delete_account')}}</h2>
- <p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
- <div v-if="deletingAccount">
- <p>{{$t('settings.delete_account_instructions')}}</p>
- <p>{{$t('login.password')}}</p>
- <input type="password" v-model="deleteAccountConfirmPasswordInput">
- <button class="btn btn-default" @click="deleteAccount">{{$t('settings.delete_account')}}</button>
+
+ <div :label="$t('settings.data_import_export_tab')" v-if="pleromaBackend">
+ <div class="setting-item">
+ <h2>{{$t('settings.follow_import')}}</h2>
+ <p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
+ <form v-model="followImportForm">
+ <input type="file" ref="followlist" v-on:change="followListChange"></input>
+ </form>
+ <i class=" icon-spin4 animate-spin uploading" v-if="uploading[3]"></i>
+ <button class="btn btn-default" v-else @click="importFollows">{{$t('general.submit')}}</button>
+ <div v-if="followsImported">
+ <i class="icon-cross" @click="dismissImported"></i>
+ <p>{{$t('settings.follows_imported')}}</p>
+ </div>
+ <div v-else-if="followImportError">
+ <i class="icon-cross" @click="dismissImported"></i>
+ <p>{{$t('settings.follow_import_error')}}</p>
+ </div>
+ </div>
+ <div class="setting-item" v-if="enableFollowsExport">
+ <h2>{{$t('settings.follow_export')}}</h2>
+ <button class="btn btn-default" @click="exportFollows">{{$t('settings.follow_export_button')}}</button>
+ </div>
+ <div class="setting-item" v-else>
+ <h2>{{$t('settings.follow_export_processing')}}</h2>
+ </div>
</div>
- <p v-if="deleteAccountError !== false">{{$t('settings.delete_account_error')}}</p>
- <p v-if="deleteAccountError">{{deleteAccountError}}</p>
- <button class="btn btn-default" v-if="!deletingAccount" @click="confirmDelete">{{$t('general.submit')}}</button>
- </div>
+ </tab-switcher>
</div>
</div>
</template>
@@ -151,13 +156,4 @@
margin: 0.25em;
}
}
-
-.tab-switcher {
- margin: 7px 7px;
- display: inline-block;
-
- button {
- height: 30px;
- }
-}
</style>
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
@@ -48,8 +48,8 @@ const de = {
settings: 'Einstellungen',
theme: 'Farbschema',
presets: 'Voreinstellungen',
- export_theme: 'Aktuelles Theme exportieren',
- import_theme: 'Gespeichertes Theme laden',
+ export_theme: 'Farbschema speichern',
+ import_theme: 'Farbschema laden',
invalid_theme_imported: 'Die ausgewählte Datei ist kein unterstütztes Pleroma-Theme. Keine Änderungen wurden vorgenommen.',
theme_help: 'Benutze HTML Farbcodes (#rrggbb) um dein Farbschema anzupassen',
radii_help: 'Kantenrundung (in Pixel) der Oberfläche anpassen',
@@ -273,9 +273,11 @@ const en = {
load_older: 'Load older statuses',
conversation: 'Conversation',
collapse: 'Collapse',
- repeated: 'repeated'
+ repeated: 'repeated',
+ no_retweet_hint: 'Post is marked as followers-only or direct and cannot be repeated'
},
settings: {
+ general: 'General',
user_settings: 'User Settings',
name_bio: 'Name & Bio',
name: 'Name',
@@ -291,8 +293,8 @@ const en = {
settings: 'Settings',
theme: 'Theme',
presets: 'Presets',
- export_theme: 'Export current theme',
- import_theme: 'Load saved theme',
+ export_theme: 'Save preset',
+ import_theme: 'Load preset',
theme_help: 'Use hex color codes (#rrggbb) to customize your color theme.',
invalid_theme_imported: 'The selected file is not a supported Pleroma theme. No changes to your theme were made.',
radii_help: 'Set up interface edge rounding (in pixels)',
@@ -325,9 +327,15 @@ const en = {
loop_video: 'Loop videos',
loop_video_silent_only: 'Loop only videos without sound (i.e. Mastodon\'s "gifs")',
reply_link_preview: 'Enable reply-link preview on mouse hover',
+ replies_in_timeline: 'Replies in timeline',
reply_visibility_all: 'Show all replies',
reply_visibility_following: 'Only show replies directed at me or users I\'m following',
reply_visibility_self: 'Only show replies directed at me',
+ notification_visibility: 'Types of notifications to show',
+ notification_visibility_likes: 'Likes',
+ notification_visibility_mentions: 'Mentions',
+ notification_visibility_repeats: 'Repeats',
+ notification_visibility_follows: 'Follows',
follow_import: 'Follow import',
import_followers_from_a_csv_file: 'Import follows from a csv file',
follows_imported: 'Follows imported! Processing them will take a while.',
@@ -1623,9 +1631,11 @@ const ru = {
load_older: 'Загрузить старые статусы',
conversation: 'Разговор',
collapse: 'Свернуть',
- repeated: 'повторил(а)'
+ repeated: 'повторил(а)',
+ no_retweet_hint: 'Пост помечен как "только для подписчиков" или "личное" и поэтому не может быть повторён'
},
settings: {
+ general: 'Общие',
user_settings: 'Настройки пользователя',
name_bio: 'Имя и описание',
name: 'Имя',
@@ -1640,11 +1650,11 @@ const ru = {
set_new_profile_background: 'Загрузить новый фон профиля',
settings: 'Настройки',
theme: 'Тема',
- export_theme: 'Экспортировать текущую тему',
- import_theme: 'Загрузить сохранённую тему',
+ export_theme: 'Сохранить Тему',
+ import_theme: 'Загрузить Тему',
presets: 'Пресеты',
theme_help: 'Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.',
- radii_help: 'Округление краёв элементов интерфейса (в пикселях)',
+ radii_help: 'Скругление углов элементов интерфейса (в пикселях)',
background: 'Фон',
foreground: 'Передний план',
text: 'Текст',
@@ -1673,6 +1683,15 @@ const ru = {
loop_video: 'Зациливать видео',
loop_video_silent_only: 'Зацикливать только беззвучные видео (т.е. "гифки" с Mastodon)',
reply_link_preview: 'Включить предварительный просмотр ответа при наведении мыши',
+ replies_in_timeline: 'Ответы в ленте',
+ reply_visibility_all: 'Показывать все ответы',
+ reply_visibility_following: 'Показывать только ответы мне и тех на кого я подписан',
+ reply_visibility_self: 'Показывать только ответы мне',
+ notification_visibility: 'Показывать уведомления',
+ notification_visibility_likes: 'Лайки',
+ notification_visibility_mentions: 'Упоминания',
+ notification_visibility_repeats: 'Повторы',
+ notification_visibility_follows: 'Подписки',
follow_import: 'Импортировать читаемых',
import_followers_from_a_csv_file: 'Импортировать читаемых из файла .csv',
follows_imported: 'Список читаемых импортирован. Обработка займёт некоторое время..',
@@ -1725,7 +1744,18 @@ const ru = {
},
post_status: {
posting: 'Отправляется',
- default: 'Что нового?'
+ content_warning: 'Тема (не обязательно)',
+ default: 'Что нового?',
+ account_not_locked_warning: 'Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков',
+ account_not_locked_warning_link: 'залочен',
+ direct_warning: 'Этот пост будет видет только упомянутым пользователям',
+ attachments_sensitive: 'Вложения содержат чувствительный контент',
+ scope: {
+ public: 'Публичный - этот пост виден всем',
+ unlisted: 'Непубличный - этот пост не виден на публичных лентах',
+ private: 'Для подписчиков - этот пост видят только подписчики',
+ direct: 'Личное - этот пост видят только те кто в нём упомянут'
+ }
},
finder: {
find_user: 'Найти пользователя',
diff --git a/src/main.js b/src/main.js
@@ -50,6 +50,7 @@ const persistedStateOptions = {
'config.hideAttachmentsInConv',
'config.hideNsfw',
'config.replyVisibility',
+ 'config.notificationVisibility',
'config.autoLoad',
'config.hoverPreview',
'config.streaming',
@@ -102,23 +103,29 @@ window.fetch('/api/statusnet/config.json')
.then((res) => res.json())
.then((data) => {
var staticConfig = data
-
- var theme = (apiConfig.theme || staticConfig.theme)
- var background = (apiConfig.background || staticConfig.background)
- var logo = (apiConfig.logo || staticConfig.logo)
- var redirectRootNoLogin = (apiConfig.redirectRootNoLogin || staticConfig.redirectRootNoLogin)
- var redirectRootLogin = (apiConfig.redirectRootLogin || staticConfig.redirectRootLogin)
- var chatDisabled = (apiConfig.chatDisabled || staticConfig.chatDisabled)
- var showWhoToFollowPanel = (apiConfig.showWhoToFollowPanel || staticConfig.showWhoToFollowPanel)
- var whoToFollowProvider = (apiConfig.whoToFollowProvider || staticConfig.whoToFollowProvider)
- var whoToFollowLink = (apiConfig.whoToFollowLink || staticConfig.whoToFollowLink)
- var showInstanceSpecificPanel = (apiConfig.showInstanceSpecificPanel || staticConfig.showInstanceSpecificPanel)
- var scopeOptionsEnabled = (apiConfig.scopeOptionsEnabled || staticConfig.scopeOptionsEnabled)
- var collapseMessageWithSubject = (apiConfig.collapseMessageWithSubject || staticConfig.collapseMessageWithSubject)
+ // This takes static config and overrides properties that are present in apiConfig
+ var config = Object.assign({}, staticConfig, apiConfig)
+
+ var theme = (config.theme)
+ var background = (config.background)
+ var logo = (config.logo)
+ var logoMask = (typeof config.logoMask === 'undefined' ? true : config.logoMask)
+ var logoMargin = (typeof config.logoMargin === 'undefined' ? 0 : config.logoMargin)
+ var redirectRootNoLogin = (config.redirectRootNoLogin)
+ var redirectRootLogin = (config.redirectRootLogin)
+ var chatDisabled = (config.chatDisabled)
+ var showWhoToFollowPanel = (config.showWhoToFollowPanel)
+ var whoToFollowProvider = (config.whoToFollowProvider)
+ var whoToFollowLink = (config.whoToFollowLink)
+ var showInstanceSpecificPanel = (config.showInstanceSpecificPanel)
+ var scopeOptionsEnabled = (config.scopeOptionsEnabled)
+ var collapseMessageWithSubject = (config.collapseMessageWithSubject)
store.dispatch('setOption', { name: 'theme', value: theme })
store.dispatch('setOption', { name: 'background', value: background })
store.dispatch('setOption', { name: 'logo', value: logo })
+ store.dispatch('setOption', { name: 'logoMask', value: logoMask })
+ store.dispatch('setOption', { name: 'logoMargin', value: logoMargin })
store.dispatch('setOption', { name: 'showWhoToFollowPanel', value: showWhoToFollowPanel })
store.dispatch('setOption', { name: 'whoToFollowProvider', value: whoToFollowProvider })
store.dispatch('setOption', { name: 'whoToFollowLink', value: whoToFollowLink })
diff --git a/src/modules/config.js b/src/modules/config.js
@@ -18,6 +18,12 @@ const defaultState = {
pauseOnUnfocused: true,
stopGifs: false,
replyVisibility: 'all',
+ notificationVisibility: {
+ follows: true,
+ mentions: true,
+ likes: true,
+ repeats: true
+ },
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
@@ -68,6 +68,15 @@ export const prepareStatus = (status) => {
return status
}
+const visibleNotificationTypes = (rootState) => {
+ return [
+ rootState.config.notificationVisibility.likes && 'like',
+ rootState.config.notificationVisibility.mentions && 'mention',
+ rootState.config.notificationVisibility.repeats && 'repeat',
+ rootState.config.notificationVisibility.follows && 'follow'
+ ].filter(_ => _)
+}
+
export const statusType = (status) => {
if (status.is_post_verb) {
return 'status'
@@ -86,8 +95,7 @@ export const statusType = (status) => {
return 'deletion'
}
- // TODO change to status.activity_type === 'follow' when gs supports it
- if (status.text.match(/started following/)) {
+ if (status.text.match(/started following/) || status.activity_type === 'follow') {
return 'follow'
}
@@ -187,11 +195,11 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
const favoriteStatus = (favorite, counter) => {
const status = find(allStatuses, { id: toInteger(favorite.in_reply_to_status_id) })
if (status) {
- status.fave_num += 1
-
// This is our favorite, so the relevant bit.
if (favorite.user.id === user.id) {
status.favorited = true
+ } else {
+ status.fave_num += 1
}
}
return status
@@ -225,6 +233,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
},
'favorite': (favorite) => {
// Only update if this is a new favorite.
+ // Ignore our own favorites because we get info about likes as response to like request
if (!state.favorites.has(favorite.id)) {
state.favorites.add(favorite.id)
favoriteStatus(favorite)
@@ -268,7 +277,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
}
-const addNewNotifications = (state, { dispatch, notifications, older }) => {
+const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes }) => {
const allStatuses = state.allStatuses
const allStatusesObject = state.allStatusesObject
each(notifications, (notification) => {
@@ -317,7 +326,7 @@ const addNewNotifications = (state, { dispatch, notifications, older }) => {
result.image = action.attachments[0].url
}
- if (fresh && !state.notifications.desktopNotificationSilence) {
+ if (fresh && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.ntype)) {
let notification = new window.Notification(title, result)
// Chrome is known for not closing notifications automatically
// according to MDN, anyway.
@@ -347,6 +356,11 @@ export const mutations = {
const newStatus = state.allStatusesObject[status.id]
newStatus.favorited = value
},
+ setFavoritedConfirm (state, { status }) {
+ const newStatus = state.allStatusesObject[status.id]
+ newStatus.favorited = status.favorited
+ newStatus.fave_num = status.fave_num
+ },
setRetweeted (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.repeated = value
@@ -399,7 +413,7 @@ const statuses = {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser })
},
addNewNotifications ({ rootState, commit, dispatch }, { notifications, older }) {
- commit('addNewNotifications', { dispatch, notifications, older })
+ commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older })
},
setError ({ rootState, commit }, { value }) {
commit('setError', { value })
@@ -424,11 +438,31 @@ const statuses = {
// Optimistic favoriting...
commit('setFavorited', { status, value: true })
apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
+ .then(response => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {}
+ }
+ })
+ .then(status => {
+ commit('setFavoritedConfirm', { status })
+ })
},
unfavorite ({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: false })
apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
+ .then(response => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {}
+ }
+ })
+ .then(status => {
+ commit('setFavoritedConfirm', { status })
+ })
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...
diff --git a/static/config.json b/static/config.json
@@ -2,6 +2,8 @@
"theme": "pleroma-dark",
"background": "/static/aurora_borealis.jpg",
"logo": "/static/logo.png",
+ "logoMask": true,
+ "logoMargin": ".1em",
"redirectRootNoLogin": "/main/all",
"redirectRootLogin": "/main/friends",
"chatDisabled": false,
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
@@ -247,7 +247,7 @@ describe('The Statuses module', () => {
in_reply_to_status_id: '1', // The API uses strings here...
uri: 'tag:shitposter.club,2016-08-21:fave:3895:note:773501:2016-08-21T16:52:15+00:00',
text: 'a favorited something by b',
- user: {}
+ user: { id: 99 }
}
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
@@ -264,7 +264,7 @@ describe('The Statuses module', () => {
expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(1)
expect(state.timelines.public.maxId).to.eq(favorite.id)
- // If something is favorited by the current user, it also sets the 'favorited' property
+ // If something is favorited by the current user, it also sets the 'favorited' property but does not increment counter to avoid over-counting. Counter is incremented (updated, really) via response to the favorite request.
const user = {
id: 1
}
@@ -281,7 +281,7 @@ describe('The Statuses module', () => {
mutations.addNewStatuses(state, { statuses: [ownFavorite], showImmediately: true, timeline: 'public', user })
expect(state.timelines.public.visibleStatuses.length).to.eql(1)
- expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(2)
+ expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(1)
expect(state.timelines.public.visibleStatuses[0].favorited).to.eql(true)
})
diff --git a/yarn.lock b/yarn.lock
@@ -434,6 +434,10 @@ babel-helper-replace-supers@^6.24.1:
babel-traverse "^6.24.1"
babel-types "^6.24.1"
+babel-helper-vue-jsx-merge-props@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6"
+
babel-helpers@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
@@ -500,6 +504,10 @@ babel-plugin-syntax-exponentiation-operator@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
+babel-plugin-syntax-jsx@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
+
babel-plugin-syntax-object-rest-spread@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
@@ -516,7 +524,7 @@ babel-plugin-transform-async-generator-functions@^6.24.1:
babel-plugin-syntax-async-generators "^6.5.0"
babel-runtime "^6.22.0"
-babel-plugin-transform-async-to-generator@^6.24.1:
+babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
dependencies:
@@ -555,7 +563,7 @@ babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-block-scoping@^6.24.1:
+babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
dependencies:
@@ -565,7 +573,7 @@ babel-plugin-transform-es2015-block-scoping@^6.24.1:
babel-types "^6.26.0"
lodash "^4.17.4"
-babel-plugin-transform-es2015-classes@^6.24.1:
+babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
dependencies:
@@ -579,33 +587,33 @@ babel-plugin-transform-es2015-classes@^6.24.1:
babel-traverse "^6.24.1"
babel-types "^6.24.1"
-babel-plugin-transform-es2015-computed-properties@^6.24.1:
+babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
dependencies:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
-babel-plugin-transform-es2015-destructuring@^6.22.0:
+babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
+babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
dependencies:
babel-runtime "^6.22.0"
babel-types "^6.24.1"
-babel-plugin-transform-es2015-for-of@^6.22.0:
+babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-function-name@^6.24.1:
+babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
dependencies:
@@ -619,7 +627,7 @@ babel-plugin-transform-es2015-literals@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-modules-amd@^6.24.1:
+babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
dependencies:
@@ -627,6 +635,15 @@ babel-plugin-transform-es2015-modules-amd@^6.24.1:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
+babel-plugin-transform-es2015-modules-commonjs@^6.23.0:
+ version "6.26.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
+ dependencies:
+ babel-plugin-transform-strict-mode "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-types "^6.26.0"
+
babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
@@ -636,7 +653,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
babel-template "^6.26.0"
babel-types "^6.26.0"
-babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
+babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
dependencies:
@@ -644,7 +661,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
-babel-plugin-transform-es2015-modules-umd@^6.24.1:
+babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
dependencies:
@@ -652,14 +669,14 @@ babel-plugin-transform-es2015-modules-umd@^6.24.1:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
-babel-plugin-transform-es2015-object-super@^6.24.1:
+babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
dependencies:
babel-helper-replace-supers "^6.24.1"
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-parameters@^6.24.1:
+babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
dependencies:
@@ -670,7 +687,7 @@ babel-plugin-transform-es2015-parameters@^6.24.1:
babel-traverse "^6.24.1"
babel-types "^6.24.1"
-babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
+babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
dependencies:
@@ -683,7 +700,7 @@ babel-plugin-transform-es2015-spread@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-sticky-regex@^6.24.1:
+babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
dependencies:
@@ -697,13 +714,13 @@ babel-plugin-transform-es2015-template-literals@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-typeof-symbol@^6.22.0:
+babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-es2015-unicode-regex@^6.24.1:
+babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
dependencies:
@@ -711,7 +728,7 @@ babel-plugin-transform-es2015-unicode-regex@^6.24.1:
babel-runtime "^6.22.0"
regexpu-core "^2.0.0"
-babel-plugin-transform-exponentiation-operator@^6.24.1:
+babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
dependencies:
@@ -726,7 +743,7 @@ babel-plugin-transform-object-rest-spread@^6.22.0:
babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.26.0"
-babel-plugin-transform-regenerator@^6.24.1:
+babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
dependencies:
@@ -745,6 +762,47 @@ babel-plugin-transform-strict-mode@^6.24.1:
babel-runtime "^6.22.0"
babel-types "^6.24.1"
+babel-plugin-transform-vue-jsx@3:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.7.0.tgz#d40492e6692a36b594f7e9a1928f43e969740960"
+ dependencies:
+ esutils "^2.0.2"
+
+babel-preset-env@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a"
+ dependencies:
+ babel-plugin-check-es2015-constants "^6.22.0"
+ babel-plugin-syntax-trailing-function-commas "^6.22.0"
+ babel-plugin-transform-async-to-generator "^6.22.0"
+ babel-plugin-transform-es2015-arrow-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoping "^6.23.0"
+ babel-plugin-transform-es2015-classes "^6.23.0"
+ babel-plugin-transform-es2015-computed-properties "^6.22.0"
+ babel-plugin-transform-es2015-destructuring "^6.23.0"
+ babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
+ babel-plugin-transform-es2015-for-of "^6.23.0"
+ babel-plugin-transform-es2015-function-name "^6.22.0"
+ babel-plugin-transform-es2015-literals "^6.22.0"
+ babel-plugin-transform-es2015-modules-amd "^6.22.0"
+ babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-umd "^6.23.0"
+ babel-plugin-transform-es2015-object-super "^6.22.0"
+ babel-plugin-transform-es2015-parameters "^6.23.0"
+ babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
+ babel-plugin-transform-es2015-spread "^6.22.0"
+ babel-plugin-transform-es2015-sticky-regex "^6.22.0"
+ babel-plugin-transform-es2015-template-literals "^6.22.0"
+ babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
+ babel-plugin-transform-es2015-unicode-regex "^6.22.0"
+ babel-plugin-transform-exponentiation-operator "^6.22.0"
+ babel-plugin-transform-regenerator "^6.22.0"
+ browserslist "^3.2.6"
+ invariant "^2.2.2"
+ semver "^5.3.0"
+
babel-preset-es2015@^6.0.0:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
@@ -996,6 +1054,13 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
caniuse-db "^1.0.30000639"
electron-to-chromium "^1.2.7"
+browserslist@^3.2.6:
+ version "3.2.8"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6"
+ dependencies:
+ caniuse-lite "^1.0.30000844"
+ electron-to-chromium "^1.3.47"
+
buffer@^4.9.0:
version "4.9.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
@@ -1069,6 +1134,10 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000801"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000801.tgz#a1d49def94c4e5aca5ccf1d58812e4668fac19d4"
+caniuse-lite@^1.0.30000844:
+ version "1.0.30000878"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000878.tgz#c644c39588dd42d3498e952234c372e5a40a4123"
+
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -1789,6 +1858,10 @@ electron-to-chromium@^1.2.7:
version "1.3.32"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.32.tgz#11d0684c0840e003c4be8928f8ac5f35dbc2b4e6"
+electron-to-chromium@^1.3.47:
+ version "1.3.61"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.61.tgz#a8ac295b28d0f03d85e37326fd16b6b6b17a1795"
+
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"