ユーザーグループ

Resolve #3218
This commit is contained in:
syuilo 2019-05-18 20:36:33 +09:00
parent 61f54f8f74
commit c7cc3dcdfd
No known key found for this signature in database
GPG key ID: BDC4C49D06AB9D69
65 changed files with 1797 additions and 638 deletions

View file

@ -116,6 +116,10 @@ export default Vue.extend({
},
created() {
this.$emit('init', {
title: this.$t('@.explore'),
icon: faHashtag
});
this.$root.api('hashtags/list', {
sort: '+attachedLocalUsers',
attachedToLocalUserOnly: true,

View file

@ -0,0 +1,68 @@
<template>
<div>
<ui-container :body-togglable="true">
<template #header>{{ $t('received-follow-requests') }}</template>
<div v-if="!fetching">
<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="mcbzkkaw">
<div v-for="req in requests">
<router-link :key="req.id" :to="req.follower | userPage">
<mk-user-name :user="req.follower"/>
</router-link>
<span>
<a @click="accept(req.follower)">{{ $t('accept') }}</a>|<a @click="reject(req.follower)">{{ $t('reject') }}</a>
</span>
</div>
</sequential-entrance>
</div>
</ui-container>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import Progress from '../../scripts/loading';
export default Vue.extend({
i18n: i18n('common/views/pages/follow-requests.vue'),
data() {
return {
fetching: true,
requests: []
};
},
mounted() {
Progress.start();
this.$root.api('following/requests/list').then(requests => {
this.fetching = false;
this.requests = requests;
Progress.done();
});
},
methods: {
accept(user) {
this.$root.api('following/requests/accept', { userId: user.id }).then(() => {
this.requests = this.requests.filter(r => r.follower.id != user.id);
});
},
reject(user) {
this.$root.api('following/requests/reject', { userId: user.id }).then(() => {
this.requests = this.requests.filter(r => r.follower.id != user.id);
});
}
}
});
</script>
<style lang="stylus" scoped>
.mcbzkkaw
> div
display flex
padding 16px
border solid 1px var(--faceDivider)
border-radius 4px
> span
margin 0 0 0 auto
</style>

View file

@ -50,6 +50,11 @@ export default Vue.extend({
},
created() {
this.fetch();
this.$emit('init', {
title: this.$t('@.pages'),
icon: faStickyNote
});
},
methods: {
async fetch() {

View file

@ -0,0 +1,180 @@
<template>
<div class="ivrbakop">
<ui-container v-if="group">
<template #header><fa :icon="faUsers"/> {{ group.name }}</template>
<section>
<ui-margin>
<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
</ui-margin>
</section>
</ui-container>
<ui-container>
<template #header><fa :icon="faUsers"/> {{ $t('users') }}</template>
<section>
<ui-margin>
<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('add-user') }}</ui-button>
</ui-margin>
<sequential-entrance animation="entranceFromTop" delay="25">
<div class="kjlrfbes" v-for="user in users">
<div>
<a :href="user | userPage">
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
</a>
</div>
<div>
<header>
<b><mk-user-name :user="user"/></b>
<span class="username">@{{ user | acct }}</span>
</header>
<div>
<a @click="remove(user)">{{ $t('remove-user') }}</a>
</div>
</div>
</div>
</sequential-entrance>
</section>
</ui-container>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import { faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/components/user-group-editor.vue'),
props: {
groupId: {
required: true
}
},
data() {
return {
group: null,
users: [],
faICursor, faTrashAlt, faUsers, faPlus
};
},
created() {
this.$root.api('users/groups/show', {
groupId: this.groupId
}).then(group => {
this.group = group;
this.fetchUsers();
this.$emit('init', {
title: this.group.name,
icon: faUsers
});
});
},
methods: {
fetchUsers() {
this.$root.api('users/show', {
userIds: this.group.userIds
}).then(users => {
this.users = users;
});
},
rename() {
this.$root.dialog({
title: this.$t('rename'),
input: {
default: this.group.name
}
}).then(({ canceled, result: name }) => {
if (canceled) return;
this.$root.api('users/groups/update', {
groupId: this.group.id,
name: name
});
});
},
del() {
this.$root.dialog({
type: 'warning',
text: this.$t('delete-are-you-sure').replace('$1', this.group.name),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
this.$root.api('users/groups/delete', {
groupId: this.group.id
}).then(() => {
this.$root.dialog({
type: 'success',
text: this.$t('deleted')
});
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
});
});
},
remove(user: any) {
this.$root.api('users/groups/pull', {
groupId: this.group.id,
userId: user.id
}).then(() => {
this.fetchUsers();
});
},
async add() {
const { result: user } = await this.$root.dialog({
user: {
local: true
}
});
if (user == null) return;
this.$root.api('users/groups/push', {
groupId: this.group.id,
userId: user.id
}).then(() => {
this.fetchUsers();
});
}
}
});
</script>
<style lang="stylus" scoped>
.ivrbakop
.kjlrfbes
display flex
padding 16px
border-top solid 1px var(--faceDivider)
> div:first-child
> a
> .avatar
width 64px
height 64px
> div:last-child
flex 1
padding-left 16px
@media (max-width 500px)
font-size 14px
> header
> .username
margin-left 8px
opacity 0.7
</style>

View file

@ -0,0 +1,63 @@
<template>
<ui-container>
<template #header><fa :icon="faUsers"/> {{ $t('user-groups') }}</template>
<ui-margin>
<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-group') }}</ui-button>
</ui-margin>
<div class="hwgkdrbl" v-for="group in groups" :key="group.id">
<ui-hr/>
<ui-margin>
<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link>
</ui-margin>
</div>
</ui-container>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import { faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/components/user-groups.vue'),
data() {
return {
fetching: true,
groups: [],
faUsers, faPlus
};
},
mounted() {
this.$root.api('users/groups/owned').then(groups => {
this.fetching = false;
this.groups = groups;
});
this.$emit('init', {
title: this.$t('user-groups'),
icon: faUsers
});
},
methods: {
add() {
this.$root.dialog({
title: this.$t('group-name'),
input: true
}).then(async ({ canceled, result: name }) => {
if (canceled) return;
const list = await this.$root.api('users/groups/create', {
name
});
this.groups.push(list)
});
},
}
});
</script>
<style lang="stylus" scoped>
.hwgkdrbl
display block
</style>

View file

@ -0,0 +1,180 @@
<template>
<div class="cudqjmnl">
<ui-container v-if="list">
<template #header><fa :icon="faListUl"/> {{ list.name }}</template>
<section class="fwvevrks">
<ui-margin>
<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
</ui-margin>
</section>
</ui-container>
<ui-container>
<template #header><fa :icon="faUsers"/> {{ $t('users') }}</template>
<section>
<ui-margin>
<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('add-user') }}</ui-button>
</ui-margin>
<sequential-entrance animation="entranceFromTop" delay="25">
<div class="phcqulfl" v-for="user in users">
<div>
<a :href="user | userPage">
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
</a>
</div>
<div>
<header>
<b><mk-user-name :user="user"/></b>
<span class="username">@{{ user | acct }}</span>
</header>
<div>
<a @click="remove(user)">{{ $t('remove-user') }}</a>
</div>
</div>
</div>
</sequential-entrance>
</section>
</ui-container>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import { faListUl, faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/components/user-list-editor.vue'),
props: {
listId: {
required: true
}
},
data() {
return {
list: null,
users: [],
faListUl, faICursor, faTrashAlt, faUsers, faPlus
};
},
created() {
this.$root.api('users/lists/show', {
listId: this.listId
}).then(list => {
this.list = list;
this.fetchUsers();
this.$emit('init', {
title: this.list.name,
icon: faListUl
});
});
},
methods: {
fetchUsers() {
this.$root.api('users/show', {
userIds: this.list.userIds
}).then(users => {
this.users = users;
});
},
rename() {
this.$root.dialog({
title: this.$t('rename'),
input: {
default: this.list.name
}
}).then(({ canceled, result: name }) => {
if (canceled) return;
this.$root.api('users/lists/update', {
listId: this.list.id,
name: name
});
});
},
del() {
this.$root.dialog({
type: 'warning',
text: this.$t('delete-are-you-sure').replace('$1', this.list.name),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
this.$root.api('users/lists/delete', {
listId: this.list.id
}).then(() => {
this.$root.dialog({
type: 'success',
text: this.$t('deleted')
});
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
});
});
},
remove(user: any) {
this.$root.api('users/lists/pull', {
listId: this.list.id,
userId: user.id
}).then(() => {
this.fetchUsers();
});
},
async add() {
const { result: user } = await this.$root.dialog({
user: {
local: true
}
});
if (user == null) return;
this.$root.api('users/lists/push', {
listId: this.list.id,
userId: user.id
}).then(() => {
this.fetchUsers();
});
}
}
});
</script>
<style lang="stylus" scoped>
.cudqjmnl
.phcqulfl
display flex
padding 16px
border-top solid 1px var(--faceDivider)
> div:first-child
> a
> .avatar
width 64px
height 64px
> div:last-child
flex 1
padding-left 16px
@media (max-width 500px)
font-size 14px
> header
> .username
margin-left 8px
opacity 0.7
</style>

View file

@ -0,0 +1,63 @@
<template>
<ui-container>
<template #header><fa :icon="faListUl"/> {{ $t('user-lists') }}</template>
<ui-margin>
<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-list') }}</ui-button>
</ui-margin>
<div class="cpqqyrst" v-for="list in lists" :key="list.id">
<ui-hr/>
<ui-margin>
<router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link>
</ui-margin>
</div>
</ui-container>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import { faListUl, faPlus } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/components/user-lists.vue'),
data() {
return {
fetching: true,
lists: [],
faListUl, faPlus
};
},
mounted() {
this.$root.api('users/lists/list').then(lists => {
this.fetching = false;
this.lists = lists;
});
this.$emit('init', {
title: this.$t('user-lists'),
icon: faListUl
});
},
methods: {
add() {
this.$root.dialog({
title: this.$t('list-name'),
input: true
}).then(async ({ canceled, result: name }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
name
});
this.lists.push(list)
});
},
}
});
</script>
<style lang="stylus" scoped>
.cpqqyrst
display block
</style>