Merge pull request #403 from h3poteto/iss-396

closes #396 Create list edit page which can manage list memberships
This commit is contained in:
AkiraFukushima 2018-06-19 08:50:42 +09:00 committed by GitHub
commit 75b16fff33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 407 additions and 6 deletions

View File

@ -11,13 +11,28 @@
@{{ user.acct }}
</div>
</div>
<div class="tool" v-if="remove">
<el-button type="text" @click.stop.prevent="removeAccount(user)">
<icon name="times"></icon>
</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'user',
props: [ 'user' ],
props: {
user: {
type: Object,
default: {}
},
remove: {
type: Boolean,
default: false
}
},
methods: {
username (account) {
if (account.display_name !== '') {
@ -30,6 +45,9 @@ export default {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
},
removeAccount (account) {
this.$emit('removeAccount', account)
}
}
}
@ -66,5 +84,9 @@ export default {
word-wrap: break-word;
}
}
.tool {
margin-left: auto;
}
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<div class="members">
<div class="add-account">
<el-button type="text" class="add-button" @click="addAccount">
<icon name="plus"></icon>
</el-button>
</div>
<template v-for="account in members">
<user :user="account" :remove="true" @removeAccount="removeAccount"></user>
</template>
</div>
</template>
<script>
import { mapState } from 'vuex'
import User from '../Cards/User'
export default {
name: 'edit-list',
props: ['list_id'],
components: { User },
computed: {
...mapState({
members: state => state.TimelineSpace.Contents.Lists.Edit.members
})
},
created () {
this.init()
},
methods: {
async init () {
const loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
await this.$store.dispatch('TimelineSpace/Contents/Lists/Edit/fetchMembers', this.list_id)
} catch (err) {
this.$message({
message: 'Failed to fetch members',
type: 'error'
})
} finally {
loading.close()
}
},
async removeAccount (account) {
const loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
await this.$store.dispatch('TimelineSpace/Contents/Lists/Edit/removeAccount', {
account: account,
listId: this.list_id
})
await this.$store.dispatch('TimelineSpace/Contents/Lists/Edit/fetchMembers', this.list_id)
} catch (err) {
this.$message({
message: 'Failed to remove user',
type: 'error'
})
} finally {
loading.close()
}
},
addAccount () {
this.$store.commit('TimelineSpace/Modals/AddListMember/setListId', this.list_id)
this.$store.dispatch('TimelineSpace/Modals/AddListMember/changeModal', true)
}
}
}
</script>
<style lang="scss" scoped>
.members {
.add-account {
text-align: center;
border-bottom: 1px solid var(--theme-border-color);
.add-button {
width: 100%;
}
}
}
</style>

View File

