Merge branch '227-manage-blocks-mutes' into 'develop'

Add Blocks / Mutes management tabs under user settings page

See merge request pleroma/pleroma-fe!578
This commit is contained in:
Shpuld Shpludson 2019-02-22 14:54:12 +00:00
commit e34e1ccdae
20 changed files with 612 additions and 78 deletions

View File

@ -28,6 +28,7 @@
"sass-loader": "^4.0.2",
"vue": "^2.5.13",
"vue-chat-scroll": "^1.2.1",
"vue-compose": "^0.7.1",
"vue-i18n": "^7.3.2",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.3.4",

View File

@ -0,0 +1,28 @@
import UserCardContent from '../user_card_content/user_card_content.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const BasicUserCard = {
props: [
'user'
],
data () {
return {
userExpanded: false
}
},
components: {
UserCardContent,
UserAvatar
},
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}
}
}
export default BasicUserCard

View File

@ -0,0 +1,92 @@
<template>
<div class="user-card">
<router-link :to="userProfileLink(user)">
<UserAvatar class="avatar" :compact="true" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
</router-link>
<div class="user-card-expanded-content" v-if="userExpanded">
<user-card-content :user="user" :switcher="false"></user-card-content>
</div>
<div class="user-card-collapsed-content" v-else>
<div class="user-card-primary-area">
<div :title="user.name" class="user-name">
<span v-if="user.name_html" v-html="user.name_html"></span>
<span v-else>{{ user.name }}</span>
</div>
<div>
<router-link class='user-screen-name' :to="userProfileLink(user)">
@{{user.screen_name}}
</router-link>
</div>
</div>
<div class="user-card-secondary-area">
<slot name="secondary-area"></slot>
</div>
</div>
</div>
</template>
<script src="./basic_user_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.user-card {
display: flex;
flex: 1 0;
padding-top: 0.6em;
padding-right: 1em;
padding-bottom: 0.6em;
padding-left: 1em;
border-bottom: 1px solid;
margin: 0;
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
&-collapsed-content {
margin-left: 0.7em;
text-align: left;
flex: 1;
display: flex;
align-items: flex-start;
justify-content: space-between;
}
&-primary-area {
flex: 1;
.user-name {
img {
object-fit: contain;
height: 16px;
width: 16px;
vertical-align: middle;
}
}
}
&-secondary-area {
flex: none;
}
&-expanded-content {
flex: 1;
margin: 0.2em 0 0 0.7em;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-width: 1px;
overflow: hidden;
.panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
p {
margin-bottom: 0;
}
}
}
</style>

View File

@ -0,0 +1,37 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const BlockCard = {
props: ['userId'],
data () {
return {
progress: false
}
},
computed: {
user () {
return this.$store.getters.userById(this.userId)
},
blocked () {
return this.user.statusnet_blocking
}
},
components: {
BasicUserCard
},
methods: {
unblockUser () {
this.progress = true
this.$store.dispatch('unblockUser', this.user.id).then(() => {
this.progress = false
})
},
blockUser () {
this.progress = true
this.$store.dispatch('blockUser', this.user.id).then(() => {
this.progress = false
})
}
}
}
export default BlockCard

View File

@ -0,0 +1,24 @@
<template>
<basic-user-card :user="user">
<template slot="secondary-area">
<button class="btn btn-default" @click="unblockUser" :disabled="progress" v-if="blocked">
<template v-if="progress">
{{ $t('user_card.unblock_progress') }}
</template>
<template v-else>
{{ $t('user_card.unblock') }}
</template>
</button>
<button class="btn btn-default" @click="blockUser" :disabled="progress" v-else>
<template v-if="progress">
{{ $t('user_card.block_progress') }}
</template>
<template v-else>
{{ $t('user_card.block') }}
</template>
</button>
</template>
</basic-user-card>
</template>
<script src="./block_card.js"></script>

View File

@ -0,0 +1,37 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const MuteCard = {
props: ['userId'],
data () {
return {
progress: false
}
},
computed: {
user () {
return this.$store.getters.userById(this.userId)
},
muted () {
return this.user.muted
}
},
components: {
BasicUserCard
},
methods: {
unmuteUser () {
this.progress = true
this.$store.dispatch('unmuteUser', this.user.id).then(() => {
this.progress = false
})
},
muteUser () {
this.progress = true
this.$store.dispatch('muteUser', this.user.id).then(() => {
this.progress = false
})
}
}
}
export default MuteCard

