forked from AkkomaGang/akkoma-fe
Merge branch 'develop' into feature/hash-routed
This commit is contained in:
commit
f25ae61c5d
42 changed files with 390 additions and 110 deletions
|
@ -1,7 +1,7 @@
|
|||
# This file is a template, and might need editing before it works on your project.
|
||||
# Official framework image. Look for the different tagged releases at:
|
||||
# https://hub.docker.com/r/library/node/tags/
|
||||
image: node:6
|
||||
image: node:7
|
||||
|
||||
before_script:
|
||||
# Install ssh-agent if not already installed, it is required by Docker.
|
||||
|
@ -29,12 +29,14 @@ cache:
|
|||
|
||||
test:
|
||||
script:
|
||||
- npm install
|
||||
- npm install -g yarn
|
||||
- yarn
|
||||
- npm run unit
|
||||
|
||||
build:
|
||||
script:
|
||||
- npm install
|
||||
- npm install -g yarn
|
||||
- yarn
|
||||
- npm run build
|
||||
artifacts:
|
||||
paths:
|
||||
|
@ -45,6 +47,7 @@ deploy:
|
|||
only:
|
||||
- develop
|
||||
script:
|
||||
- npm install
|
||||
- npm install -g yarn
|
||||
- yarn
|
||||
- npm run build
|
||||
- scp -r dist/* pleromaci@heldscal.la:~/pleroma
|
||||
- scp -r dist/* pleroma@tenshi.heldscal.la:~/pleroma
|
||||
|
|
1
.node-version
Normal file
1
.node-version
Normal file
|
@ -0,0 +1 @@
|
|||
7.2.1
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="stylesheet" href="/static/font/css/fontello.css">
|
||||
<link rel="stylesheet" href="/static/font/css/animation.css">
|
||||
</head>
|
||||
<body>
|
||||
<body style="display: none">
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
"vue": "^2.0.1",
|
||||
"vue-router": "^2.0.1",
|
||||
"vue-timeago": "^3.1.2",
|
||||
"vuex": "^2.0.0"
|
||||
"vuex": "^2.0.0",
|
||||
"vuex-persistedstate": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.4.0",
|
||||
|
|
|
@ -16,7 +16,12 @@ export default {
|
|||
}),
|
||||
computed: {
|
||||
currentUser () { return this.$store.state.users.currentUser },
|
||||
style () { return { 'background-image': `url(${this.currentUser.background_image})` } }
|
||||
background () {
|
||||
return this.currentUser.background_image || this.$store.state.config.background
|
||||
},
|
||||
logoStyle () { return { 'background-image': `url(${this.$store.state.config.logo})` } },
|
||||
style () { return { 'background-image': `url(${this.background})` } },
|
||||
sitename () { return this.$store.state.config.name }
|
||||
},
|
||||
methods: {
|
||||
activatePanel (panelName) {
|
||||
|
|
|
@ -63,6 +63,10 @@ nav {
|
|||
align-items: center;
|
||||
flex-basis: 920px;
|
||||
margin: auto;
|
||||
height: 50px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div id="app" v-bind:style="style" class="base02-background">
|
||||
<nav class='container base01-background base04'>
|
||||
<div class='inner-nav'>
|
||||
<div class='inner-nav' :style="logoStyle">
|
||||
<div class='item'>
|
||||
<a route-to='friends-timeline' href="#">Pleroma FE</a>
|
||||
<a route-to='friends-timeline' href="#">{{sitename}}</a>
|
||||
</div>
|
||||
<style-switcher></style-switcher>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,12 @@ const PublicAndExternalTimeline = {
|
|||
},
|
||||
computed: {
|
||||
timeline () { return this.$store.state.statuses.timelines.publicAndExternal }
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('startFetching', 'publicAndExternal')
|
||||
},
|
||||
destroyed () {
|
||||
this.$store.dispatch('stopFetching', 'publicAndExternal')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,14 @@ const PublicTimeline = {
|
|||
},
|
||||
computed: {
|
||||
timeline () { return this.$store.state.statuses.timelines.public }
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('startFetching', 'public')
|
||||
},
|
||||
destroyed () {
|
||||
this.$store.dispatch('stopFetching', 'public')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PublicTimeline
|
||||
|
|
|
@ -3,6 +3,7 @@ import FavoriteButton from '../favorite_button/favorite_button.vue'
|
|||
import RetweetButton from '../retweet_button/retweet_button.vue'
|
||||
import DeleteButton from '../delete_button/delete_button.vue'
|
||||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import UserCardContent from '../user_card_content/user_card_content.vue'
|
||||
|
||||
const Status = {
|
||||
props: [
|
||||
|
@ -11,7 +12,9 @@ const Status = {
|
|||
],
|
||||
data: () => ({
|
||||
replying: false,
|
||||
expanded: false
|
||||
expanded: false,
|
||||
unmuted: false,
|
||||
userExpanded: false
|
||||
}),
|
||||
computed: {
|
||||
retweet () { return !!this.statusoid.retweeted_status },
|
||||
|
@ -25,14 +28,16 @@ const Status = {
|
|||
},
|
||||
loggedIn () {
|
||||
return !!this.$store.state.users.currentUser
|
||||
}
|
||||
},
|
||||
muted () { return !this.unmuted && this.status.user.muted }
|
||||
},
|
||||
components: {
|
||||
Attachment,
|
||||
FavoriteButton,
|
||||
RetweetButton,
|
||||
DeleteButton,
|
||||
PostStatusForm
|
||||
PostStatusForm,
|
||||
UserCardContent
|
||||
},
|
||||
methods: {
|
||||
toggleReplying () {
|
||||
|
@ -40,6 +45,12 @@ const Status = {
|
|||
},
|
||||
toggleExpanded () {
|
||||
this.$emit('toggleExpanded')
|
||||
},
|
||||
toggleMute () {
|
||||
this.unmuted = !this.unmuted
|
||||
},
|
||||
toggleUserExpanded () {
|
||||
this.userExpanded = !this.userExpanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +1,84 @@
|
|||
<template>
|
||||
<div class="status-el base00-background" v-if="!status.deleted">
|
||||
<div v-if="retweet" class="media container retweet-info">
|
||||
<div class="media-left">
|
||||
<i class='fa icon-retweet retweeted'></i>
|
||||
<template v-if="muted">
|
||||
<div class="media status container muted">
|
||||
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
|
||||
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="icon-eye-off"></i></a>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
Retweeted by {{retweeter}}
|
||||
</template>
|
||||
<template v-if="!muted">
|
||||
<div v-if="retweet" class="media container retweet-info">
|
||||
<div class="media-left">
|
||||
<i class='fa icon-retweet retweeted'></i>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
Retweeted by {{retweeter}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media status container">
|
||||
<div class="media-left">
|
||||
<a :href="status.user.statusnet_profile_url">
|
||||
<img class='avatar' :src="status.user.profile_image_url_original">
|
||||
</a>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<div class="user-content">
|
||||
<h4 class="media-heading">
|
||||
{{status.user.name}}
|
||||
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
|
||||
<small v-if="status.in_reply_to_screen_name"> >
|
||||
<router-link :to="{ name: 'user-profile', params: { id: status.in_reply_to_user_id } }">
|
||||
{{status.in_reply_to_screen_name}}
|
||||
</router-link>
|
||||
</small>
|
||||
-
|
||||
<small>
|
||||
<router-link :to="{ name: 'conversation', params: { id: status.id } }">
|
||||
<timeago :since="status.created_at" :auto-update="60"></timeago>
|
||||
</router-link>
|
||||
</small>
|
||||
<template v-if="expandable">
|
||||
<div class="media status container">
|
||||
<div class="media-left">
|
||||
<a :href="status.user.statusnet_profile_url">
|
||||
<img @click.prevent="toggleUserExpanded" class='avatar' :src="status.user.profile_image_url_original">
|
||||
</a>
|
||||
</div>
|
||||
<div class="media-body">
|
||||
<div class="base05 base05=border usercard" v-if="userExpanded">
|
||||
<user-card-content :user="status.user"></user-card-content>
|
||||
</div>
|
||||
<div class="user-content">
|
||||
<h4 class="media-heading">
|
||||
{{status.user.name}}
|
||||
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
|
||||
<small v-if="status.in_reply_to_screen_name"> >
|
||||
<router-link :to="{ name: 'user-profile', params: { id: status.in_reply_to_user_id } }">
|
||||
{{status.in_reply_to_screen_name}}
|
||||
</router-link>
|
||||
</small>
|
||||
-
|
||||
<small>
|
||||
<a href="#" @click.prevent="toggleExpanded" >Expand</a>
|
||||
<router-link :to="{ name: 'conversation', params: { id: status.id } }">
|
||||
<timeago :since="status.created_at" :auto-update="60"></timeago>
|
||||
</router-link>
|
||||
</small>
|
||||
</template>
|
||||
<small v-if="!status.is_local" class="source_url">
|
||||
<a :href="status.external_url" target="_blank" >Source</a>
|
||||
</small>
|
||||
</h4>
|
||||
<template v-if="expandable">
|
||||
-
|
||||
<small>
|
||||
<a href="#" @click.prevent="toggleExpanded" ><i class="icon-plus-squared"></i></a>
|
||||
</small>
|
||||
<small v-if="status.user.muted">
|
||||
<a href="#" @click.prevent="toggleMute" ><i class="icon-eye-off"></i></a>
|
||||
</small>
|
||||
</template>
|
||||
<small v-if="!status.is_local" class="source_url">
|
||||
<a :href="status.external_url" target="_blank" ><i class="icon-binoculars"></i></a>
|
||||
</small>
|
||||
</h4>
|
||||
|
||||
<div class="status-content" v-html="status.statusnet_html"></div>
|
||||
<div class="status-content" v-html="status.statusnet_html"></div>
|
||||
|
||||
<div v-if='status.attachments' class='attachments'>
|
||||
<attachment :status-id="status.id" :nsfw="status.nsfw" :attachment="attachment" v-for="attachment in status.attachments">
|
||||
</attachment>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loggedIn">
|
||||
<div class='status-actions'>
|
||||
<div>
|
||||
<a href="#" v-on:click.prevent="toggleReplying">
|
||||
<i class='fa icon-reply'></i>
|
||||
</a>
|
||||
<div v-if='status.attachments' class='attachments'>
|
||||
<attachment :status-id="status.id" :nsfw="status.nsfw" :attachment="attachment" v-for="attachment in status.attachments">
|
||||
</attachment>
|
||||
</div>
|
||||
<retweet-button :status=status></retweet-button>
|
||||
<favorite-button :status=status></favorite-button>
|
||||
<delete-button :status=status></delete-button>
|
||||
</div>
|
||||
|
||||
<post-status-form v-if="replying" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying"></post-status-form>
|
||||
<div v-if="loggedIn">
|
||||
<div class='status-actions'>
|
||||
<div>
|
||||
<a href="#" v-on:click.prevent="toggleReplying">
|
||||
<i class='fa icon-reply'></i>
|
||||
</a>
|
||||
</div>
|
||||
<retweet-button :status=status></retweet-button>
|
||||
<favorite-button :status=status></favorite-button>
|
||||
<delete-button :status=status></delete-button>
|
||||
</div>
|
||||
|
||||
<post-status-form v-if="replying" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying"></post-status-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -128,4 +142,19 @@
|
|||
padding-right: 1em;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
.muted button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
a.unmute {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.usercard {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import StyleSetter from '../../services/style_setter/style_setter.js'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
availableStyles: [],
|
||||
|
@ -13,8 +11,7 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
selected () {
|
||||
const fullPath = `/static/css/${this.selected}`
|
||||
StyleSetter.setStyle(fullPath)
|
||||
this.$store.dispatch('setOption', { name: 'theme', value: this.selected })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
<div class="base00-background panel-heading text-center" v-bind:style="style">
|
||||
<div class='user-info'>
|
||||
<img :src="user.profile_image_url">
|
||||
<div v-if='user.muted' class='muteinfo'>Muted</div>
|
||||
<div class='muteinfo' v-if='isOtherUser'>
|
||||
<button @click="toggleMute">Mute/Unmute</button>
|
||||
</div>
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
<div class='user-name'>{{user.name}}</div>
|
||||
<div class='user-screen-name'>@{{user.screen_name}}</div>
|
||||
|
@ -70,6 +74,10 @@
|
|||
const store = this.$store
|
||||
store.state.api.backendInteractor.unfollowUser(this.user.id)
|
||||
.then((unfollowedUser) => store.commit('addNewUsers', [unfollowedUser]))
|
||||
},
|
||||
toggleMute () {
|
||||
const store = this.$store
|
||||
store.commit('setMuted', {user: this.user, muted: !this.user.muted})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
src/main.js
23
src/main.js
|
@ -12,10 +12,11 @@ import UserProfile from './components/user_profile/user_profile.vue'
|
|||
import statusesModule from './modules/statuses.js'
|
||||
import usersModule from './modules/users.js'
|
||||
import apiModule from './modules/api.js'
|
||||
import configModule from './modules/config.js'
|
||||
|
||||
import VueTimeago from 'vue-timeago'
|
||||
|
||||
import StyleSetter from './services/style_setter/style_setter.js'
|
||||
import createPersistedState from 'vuex-persistedstate'
|
||||
|
||||
Vue.use(Vuex)
|
||||
Vue.use(VueRouter)
|
||||
|
@ -26,12 +27,19 @@ Vue.use(VueTimeago, {
|
|||
}
|
||||
})
|
||||
|
||||
const persistedStateOptions = {
|
||||
paths: ['users.users']
|
||||
}
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
statuses: statusesModule,
|
||||
users: usersModule,
|
||||
api: apiModule
|
||||
}
|
||||
api: apiModule,
|
||||
config: configModule
|
||||
},
|
||||
plugins: [createPersistedState(persistedStateOptions)],
|
||||
strict: process.env.NODE_ENV !== 'production'
|
||||
})
|
||||
|
||||
const routes = [
|
||||
|
@ -60,4 +68,11 @@ new Vue({
|
|||
components: { App }
|
||||
})
|
||||
|
||||
StyleSetter.setStyle('/static/css/base16-solarized-light.css')
|
||||
window.fetch('/static/config.json')
|
||||
.then((res) => res.json())
|
||||
.then(({name, theme, background, logo}) => {
|
||||
store.dispatch('setOption', { name: 'name', value: name })
|
||||
store.dispatch('setOption', { name: 'theme', value: theme })
|
||||
store.dispatch('setOption', { name: 'background', value: background })
|
||||
store.dispatch('setOption', { name: 'logo', value: logo })
|
||||
})
|
||||
|
|
|
@ -2,11 +2,32 @@ import backendInteractorService from '../services/backend_interactor_service/bac
|
|||
|
||||
const api = {
|
||||
state: {
|
||||
backendInteractor: backendInteractorService()
|
||||
backendInteractor: backendInteractorService(),
|
||||
fetchers: {}
|
||||
},
|
||||
mutations: {
|
||||
setBackendInteractor (state, backendInteractor) {
|
||||
state.backendInteractor = backendInteractor
|
||||
},
|
||||
addFetcher (state, {timeline, fetcher}) {
|
||||
state.fetchers[timeline] = fetcher
|
||||
},
|
||||
removeFetcher (state, {timeline}) {
|
||||
delete state.fetchers[timeline]
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
startFetching (store, timeline) {
|
||||
// Don't start fetching if we already are.
|
||||
if (!store.state.fetchers[timeline]) {
|
||||
const fetcher = store.state.backendInteractor.startFetching({timeline, store})
|
||||
store.commit('addFetcher', {timeline, fetcher})
|
||||
}
|
||||
},
|
||||
stopFetching (store, timeline) {
|
||||
const fetcher = store.state.fetchers[timeline]
|
||||
window.clearInterval(fetcher)
|
||||
store.commit('removeFetcher', {timeline})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
src/modules/config.js
Normal file
30
src/modules/config.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { set } from 'vue'
|
||||
import StyleSetter from '../services/style_setter/style_setter.js'
|
||||
|
||||
const defaultState = {
|
||||
name: 'Pleroma FE'
|
||||
}
|
||||
|
||||
const config = {
|
||||
state: defaultState,
|
||||
mutations: {
|
||||
setOption (state, { name, value }) {
|
||||
set(state, name, value)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setOption ({ commit }, { name, value }) {
|
||||
commit('setOption', {name, value})
|
||||
switch (name) {
|
||||
case 'name':
|
||||
document.title = value
|
||||
break
|
||||
case 'theme':
|
||||
const fullPath = `/static/css/${value}`
|
||||
StyleSetter.setStyle(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
|
@ -153,16 +153,18 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
}
|
||||
}
|
||||
|
||||
// Decide if we should treat the status as new for this timeline.
|
||||
let resultForCurrentTimeline
|
||||
// Some statuses should only be added to the global status repository.
|
||||
if (timeline && addToTimeline) {
|
||||
mergeOrAdd(timelineObject.statuses, status)
|
||||
resultForCurrentTimeline = mergeOrAdd(timelineObject.statuses, status)
|
||||
}
|
||||
|
||||
if (timeline && showImmediately) {
|
||||
// Add it directly to the visibleStatuses, don't change
|
||||
// newStatusCount
|
||||
mergeOrAdd(timelineObject.visibleStatuses, status)
|
||||
} else if (timeline && addToTimeline && result.new) {
|
||||
} else if (timeline && addToTimeline && resultForCurrentTimeline.new) {
|
||||
// Just change newStatuscount
|
||||
timelineObject.newStatusCount += 1
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import timelineFetcher from '../services/timeline_fetcher/timeline_fetcher.service.js'
|
||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||
import { compact, map, each, find, merge } from 'lodash'
|
||||
import { set } from 'vue'
|
||||
|
||||
// TODO: Unify with mergeOrAdd in statuses.js
|
||||
export const mergeOrAdd = (arr, item) => {
|
||||
|
@ -18,6 +18,10 @@ export const mergeOrAdd = (arr, item) => {
|
|||
}
|
||||
|
||||
export const mutations = {
|
||||
setMuted (state, { user: {id}, muted }) {
|
||||
const user = find(state.users, {id})
|
||||
set(user, 'muted', muted)
|
||||
},
|
||||
setCurrentUser (state, user) {
|
||||
state.currentUser = user
|
||||
},
|
||||
|
@ -29,6 +33,9 @@ export const mutations = {
|
|||
},
|
||||
addNewUsers (state, users) {
|
||||
each(users, (user) => mergeOrAdd(state.users, user))
|
||||
},
|
||||
setUserForStatus (state, status) {
|
||||
status.user = find(state.users, status.user)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,6 +54,15 @@ const users = {
|
|||
const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))
|
||||
store.commit('addNewUsers', users)
|
||||
store.commit('addNewUsers', retweetedUsers)
|
||||
|
||||
// Reconnect users to statuses
|
||||
each(statuses, (status) => {
|
||||
store.commit('setUserForStatus', status)
|
||||
})
|
||||
// Reconnect users to retweets
|
||||
each(compact(map(statuses, 'retweeted_status')), (status) => {
|
||||
store.commit('setUserForStatus', status)
|
||||
})
|
||||
},
|
||||
loginUser (store, userCredentials) {
|
||||
const commit = store.commit
|
||||
|
@ -60,12 +76,12 @@ const users = {
|
|||
commit('setCurrentUser', user)
|
||||
commit('addNewUsers', [user])
|
||||
|
||||
// Start getting fresh tweets.
|
||||
timelineFetcher.startFetching({store, credentials: userCredentials})
|
||||
|
||||
// Set our new backend interactor
|
||||
commit('setBackendInteractor', backendInteractorService(userCredentials))
|
||||
|
||||
// Start getting fresh tweets.
|
||||
store.dispatch('startFetching', 'friends')
|
||||
|
||||
// Fetch our friends
|
||||
store.rootState.api.backendInteractor.fetchFriends()
|
||||
.then((friends) => commit('addNewUsers', friends))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-env browser */
|
||||
const LOGIN_URL = '/api/account/verify_credentials.json'
|
||||
const FRIENDS_TIMELINE_URL = '/api/statuses/friends_timeline.json'
|
||||
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
||||
const PUBLIC_TIMELINE_URL = '/api/statuses/public_timeline.json'
|
||||
const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_timeline.json'
|
||||
const FAVORITE_URL = '/api/favorites/create'
|
||||
|
@ -54,6 +55,12 @@ const fetchFriends = ({credentials}) => {
|
|||
.then((data) => data.json())
|
||||
}
|
||||
|
||||
const fetchAllFollowing = ({username, credentials}) => {
|
||||
const url = `${ALL_FOLLOWING_URL}/${username}.json`
|
||||
return fetch(url, { headers: authHeaders(credentials) })
|
||||
.then((data) => data.json().users)
|
||||
}
|
||||
|
||||
const fetchMentions = ({username, sinceId = 0, credentials}) => {
|
||||
let url = `${MENTIONS_URL}?since_id=${sinceId}&screen_name=${username}`
|
||||
return fetch(url, { headers: authHeaders(credentials) })
|
||||
|
@ -169,7 +176,8 @@ const apiService = {
|
|||
retweet,
|
||||
postStatus,
|
||||
deleteStatus,
|
||||
uploadMedia
|
||||
uploadMedia,
|
||||
fetchAllFollowing
|
||||
}
|
||||
|
||||
export default apiService
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import apiService from '../api/api.service.js'
|
||||
import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
|
||||
|
||||
const backendInteractorService = (credentials) => {
|
||||
const fetchStatus = ({id}) => {
|
||||
|
@ -17,6 +18,10 @@ const backendInteractorService = (credentials) => {
|
|||
return apiService.fetchFriends({credentials})
|
||||
}
|
||||
|
||||
const fetchAllFollowing = ({username}) => {
|
||||
return apiService.fetchAllFollowing({username, credentials})
|
||||
}
|
||||
|
||||
const followUser = (id) => {
|
||||
return apiService.followUser({credentials, id})
|
||||
}
|
||||
|
@ -25,6 +30,10 @@ const backendInteractorService = (credentials) => {
|
|||
return apiService.unfollowUser({credentials, id})
|
||||
}
|
||||
|
||||
const startFetching = ({timeline, store}) => {
|
||||
return timelineFetcherService.startFetching({timeline, store, credentials})
|
||||
}
|
||||
|
||||
const backendInteractorServiceInstance = {
|
||||
fetchStatus,
|
||||
fetchConversation,
|
||||
|
@ -32,7 +41,9 @@ const backendInteractorService = (credentials) => {
|
|||
fetchFriends,
|
||||
followUser,
|
||||
unfollowUser,
|
||||
verifyCredentials: apiService.verifyCredentials
|
||||
fetchAllFollowing,
|
||||
verifyCredentials: apiService.verifyCredentials,
|
||||
startFetching
|
||||
}
|
||||
|
||||
return backendInteractorServiceInstance
|
||||
|
|
|
@ -34,7 +34,7 @@ const setStyle = (href) => {
|
|||
|
||||
styleSheet.insertRule(`a { color: ${base08Color}`, 'index-max')
|
||||
styleSheet.insertRule(`body { color: ${base05Color}`, 'index-max')
|
||||
styleSheet.insertRule(`.base05-border { color: ${base05Color}`, 'index-max')
|
||||
styleSheet.insertRule(`.base05-border { border-color: ${base05Color}`, 'index-max')
|
||||
body.style.display = 'initial'
|
||||
}
|
||||
cssEl.addEventListener('load', setDynamic)
|
||||
|
|
|
@ -30,8 +30,7 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
|
|||
const startFetching = ({ timeline = 'friends', credentials, store }) => {
|
||||
fetchAndUpdate({timeline, credentials, store, showImmediately: true})
|
||||
const boundFetchAndUpdate = () => fetchAndUpdate({ timeline, credentials, store })
|
||||
|
||||
setInterval(boundFetchAndUpdate, 10000)
|
||||
return setInterval(boundFetchAndUpdate, 10000)
|
||||
}
|
||||
const timelineFetcher = {
|
||||
fetchAndUpdate,
|
||||
|
|
BIN
static/bg.jpg
Normal file
BIN
static/bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 224 KiB |
BIN
static/bgalt.jpg
Normal file
BIN
static/bgalt.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 KiB |
6
static/config.json
Normal file
6
static/config.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Pleroma FE",
|
||||
"theme": "base16-ashes.css",
|
||||
"background": "/static/bg.jpg",
|
||||
"logo": "/static/logo.png"
|
||||
}
|
|
@ -10,6 +10,15 @@ Font license info
|
|||
Homepage: http://fortawesome.github.com/Font-Awesome/
|
||||
|
||||
|
||||
## Entypo
|
||||
|
||||
Copyright (C) 2012 by Daniel Bruce
|
||||
|
||||
Author: Daniel Bruce
|
||||
License: SIL (http://scripts.sil.org/OFL)
|
||||
Homepage: http://www.entypo.com
|
||||
|
||||
|
||||
## Fontelico
|
||||
|
||||
Copyright (C) 2012 by Fontello project
|
||||
|
|
|
@ -53,6 +53,24 @@
|
|||
"css": "retweet",
|
||||
"code": 59396,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "7fd683b2c518ceb9e5fa6757f2276faa",
|
||||
"css": "eye-off",
|
||||
"code": 59397,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "73ffeb70554099177620847206c12457",
|
||||
"css": "binoculars",
|
||||
"code": 61925,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "9e1c33b6849ceb08db8acfaf02188b7d",
|
||||
"css": "plus-squared",
|
||||
"code": 59398,
|
||||
"src": "entypo"
|
||||
}
|
||||
]
|
||||
}
|
3
static/font/css/fontello-codes.css
vendored
3
static/font/css/fontello-codes.css
vendored
|
@ -4,6 +4,9 @@
|
|||
.icon-star:before { content: '\e802'; } /* '' */
|
||||
.icon-star-empty:before { content: '\e803'; } /* '' */
|
||||
.icon-retweet:before { content: '\e804'; } /* '' */
|
||||
.icon-eye-off:before { content: '\e805'; } /* '' */
|
||||
.icon-plus-squared:before { content: '\e806'; } /* '' */
|
||||
.icon-spin3:before { content: '\e832'; } /* '' */
|
||||
.icon-spin4:before { content: '\e834'; } /* '' */
|
||||
.icon-reply:before { content: '\f112'; } /* '' */
|
||||
.icon-binoculars:before { content: '\f1e5'; } /* '' */
|
15
static/font/css/fontello-embedded.css
vendored
15
static/font/css/fontello-embedded.css
vendored
File diff suppressed because one or more lines are too long
3
static/font/css/fontello-ie7-codes.css
vendored
3
static/font/css/fontello-ie7-codes.css
vendored
|
@ -4,6 +4,9 @@
|
|||
.icon-star { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
3
static/font/css/fontello-ie7.css
vendored
3
static/font/css/fontello-ie7.css
vendored
|
@ -15,6 +15,9 @@
|
|||
.icon-star { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-retweet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-plus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
17
static/font/css/fontello.css
vendored
17
static/font/css/fontello.css
vendored
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('../font/fontello.eot?49728550');
|
||||
src: url('../font/fontello.eot?49728550#iefix') format('embedded-opentype'),
|
||||
url('../font/fontello.woff2?49728550') format('woff2'),
|
||||
url('../font/fontello.woff?49728550') format('woff'),
|
||||
url('../font/fontello.ttf?49728550') format('truetype'),
|
||||
url('../font/fontello.svg?49728550#fontello') format('svg');
|
||||
src: url('../font/fontello.eot?69621097');
|
||||
src: url('../font/fontello.eot?69621097#iefix') format('embedded-opentype'),
|
||||
url('../font/fontello.woff2?69621097') format('woff2'),
|
||||
url('../font/fontello.woff?69621097') format('woff'),
|
||||
url('../font/fontello.ttf?69621097') format('truetype'),
|
||||
url('../font/fontello.svg?69621097#fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
|||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('../font/fontello.svg?49728550#fontello') format('svg');
|
||||
src: url('../font/fontello.svg?69621097#fontello') format('svg');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -60,6 +60,9 @@
|
|||
.icon-star:before { content: '\e802'; } /* '' */
|
||||
.icon-star-empty:before { content: '\e803'; } /* '' */
|
||||
.icon-retweet:before { content: '\e804'; } /* '' */
|
||||
.icon-eye-off:before { content: '\e805'; } /* '' */
|
||||
.icon-plus-squared:before { content: '\e806'; } /* '' */
|
||||
.icon-spin3:before { content: '\e832'; } /* '' */
|
||||
.icon-spin4:before { content: '\e834'; } /* '' */
|
||||
.icon-reply:before { content: '\f112'; } /* '' */
|
||||
.icon-binoculars:before { content: '\f1e5'; } /* '' */
|
|
@ -229,11 +229,11 @@ body {
|
|||
}
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('./font/fontello.eot?17074388');
|
||||
src: url('./font/fontello.eot?17074388#iefix') format('embedded-opentype'),
|
||||
url('./font/fontello.woff?17074388') format('woff'),
|
||||
url('./font/fontello.ttf?17074388') format('truetype'),
|
||||
url('./font/fontello.svg?17074388#fontello') format('svg');
|
||||
src: url('./font/fontello.eot?5004941');
|
||||
src: url('./font/fontello.eot?5004941#iefix') format('embedded-opentype'),
|
||||
url('./font/fontello.woff?5004941') format('woff'),
|
||||
url('./font/fontello.ttf?5004941') format('truetype'),
|
||||
url('./font/fontello.svg?5004941#fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -308,9 +308,14 @@ body {
|
|||
</div>
|
||||
<div class="row">
|
||||
<div title="Code: 0xe804" class="the-icons span3"><i class="demo-icon icon-retweet"></i> <span class="i-name">icon-retweet</span><span class="i-code">0xe804</span></div>
|
||||
<div title="Code: 0xe805" class="the-icons span3"><i class="demo-icon icon-eye-off"></i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe805</span></div>
|
||||
<div title="Code: 0xe806" class="the-icons span3"><i class="demo-icon icon-plus-squared"></i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xe806</span></div>
|
||||
<div title="Code: 0xe832" class="the-icons span3"><i class="demo-icon icon-spin3 animate-spin"></i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div title="Code: 0xe834" class="the-icons span3"><i class="demo-icon icon-spin4 animate-spin"></i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div>
|
||||
<div title="Code: 0xf112" class="the-icons span3"><i class="demo-icon icon-reply"></i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div>
|
||||
<div title="Code: 0xf1e5" class="the-icons span3"><i class="demo-icon icon-binoculars"></i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div>
|
||||
|
|
Binary file not shown.
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata>
|
||||
<metadata>Copyright (C) 2017 by original authors @ fontello.com</metadata>
|
||||
<defs>
|
||||
<font id="fontello" horiz-adv-x="1000" >
|
||||
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||
|
@ -16,11 +16,17 @@
|
|||
|
||||
<glyph glyph-name="retweet" unicode="" d="M714 11q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" />
|
||||
|
||||
<glyph glyph-name="eye-off" unicode="" d="M310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="plus-squared" unicode="" d="M700 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l600 0z m-50-450l0 100-200 0 0 200-100 0 0-200-200 0 0-100 200 0 0-200 100 0 0 200 200 0z" horiz-adv-x="800" />
|
||||
|
||||
<glyph glyph-name="spin3" unicode="" d="M494 850c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="spin4" unicode="" d="M498 850c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
||||
|
||||
<glyph glyph-name="reply" unicode="" d="M1000 225q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="binoculars" unicode="" d="M393 671v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
|
||||
</font>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
static/logo.png
Normal file
BIN
static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -79,6 +79,24 @@ describe('The Statuses module', () => {
|
|||
expect(state.timelines.public.newStatusCount).to.equal(1)
|
||||
})
|
||||
|
||||
it('counts the status as new if it has not been seen on this timeline', () => {
|
||||
const state = cloneDeep(defaultState)
|
||||
const status = makeMockStatus({id: 1})
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [status], timeline: 'public' })
|
||||
mutations.addNewStatuses(state, { statuses: [status], timeline: 'friends' })
|
||||
|
||||
expect(state.allStatuses).to.eql([status])
|
||||
expect(state.timelines.public.statuses).to.eql([status])
|
||||
expect(state.timelines.public.visibleStatuses).to.eql([])
|
||||
expect(state.timelines.public.newStatusCount).to.equal(1)
|
||||
|
||||
expect(state.allStatuses).to.eql([status])
|
||||
expect(state.timelines.friends.statuses).to.eql([status])
|
||||
expect(state.timelines.friends.visibleStatuses).to.eql([])
|
||||
expect(state.timelines.friends.newStatusCount).to.equal(1)
|
||||
})
|
||||
|
||||
it('add the statuses to allStatuses if no timeline is given', () => {
|
||||
const state = cloneDeep(defaultState)
|
||||
const status = makeMockStatus({id: 1})
|
||||
|
|
|
@ -17,4 +17,18 @@ describe('The users module', () => {
|
|||
expect(state.users).to.eql([user])
|
||||
expect(state.users[0].name).to.eql('Dude')
|
||||
})
|
||||
|
||||
it('sets a mute bit on users', () => {
|
||||
const state = cloneDeep(defaultState)
|
||||
const user = { id: 1, name: 'Guy' }
|
||||
|
||||
mutations.addNewUsers(state, [user])
|
||||
mutations.setMuted(state, {user, muted: true})
|
||||
|
||||
expect(user.muted).to.eql(true)
|
||||
|
||||
mutations.setMuted(state, {user, muted: false})
|
||||
|
||||
expect(user.muted).to.eql(false)
|
||||
})
|
||||
})
|
||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -3495,6 +3495,10 @@ lodash.merge@^3.3.2:
|
|||
lodash.keysin "^3.0.0"
|
||||
lodash.toplainobject "^3.0.0"
|
||||
|
||||
lodash.merge@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5"
|
||||
|
||||
lodash.pairs@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.pairs/-/lodash.pairs-3.0.1.tgz#bbe08d5786eeeaa09a15c91ebf0dcb7d2be326a9"
|
||||
|
@ -4011,6 +4015,10 @@ object-component@0.0.3:
|
|||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
|
||||
|
||||
object-path@^0.11.2:
|
||||
version "0.11.3"
|
||||
resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.3.tgz#3e21a42ad07234d815429ae9e15c1c5f38050554"
|
||||
|
||||
object.omit@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.0.tgz#868597333d54e60662940bb458605dd6ae12fe94"
|
||||
|
@ -5734,6 +5742,13 @@ vuex:
|
|||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vuex/-/vuex-2.0.0.tgz#26befa44de220f009e432d1027487bff29571cee"
|
||||
|
||||
vuex-persistedstate:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-1.1.0.tgz#94f2e94a873f39fc716dea3129af45df8d4d6f78"
|
||||
dependencies:
|
||||
lodash.merge "^4.6.0"
|
||||
object-path "^0.11.2"
|
||||
|
||||
watchpack@^0.2.1:
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-0.2.9.tgz#62eaa4ab5e5ba35fdfc018275626e3c0f5e3fb0b"
|
||||
|
|
Loading…
Reference in a new issue