@ -9,9 +9,14 @@
</el-form>
</div>
<div class="list" v-for="list in lists" :key="list.id">
<router-link tag="span" class="title" :to="`/${id()}/lists/${list.id}`">
<router-link tag="div" class="title" :to="`/${id()}/lists/${list.id}`">
{{ list.title }}
</router-link>
<div class="tools">
<el-button type="text" @click="edit(list)">
Edit
</el-button>
</div>
</div>
</div>
</template>
@ -71,6 +76,9 @@ export default {
} finally {
this.creating = false
}
},
edit (list) {
return this.$router.push(`/${this.id()}/lists/${list.id}/edit`)
}
}
}
@ -105,7 +113,11 @@ export default {
}
.list {
padding: 12px 24px;
padding: 4px 24px;
display: flex;
flex-dirrection: row;
align-items: baseline;
justify-content: space-between;
border-bottom: 1px solid var(--theme-border-color);
.title {

View File

@ -57,6 +57,9 @@ export default {
case 'lists':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', 'Lists')
break
case 'edit-list':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', 'Members')
break
case 'list':
this.$store.dispatch('TimelineSpace/HeaderMenu/fetchList', this.$route.params.list_id)
break

View File

@ -4,6 +4,7 @@
<jump></jump>
<image-viewer></image-viewer>
<list-membership></list-membership>
<add-list-member></add-list-member>
</div>
</template>
@ -12,6 +13,7 @@ import NewToot from './Modals/NewToot'
import Jump from './Modals/Jump'
import ImageViewer from './Modals/ImageViewer'
import ListMembership from './Modals/ListMembership'
import AddListMember from './Modals/AddListMember'
export default {
name: 'modals',
@ -19,7 +21,8 @@ export default {
NewToot,
Jump,
ImageViewer,
ListMembership
ListMembership,
AddListMember
}
}
</script>

View File

@ -0,0 +1,176 @@
<template>
<el-dialog
title="Add Member to List"
:visible.sync="addListMemberModal"
width="400px"
class="add-member">
<div class="search-account" :element-loading-background="loadingBackground">
<el-form :inline="true">
<input v-model="name" placeholder="Account name" class="account-name" v-shortkey="['enter']" @shortkey="search" autofocus></input>
<el-button type="text" class="search" @click="search">
<icon name="search"></icon>
</el-button>
</el-form>
<div class="search-results">
<template v-for="user in accounts">
<div class="user">
<div class="icon">
<img :src="user.avatar" />
</div>
<div class="name">
<div class="username">
{{ username(user) }}
</div>
<div class="acct">
@{{ user.acct }}
</div>
</div>
<div class="add">
<el-button type="text" @click="add(user)">
<icon name="plus"></icon>
</el-button>
</div>
</div>
</template>
</div>
</div>
</el-dialog>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'add-list-member',
data () {
return {
name: ''
}
},
computed: {
...mapState({
loadingBackground: state => state.App.theme.wrapper_mask_color,
accounts: state => state.TimelineSpace.Modals.AddListMember.accounts,
listId: state => state.TimelineSpace.Modals.AddListMember.targetListId
}),
addListMemberModal: {
get () {
return this.$store.state.TimelineSpace.Modals.AddListMember.modalOpen
},
set (value) {
this.$store.dispatch('TimelineSpace/Modals/AddListMember/changeModal', value)
}
}
},
methods: {
username (account) {
if (account.display_name !== '') {
return account.display_name
} else {
return account.username
}
},
search () {
this.$store.dispatch('TimelineSpace/Modals/AddListMember/search', this.name)
},
add (user) {
this.$store.dispatch('TimelineSpace/Modals/AddListMember/add', user)
.then(() => {
this.addListMemberModal = false
this.$store.dispatch('TimelineSpace/Contents/Lists/Edit/fetchMembers', this.listId)
})
.catch(() => {
this.$message({
message: 'Failed to add user',
type: 'error'
})
})
}
}
}
</script>
<style lang="scss" scoped>
.add-member /deep/ {
.el-dialog__header {
background-color: var(--theme-header-menu-color);
.el-dialog__title {
color: var(--theme-secondary-color);
}
}
.el-dialog__body {
background-color: var(--theme-selected-background-color);
color: var(--theme-secondary-color);
padding: 8px 12px 30px;
}
.search-account {
background-color: var(--theme-selected-background-color);
.account-name {
width: calc(100% - 32px);
background-color: var(--theme-background-color);
border: none;
border-radius: 0 4px 4px 0;
color: var(--theme-primary-color);
line-height: 40px;
height: 40px;
padding: 0 15px;
outline: 0;
font-size: 14px;
box-sizing: border-box;
}
.search {
box-sizing: border-box;
width: 24px;
margin-left: 4px;
color: var(--theme-secondary-color);
}
.search-results {
margin-top: 12px;
height: 200px;
overflow: auto;
.user {
display: flex;
box-sizing: border-box;
padding: 4px 12px 8px;
border-bottom: 1px solid var(--theme-border-color);
cursor: pointer;
.icon {
display: inline-block;
img {
width: 36px;
height: 36px;
border-radius: 4px;
}
}
.name {
display: inline-block;
padding-left: 8px;
.acct {
color: #909399;
}
div {
width: auto;
word-wrap: break-word;
}
}
.add {
margin-left: auto;
}
}
}
}
}
</style>

View File

@ -94,6 +94,12 @@ export default new Router({
name: 'lists',
component: require('@/components/TimelineSpace/Contents/Lists/Index').default
},
{
path: 'lists/:list_id/edit',
name: 'edit-list',
component: require('@/components/TimelineSpace/Contents/Lists/Edit').default,
props: true
},
{
path: 'lists/:list_id',
name: 'list',

View File

@ -1,10 +1,12 @@
import Index from './Lists/Index'
import Show from './Lists/Show'
import Edit from './Lists/Edit'
export default {
namespaced: true,
modules: {
Index,
Show
Show,
Edit
}
}

View File

@ -0,0 +1,36 @@
import Mastodon from 'megalodon'
export default {
namespaced: true,
state: {
members: []
},
mutations: {
changeMembers (state, members) {
state.members = members
}
},
actions: {
fetchMembers ({ commit, rootState }, listId) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.get(`/lists/${listId}/accounts`, {
limit: 0
})
.then((data) => {
commit('changeMembers', data)
})
},
removeAccount ({ rootState }, obj) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.del(`/lists/${obj.listId}/accounts`, {
account_ids: [obj.account.id]
})
}
}
}

View File

@ -2,6 +2,7 @@ import NewToot from './Modals/NewToot'
import ImageViewer from './Modals/ImageViewer'
import Jump from './Modals/Jump'
import ListMembership from './Modals/ListMembership'
import AddListMember from './Modals/AddListMember'
const Modals = {
namespaced: true,
@ -9,7 +10,8 @@ const Modals = {
ImageViewer,
NewToot,
Jump,
ListMembership
ListMembership,
AddListMember
}
}

View File

@ -0,0 +1,49 @@
import Mastodon from 'megalodon'
export default {
namespaced: true,
state: {
modalOpen: false,
accounts: [],
targetListId: null
},
mutations: {
changeModal (state, value) {
state.modalOpen = value
},
updateAccounts (state, accounts) {
state.accounts = accounts
},
setListId (state, id) {
state.targetListId = id
}
},
actions: {
changeModal ({ commit }, value) {
commit('changeModal', value)
},
search ({ commit, rootState }, name) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.get('/accounts/search', {
q: name,
following: true
})
.then(data => {
commit('updateAccounts', data)
return data
})
},
add ({ state, rootState }, account) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.post(`/lists/${state.targetListId}/accounts`, {
account_ids: [account.id]
})
}
}
}