logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
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:

M.babelrc4++--
Mbuild/webpack.base.conf.js2+-
Mpackage.json4++++
Msrc/App.js31+++++++++++++++++++++++++++++--
Msrc/App.scss58++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/App.vue6+++++-
Msrc/components/attachment/attachment.js2+-
Msrc/components/attachment/attachment.vue9+++++----
Msrc/components/conversation/conversation.vue4++--
Msrc/components/media_upload/media_upload.js6++++--
Msrc/components/media_upload/media_upload.vue2+-
Msrc/components/notifications/notifications.js12++++++++++--
Msrc/components/notifications/notifications.scss52++++++++++------------------------------------------
Msrc/components/notifications/notifications.vue6++++--
Msrc/components/retweet_button/retweet_button.vue11++++++++---
Msrc/components/settings/settings.js15+++++++++++++++
Msrc/components/settings/settings.vue231+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/components/still-image/still-image.js6+++++-
Msrc/components/still-image/still-image.vue1+
Msrc/components/style_switcher/style_switcher.vue230++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Asrc/components/tab_switcher/tab_switcher.jsx44++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/tab_switcher/tab_switcher.scss43+++++++++++++++++++++++++++++++++++++++++++
Msrc/components/timeline/timeline.vue31-------------------------------
Msrc/components/user_profile/user_profile.vue2++
Msrc/components/user_settings/user_settings.js4+++-
Msrc/components/user_settings/user_settings.vue246+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/i18n/messages.js50++++++++++++++++++++++++++++++++++++++++----------
Msrc/main.js33++++++++++++++++++++-------------
Msrc/modules/config.js6++++++
Msrc/modules/statuses.js48+++++++++++++++++++++++++++++++++++++++++-------
Mstatic/config.json2++
Mtest/unit/specs/modules/statuses.spec.js6+++---
Myarn.lock111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
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"