View File

@ -0,0 +1,24 @@
<template>
<basic-user-card :user="user">
<template slot="secondary-area">
<button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted">
<template v-if="progress">
{{ $t('user_card.unmute_progress') }}
</template>
<template v-else>
{{ $t('user_card.unmute') }}
</template>
</button>
<button class="btn btn-default" @click="muteUser" :disabled="progress" v-else>
<template v-if="progress">
{{ $t('user_card.mute_progress') }}
</template>
<template v-else>
{{ $t('user_card.mute') }}
</template>
</button>
</template>
</basic-user-card>
</template>
<script src="./mute_card.js"></script>

View File

@ -1,9 +1,33 @@
import { unescape } from 'lodash'
import { compose } from 'vue-compose'
import unescape from 'lodash/unescape'
import get from 'lodash/get'
import TabSwitcher from '../tab_switcher/tab_switcher.js'
import ImageCropper from '../image_cropper/image_cropper.vue'
import StyleSwitcher from '../style_switcher/style_switcher.vue'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
import BlockCard from '../block_card/block_card.vue'
import MuteCard from '../mute_card/mute_card.vue'
import withSubscription from '../../hocs/with_subscription/with_subscription'
import withList from '../../hocs/with_list/with_list'
const BlockList = compose(
withSubscription({
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
childPropName: 'entries'
}),
withList({ getEntryProps: userId => ({ userId }) })
)(BlockCard)
const MuteList = compose(
withSubscription({
fetch: (props, $store) => $store.dispatch('fetchMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
childPropName: 'entries'
}),
withList({ getEntryProps: userId => ({ userId }) })
)(MuteCard)
const UserSettings = {
data () {
@ -41,7 +65,9 @@ const UserSettings = {
components: {
StyleSwitcher,
TabSwitcher,
ImageCropper
ImageCropper,
BlockList,
MuteList
},
computed: {
user () {

View File

@ -162,6 +162,12 @@
<h2>{{$t('settings.follow_export_processing')}}</h2>
</div>
</div>
<div :label="$t('settings.blocks_tab')">
<block-list :refresh="true">
<template slot="empty">{{$t('settings.no_blocks')}}</template>
</block-list>
</div>
</tab-switcher>
</div>
</div>

View File

@ -0,0 +1,40 @@
import Vue from 'vue'
import map from 'lodash/map'
import isEmpty from 'lodash/isEmpty'
import './with_list.scss'
const defaultEntryPropsGetter = entry => ({ entry })
const defaultKeyGetter = entry => entry.id
const withList = ({
getEntryProps = defaultEntryPropsGetter, // function to accept entry and index values and return props to be passed into the item component
getKey = defaultKeyGetter // funciton to accept entry and index values and return key prop value
}) => (ItemComponent) => (
Vue.component('withList', {
props: [
'entries', // array of entry
'entryProps', // additional props to be passed into each entry
'entryListeners' // additional event listeners to be passed into each entry
],
render (createElement) {
return (
<div class="with-list">
{map(this.entries, (entry, index) => {
const props = {
key: getKey(entry, index),
props: {
...this.$props.entryProps,
...getEntryProps(entry, index)
},
on: this.$props.entryListeners
}
return <ItemComponent {...props} />
})}
{isEmpty(this.entries) && this.$slots.empty && <div class="with-list-empty-content faint">{this.$slots.empty}</div>}
</div>
)
}
})
)
export default withList

View File

@ -0,0 +1,6 @@
.with-list {
&-empty-content {
text-align: center;
padding: 10px;
}
}

View File

@ -0,0 +1,91 @@
import Vue from 'vue'
import filter from 'lodash/filter'
import isEmpty from 'lodash/isEmpty'
import './with_load_more.scss'
const withLoadMore = ({
fetch, // function to fetch entries and return a promise
select, // function to select data from store
childPropName = 'entries' // name of the prop to be passed into the wrapped component
}) => (WrappedComponent) => {
const originalProps = WrappedComponent.props || []
const props = filter(originalProps, v => v !== 'entries')
return Vue.component('withLoadMore', {
render (createElement) {
const props = {
props: {
...this.$props,
[childPropName]: this.entries
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
}
const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
return (
<div class="with-load-more">
<WrappedComponent {...props}>
{children}
</WrappedComponent>
<div class="with-load-more-footer">
{this.error && <a onClick={this.fetchEntries} class="alert error">{this.$t('general.generic_error')}</a>}
{!this.error && this.loading && <i class="icon-spin3 animate-spin"/>}
{!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>}
</div>
</div>
)
},
props,
data () {
return {
loading: false,
bottomedOut: false,
error: false
}
},
computed: {
entries () {
return select(this.$props, this.$store) || []
}
},
created () {
window.addEventListener('scroll', this.scrollLoad)
if (this.entries.length === 0) {
this.fetchEntries()
}
},
destroyed () {
window.removeEventListener('scroll', this.scrollLoad)
},
methods: {
fetchEntries () {
if (!this.loading) {
this.loading = true
this.error = false
fetch(this.$props, this.$store)
.then((newEntries) => {
this.loading = false
this.bottomedOut = isEmpty(newEntries)
})
.catch(() => {
this.loading = false
this.error = true
})
}
},
scrollLoad (e) {
const bodyBRect = document.body.getBoundingClientRect()
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
if (this.loading === false &&
this.bottomedOut === false &&
this.$el.offsetHeight > 0 &&
(window.innerHeight + window.pageYOffset) >= (height - 750)
) {
this.fetchEntries()
}
}
}
})
}
export default withLoadMore

View File

@ -0,0 +1,10 @@
.with-load-more {
&-footer {
padding: 10px;
text-align: center;
.error {
font-size: 14px;
}
}
}

View File

@ -0,0 +1,84 @@
import Vue from 'vue'
import reject from 'lodash/reject'
import isEmpty from 'lodash/isEmpty'
import omit from 'lodash/omit'
import './with_subscription.scss'
const withSubscription = ({
fetch, // function to fetch entries and return a promise
select, // function to select data from store
childPropName = 'content' // name of the prop to be passed into the wrapped component
}) => (WrappedComponent) => {
const originalProps = WrappedComponent.props || []
const props = reject(originalProps, v => v === 'content')
return Vue.component('withSubscription', {
props: [
...props,
'refresh' // boolean saying to force-fetch data whenever created
],
render (createElement) {
if (!this.error && !this.loading) {
const props = {
props: {
...omit(this.$props, 'refresh'),
[childPropName]: this.fetchedData
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
}
const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
return (
<div class="with-subscription">
<WrappedComponent {...props}>
{children}
</WrappedComponent>
</div>
)
} else {
return (
<div class="with-subscription-loading">
{this.error
? <a onClick={this.fetchData} class="alert error">{this.$t('general.generic_error')}</a>
: <i class="icon-spin3 animate-spin"/>
}
</div>
)
}
},
data () {
return {
loading: false,
error: false
}
},
computed: {
fetchedData () {
return select(this.$props, this.$store)
}
},
created () {
if (this.refresh || isEmpty(this.fetchedData)) {
this.fetchData()
}
},
methods: {
fetchData () {
if (!this.loading) {
this.loading = true
this.error = false
fetch(this.$props, this.$store)
.then(() => {
this.loading = false
})
.catch(() => {
this.error = true
this.loading = false
})
}
}
}
})
}
export default withSubscription

View File

@ -0,0 +1,10 @@
.with-subscription {
&-loading {
padding: 10px;
text-align: center;
.error {
font-size: 14px;
}
}
}

View File

@ -110,6 +110,7 @@
"avatarRadius": "Avatars",
"background": "Background",
"bio": "Bio",
"blocks_tab": "Blocks",
"btnRadius": "Buttons",
"cBlue": "Blue (Reply, follow)",
"cGreen": "Green (Retweet)",
@ -164,6 +165,7 @@
"lock_account_description": "Restrict your account to approved followers only",
"loop_video": "Loop videos",
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"mutes_tab": "Mutes",
"play_videos_in_modal": "Play videos directly in the media viewer",
"use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name",
@ -175,6 +177,8 @@
"notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats",
"no_rich_text_description": "Strip rich text formatting from all posts",
"no_blocks": "No blocks",
"no_mutes": "No mutes",
"hide_follows_description": "Don't show who I'm following",
"hide_followers_description": "Don't show who's following me",
"show_admin_badge": "Show Admin badge in my profile",
@ -366,7 +370,13 @@
"muted": "Muted",
"per_day": "per day",
"remote_follow": "Remote follow",
"statuses": "Statuses"
"statuses": "Statuses",
"unblock": "Unblock",
"unblock_progress": "Unblocking...",
"block_progress": "Blocking...",
"unmute": "Unmute",
"unmute_progress": "Unmuting...",
"mute_progress": "Muting..."
},
"user_profile": {
"timeline_title": "User Timeline"

View File

@ -85,6 +85,12 @@ export const mutations = {
addNewUsers (state, users) {
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
},
saveBlocks (state, blockIds) {
state.currentUser.blockIds = blockIds
},
saveMutes (state, muteIds) {
state.currentUser.muteIds = muteIds
},
setUserForStatus (state, status) {
status.user = state.usersObject[status.user.id]
},
@ -137,6 +143,38 @@ const users = {
store.rootState.api.backendInteractor.fetchUser({ id })
.then((user) => store.commit('addNewUsers', [user]))
},
fetchBlocks (store) {
return store.rootState.api.backendInteractor.fetchBlocks()
.then((blocks) => {
store.commit('saveBlocks', map(blocks, 'id'))
store.commit('addNewUsers', blocks)
return blocks
})
},
blockUser (store, id) {
return store.rootState.api.backendInteractor.blockUser(id)
.then((user) => store.commit('addNewUsers', [user]))
},
unblockUser (store, id) {
return store.rootState.api.backendInteractor.unblockUser(id)
.then((user) => store.commit('addNewUsers', [user]))
},
fetchMutes (store) {
return store.rootState.api.backendInteractor.fetchMutes()
.then((mutedUsers) => {
each(mutedUsers, (user) => { user.muted = true })
store.commit('addNewUsers', mutedUsers)
store.commit('saveMutes', map(mutedUsers, 'id'))
})
},
muteUser (store, id) {
return store.state.api.backendInteractor.setUserMute({ id, muted: true })
.then((user) => store.commit('addNewUsers', [user]))
},
unmuteUser (store, id) {
return store.state.api.backendInteractor.setUserMute({ id, muted: false })
.then((user) => store.commit('addNewUsers', [user]))
},
addFriends ({ rootState, commit }, fetchBy) {
return new Promise((resolve, reject) => {
const user = rootState.users.usersObject[fetchBy]
@ -263,6 +301,8 @@ const users = {
const user = data
// user.credentials = userCredentials
user.credentials = accessToken
user.blockIds = []
user.muteIds = []
commit('setCurrentUser', user)
commit('addNewUsers', [user])
@ -279,11 +319,8 @@ const users = {
// Start getting fresh posts.
store.dispatch('startFetching', { timeline: 'friends' })
// Get user mutes and follower info
store.rootState.api.backendInteractor.fetchMutes().then((mutedUsers) => {
each(mutedUsers, (user) => { user.muted = true })
store.commit('addNewUsers', mutedUsers)
})
// Get user mutes
store.dispatch('fetchMutes')
// Fetch our friends
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })

View File

@ -18,6 +18,7 @@ const MENTIONS_URL = '/api/statuses/mentions.json'
const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json'
const FOLLOWERS_URL = '/api/statuses/followers.json'
const FRIENDS_URL = '/api/statuses/friends.json'
const BLOCKS_URL = '/api/statuses/blocks.json'
const FOLLOWING_URL = '/api/friendships/create.json'
const UNFOLLOWING_URL = '/api/friendships/destroy.json'
const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json'
@ -519,6 +520,17 @@ const fetchMutes = ({credentials}) => {
}).then((data) => data.json())
}
const fetchBlocks = ({page, credentials}) => {
return fetch(BLOCKS_URL, {
headers: authHeaders(credentials)
}).then((data) => {
if (data.ok) {
return data.json()
}
throw new Error('Error fetching blocks', data)
})
}
const suggestions = ({credentials}) => {
return fetch(SUGGESTIONS_URL, {
headers: authHeaders(credentials)
@ -560,6 +572,7 @@ const apiService = {
fetchAllFollowing,
setUserMute,
fetchMutes,
fetchBlocks,
register,
getCaptcha,
updateAvatar,

View File

@ -63,6 +63,7 @@ const backendInteractorService = (credentials) => {
}
const fetchMutes = () => apiService.fetchMutes({credentials})
const fetchBlocks = (params) => apiService.fetchBlocks({credentials, ...params})
const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
const getCaptcha = () => apiService.getCaptcha()
@ -94,6 +95,7 @@ const backendInteractorService = (credentials) => {
startFetching,
setUserMute,
fetchMutes,
fetchBlocks,
register,
getCaptcha,
updateAvatar,

View File

@ -38,11 +38,7 @@
dom-event-types "^1.0.0"
lodash "^4.17.4"
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
abbrev@1.0.x:
abbrev@1, abbrev@1.0.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
@ -1385,14 +1381,10 @@ color-convert@^1.3.0, color-convert@^1.9.0:
dependencies:
color-name "1.1.3"
color-name@1.1.3:
color-name@1.1.3, color-name@^1.0.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
color-name@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
color-string@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
@ -2431,14 +2423,10 @@ extract-zip@^1.6.5, extract-zip@^1.6.7:
mkdirp "0.5.1"
yauzl "2.4.1"
extsprintf@1.3.0:
extsprintf@1.3.0, extsprintf@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
@ -3024,18 +3012,12 @@ https-proxy-agent@1:
debug "2"
extend "3"
iconv-lite@0.4.23:
iconv-lite@0.4.23, iconv-lite@^0.4.4:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
dependencies:
safer-buffer ">= 2.1.2 < 3"
icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@ -3480,11 +3462,7 @@ js-beautify@^1.6.3:
mkdirp "~0.5.0"
nopt "~4.0.1"
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
js-tokens@^3.0.2:
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@ -4237,7 +4215,7 @@ minimatch@3.0.3:
dependencies:
brace-expansion "^1.0.0"
minimist@0.0.8:
minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
@ -4245,10 +4223,6 @@ minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
minipass@^2.2.1, minipass@^2.3.4:
version "2.3.5"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
@ -4679,7 +4653,7 @@ os-locale@^1.4.0:
dependencies:
lcid "^1.0.0"
os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@ -4787,10 +4761,6 @@ path-is-inside@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@ -5224,14 +5194,10 @@ punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
q@1.4.1:
q@1.4.1, q@^1.1.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
qjobs@^1.1.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
@ -5546,16 +5512,10 @@ resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
resolve@1.1.x:
resolve@1.1.x, resolve@^1.1.6:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@^1.1.6:
version "1.9.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.9.0.tgz#a14c6fdfa8f92a7df1d996cb7105fa744658ea06"
dependencies:
path-parse "^1.0.6"
restore-cursor@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@ -5607,14 +5567,10 @@ safe-regex@^1.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
samsam@1.1.2:
samsam@1.1.2, samsam@~1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
samsam@~1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621"
sanitize-html@^1.13.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.0.tgz#9a602beb1c9faf960fb31f9890f61911cc4d9156"
@ -5988,18 +5944,14 @@ static-extend@^0.1.1:
define-property "^0.2.5"
object-copy "^0.1.0"
"statuses@>= 1.4.0 < 2":
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
"statuses@>= 1.4.0 < 2", statuses@~1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
statuses@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
statuses@~1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
stream-browserify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
@ -6088,7 +6040,7 @@ strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
supports-color@3.1.2:
supports-color@3.1.2, supports-color@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
dependencies:
@ -6098,7 +6050,7 @@ supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
supports-color@^3.1.0, supports-color@^3.2.3:
supports-color@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
dependencies:
@ -6196,18 +6148,12 @@ timers-browserify@^2.0.2:
dependencies:
setimmediate "^1.0.4"
tmp@0.0.31:
tmp@0.0.31, tmp@0.0.x:
version "0.0.31"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
dependencies:
os-tmpdir "~1.0.1"
tmp@0.0.x:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
dependencies:
os-tmpdir "~1.0.2"
to-array@0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
@ -6476,6 +6422,16 @@ vue-chat-scroll@^1.2.1:
version "1.3.5"
resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68"
vue-compose@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/vue-compose/-/vue-compose-0.7.1.tgz#1c11c4cd5e2c8f2743b03fce8ab43d78aabc20b3"
dependencies:
vue-hoc "0.x.x"
vue-hoc@0.x.x:
version "0.4.7"
resolved "https://registry.yarnpkg.com/vue-hoc/-/vue-hoc-0.4.7.tgz#4d3322ba89b8b0e42b19045ef536c21d948a4fac"
vue-hot-reload-api@^2.0.11:
version "2.3.1"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"