整理した
This commit is contained in:
parent
8a279a4656
commit
cf33e483f7
552 changed files with 360 additions and 1311 deletions
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div class="followers-you-know">
|
||||
<p class="title">%fa:users%%i18n:desktop.tags.mk-user.followers-you-know.title%</p>
|
||||
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:desktop.tags.mk-user.followers-you-know.loading%<mk-ellipsis/></p>
|
||||
<div v-if="!fetching && users.length > 0">
|
||||
<router-link v-for="user in users" :to="`/@${getAcct(user)}`" :key="user.id">
|
||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user.name" v-user-preview="user.id"/>
|
||||
</router-link>
|
||||
</div>
|
||||
<p class="empty" v-if="!fetching && users.length == 0">%i18n:desktop.tags.mk-user.followers-you-know.no-users%</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import getAcct from '../../../../../../common/user/get-acct';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['user'],
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
fetching: true
|
||||
};
|
||||
},
|
||||
method() {
|
||||
getAcct
|
||||
},
|
||||
mounted() {
|
||||
(this as any).api('users/followers', {
|
||||
userId: this.user.id,
|
||||
iknow: true,
|
||||
limit: 16
|
||||
}).then(x => {
|
||||
this.users = x.users;
|
||||
this.fetching = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.followers-you-know
|
||||
background #fff
|
||||
border solid 1px rgba(0, 0, 0, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
> .title
|
||||
z-index 1
|
||||
margin 0
|
||||
padding 0 16px
|
||||
line-height 42px
|
||||
font-size 0.9em
|
||||
font-weight bold
|
||||
color #888
|
||||
box-shadow 0 1px rgba(0, 0, 0, 0.07)
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
> div
|
||||
padding 8px
|
||||
|
||||
> a
|
||||
display inline-block
|
||||
margin 4px
|
||||
|
||||
> img
|
||||
width 48px
|
||||
height 48px
|
||||
vertical-align bottom
|
||||
border-radius 100%
|
||||
|
||||
> .initializing
|
||||
> .empty
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
</style>
|
||||
124
src/client/app/desktop/views/pages/user/user.friends.vue
Normal file
124
src/client/app/desktop/views/pages/user/user.friends.vue
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<div class="friends">
|
||||
<p class="title">%fa:users%%i18n:desktop.tags.mk-user.frequently-replied-users.title%</p>
|
||||
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:desktop.tags.mk-user.frequently-replied-users.loading%<mk-ellipsis/></p>
|
||||
<template v-if="!fetching && users.length != 0">
|
||||
<div class="user" v-for="friend in users">
|
||||
<router-link class="avatar-anchor" :to="`/@${getAcct(friend)}`">
|
||||
<img class="avatar" :src="`${friend.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="friend.id"/>
|
||||
</router-link>
|
||||
<div class="body">
|
||||
<router-link class="name" :to="`/@${getAcct(friend)}`" v-user-preview="friend.id">{{ friend.name }}</router-link>
|
||||
<p class="username">@{{ getAcct(friend) }}</p>
|
||||
</div>
|
||||
<mk-follow-button :user="friend"/>
|
||||
</div>
|
||||
</template>
|
||||
<p class="empty" v-if="!fetching && users.length == 0">%i18n:desktop.tags.mk-user.frequently-replied-users.no-users%</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import getAcct from '../../../../../../common/user/get-acct';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['user'],
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
fetching: true
|
||||
};
|
||||
},
|
||||
method() {
|
||||
getAcct
|
||||
},
|
||||
mounted() {
|
||||
(this as any).api('users/get_frequently_replied_users', {
|
||||
userId: this.user.id,
|
||||
limit: 4
|
||||
}).then(docs => {
|
||||
this.users = docs.map(doc => doc.user);
|
||||
this.fetching = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.friends
|
||||
background #fff
|
||||
border solid 1px rgba(0, 0, 0, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
> .title
|
||||
z-index 1
|
||||
margin 0
|
||||
padding 0 16px
|
||||
line-height 42px
|
||||
font-size 0.9em
|
||||
font-weight bold
|
||||
color #888
|
||||
box-shadow 0 1px rgba(0, 0, 0, 0.07)
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
> .initializing
|
||||
> .empty
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
> .user
|
||||
padding 16px
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
&:last-child
|
||||
border-bottom none
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .avatar-anchor
|
||||
display block
|
||||
float left
|
||||
margin 0 12px 0 0
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
width 42px
|
||||
height 42px
|
||||
margin 0
|
||||
border-radius 8px
|
||||
vertical-align bottom
|
||||
|
||||
> .body
|
||||
float left
|
||||
width calc(100% - 54px)
|
||||
|
||||
> .name
|
||||
margin 0
|
||||
font-size 16px
|
||||
line-height 24px
|
||||
color #555
|
||||
|
||||
> .username
|
||||
display block
|
||||
margin 0
|
||||
font-size 15px
|
||||
line-height 16px
|
||||
color #ccc
|
||||
|
||||
> .mk-follow-button
|
||||
position absolute
|
||||
top 16px
|
||||
right 16px
|
||||
|
||||
</style>
|
||||
196
src/client/app/desktop/views/pages/user/user.header.vue
Normal file
196
src/client/app/desktop/views/pages/user/user.header.vue
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
<template>
|
||||
<div class="header" :data-is-dark-background="user.bannerUrl != null">
|
||||
<div class="banner-container" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=2048)` : ''">
|
||||
<div class="banner" ref="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=2048)` : ''" @click="onBannerClick"></div>
|
||||
</div>
|
||||
<div class="fade"></div>
|
||||
<div class="container">
|
||||
<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=150`" alt="avatar"/>
|
||||
<div class="title">
|
||||
<p class="name">{{ user.name }}</p>
|
||||
<p class="username">@{{ acct }}</p>
|
||||
<p class="location" v-if="user.host === null && user.account.profile.location">%fa:map-marker%{{ user.account.profile.location }}</p>
|
||||
</div>
|
||||
<footer>
|
||||
<router-link :to="`/@${acct}`" :data-active="$parent.page == 'home'">%fa:home%概要</router-link>
|
||||
<router-link :to="`/@${acct}/media`" :data-active="$parent.page == 'media'">%fa:image%メディア</router-link>
|
||||
<router-link :to="`/@${acct}/graphs`" :data-active="$parent.page == 'graphs'">%fa:chart-bar%グラフ</router-link>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import getAcct from '../../../../../../common/user/get-acct';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['user'],
|
||||
computed: {
|
||||
acct() {
|
||||
return getAcct(this.user);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('load', this.onScroll);
|
||||
window.addEventListener('scroll', this.onScroll);
|
||||
window.addEventListener('resize', this.onScroll);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('load', this.onScroll);
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
window.removeEventListener('resize', this.onScroll);
|
||||
},
|
||||
methods: {
|
||||
onScroll() {
|
||||
const banner = this.$refs.banner as any;
|
||||
|
||||
const top = window.scrollY;
|
||||
|
||||
const z = 1.25; // 奥行き(小さいほど奥)
|
||||
const pos = -(top / z);
|
||||
banner.style.backgroundPosition = `center calc(50% - ${pos}px)`;
|
||||
|
||||
const blur = top / 32
|
||||
if (blur <= 10) banner.style.filter = `blur(${blur}px)`;
|
||||
},
|
||||
|
||||
onBannerClick() {
|
||||
if (!(this as any).os.isSignedIn || (this as any).os.i.id != this.user.id) return;
|
||||
|
||||
(this as any).apis.updateBanner((this as any).os.i, i => {
|
||||
this.user.bannerUrl = i.bannerUrl;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.header
|
||||
$banner-height = 320px
|
||||
$footer-height = 58px
|
||||
|
||||
overflow hidden
|
||||
background #f7f7f7
|
||||
box-shadow 0 1px 1px rgba(0, 0, 0, 0.075)
|
||||
|
||||
&[data-is-dark-background]
|
||||
> .banner-container
|
||||
> .banner
|
||||
background-color #383838
|
||||
|
||||
> .fade
|
||||
background linear-gradient(transparent, rgba(0, 0, 0, 0.7))
|
||||
|
||||
> .container
|
||||
> .title
|
||||
color #fff
|
||||
|
||||
> .name
|
||||
text-shadow 0 0 8px #000
|
||||
|
||||
> .banner-container
|
||||
height $banner-height
|
||||
overflow hidden
|
||||
background-size cover
|
||||
background-position center
|
||||
|
||||
> .banner
|
||||
height 100%
|
||||
background-color #f5f5f5
|
||||
background-size cover
|
||||
background-position center
|
||||
|
||||
> .fade
|
||||
$fade-hight = 78px
|
||||
|
||||
position absolute
|
||||
top ($banner-height - $fade-hight)
|
||||
left 0
|
||||
width 100%
|
||||
height $fade-hight
|
||||
|
||||
> .container
|
||||
max-width 1200px
|
||||
margin 0 auto
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
position absolute
|
||||
bottom 16px
|
||||
left 16px
|
||||
z-index 2
|
||||
width 160px
|
||||
height 160px
|
||||
margin 0
|
||||
border solid 3px #fff
|
||||
border-radius 8px
|
||||
box-shadow 1px 1px 3px rgba(0, 0, 0, 0.2)
|
||||
|
||||
> .title
|
||||
position absolute
|
||||
bottom $footer-height
|
||||
left 0
|
||||
width 100%
|
||||
padding 0 0 8px 195px
|
||||
color #656565
|
||||
font-family '游ゴシック', 'YuGothic', 'ヒラギノ角ゴ ProN W3', 'Hiragino Kaku Gothic ProN', 'Meiryo', 'メイリオ', sans-serif
|
||||
|
||||
> .name
|
||||
display block
|
||||
margin 0
|
||||
line-height 40px
|
||||
font-weight bold
|
||||
font-size 2em
|
||||
|
||||
> .username
|
||||
> .location
|
||||
display inline-block
|
||||
margin 0 16px 0 0
|
||||
line-height 20px
|
||||
opacity 0.8
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
> footer
|
||||
z-index 1
|
||||
height $footer-height
|
||||
padding-left 195px
|
||||
|
||||
> a
|
||||
display inline-block
|
||||
margin 0
|
||||
padding 0 16px
|
||||
height $footer-height
|
||||
line-height $footer-height
|
||||
color #555
|
||||
|
||||
&[data-active]
|
||||
border-bottom solid 4px $theme-color
|
||||
|
||||
> i
|
||||
margin-right 6px
|
||||
|
||||
> button
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
margin 8px
|
||||
padding 0
|
||||
width $footer-height - 16px
|
||||
line-height $footer-height - 16px - 2px
|
||||
font-size 1.2em
|
||||
color #777
|
||||
border solid 1px #eee
|
||||
border-radius 4px
|
||||
|
||||
&:hover
|
||||
color #555
|
||||
border solid 1px #ddd
|
||||
|
||||
</style>
|
||||
103
src/client/app/desktop/views/pages/user/user.home.vue
Normal file
103
src/client/app/desktop/views/pages/user/user.home.vue
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<div>
|
||||
<div ref="left">
|
||||
<x-profile :user="user"/>
|
||||
<x-photos :user="user"/>
|
||||
<x-followers-you-know v-if="os.isSignedIn && os.i.id != user.id" :user="user"/>
|
||||
<p v-if="user.host === null">%i18n:desktop.tags.mk-user.last-used-at%: <b><mk-time :time="user.account.lastUsedAt"/></b></p>
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
<mk-post-detail v-if="user.pinnedPost" :post="user.pinnedPost" :compact="true"/>
|
||||
<x-timeline class="timeline" ref="tl" :user="user"/>
|
||||
</main>
|
||||
<div>
|
||||
<div ref="right">
|
||||
<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>
|
||||
<mk-activity :user="user"/>
|
||||
<x-friends :user="user"/>
|
||||
<div class="nav"><mk-nav/></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XTimeline from './user.timeline.vue';
|
||||
import XProfile from './user.profile.vue';
|
||||
import XPhotos from './user.photos.vue';
|
||||
import XFollowersYouKnow from './user.followers-you-know.vue';
|
||||
import XFriends from './user.friends.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XTimeline,
|
||||
XProfile,
|
||||
XPhotos,
|
||||
XFollowersYouKnow,
|
||||
XFriends
|
||||
},
|
||||
props: ['user'],
|
||||
methods: {
|
||||
warp(date) {
|
||||
(this.$refs.tl as any).warp(date);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.home
|
||||
display flex
|
||||
justify-content center
|
||||
margin 0 auto
|
||||
max-width 1200px
|
||||
|
||||
> main
|
||||
> div > div
|
||||
> *:not(:last-child)
|
||||
margin-bottom 16px
|
||||
|
||||
> main
|
||||
padding 16px
|
||||
width calc(100% - 275px * 2)
|
||||
|
||||
> .timeline
|
||||
border solid 1px rgba(0, 0, 0, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
> div
|
||||
width 275px
|
||||
margin 0
|
||||
|
||||
&:first-child > div
|
||||
padding 16px 0 16px 16px
|
||||
|
||||
> p
|
||||
display block
|
||||
margin 0
|
||||
padding 0 12px
|
||||
text-align center
|
||||
font-size 0.8em
|
||||
color #aaa
|
||||
|
||||
&:last-child > div
|
||||
padding 16px 16px 16px 0
|
||||
|
||||
> .nav
|
||||
padding 16px
|
||||
font-size 12px
|
||||
color #aaa
|
||||
background #fff
|
||||
border solid 1px rgba(0, 0, 0, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
a
|
||||
color #999
|
||||
|
||||
i
|
||||
color #ccc
|
||||
|
||||
</style>
|
||||
88
src/client/app/desktop/views/pages/user/user.photos.vue
Normal file
88
src/client/app/desktop/views/pages/user/user.photos.vue
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<template>
|
||||
<div class="photos">
|
||||
<p class="title">%fa:camera%%i18n:desktop.tags.mk-user.photos.title%</p>
|
||||
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:desktop.tags.mk-user.photos.loading%<mk-ellipsis/></p>
|
||||
<div class="stream" v-if="!fetching && images.length > 0">
|
||||
<div v-for="image in images" class="img"
|
||||
:style="`background-image: url(${image.url}?thumbnail&size=256)`"
|
||||
></div>
|
||||
</div>
|
||||
<p class="empty" v-if="!fetching && images.length == 0">%i18n:desktop.tags.mk-user.photos.no-photos%</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
export default Vue.extend({
|
||||
props: ['user'],
|
||||
data() {
|
||||
return {
|
||||
images: [],
|
||||
fetching: true
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
(this as any).api('users/posts', {
|
||||
userId: this.user.id,
|
||||
withMedia: true,
|
||||
limit: 9
|
||||
}).then(posts => {
|
||||
posts.forEach(post => {
|
||||
post.media.forEach(media => {
|
||||
if (this.images.length < 9) this.images.push(media);
|
||||
});
|
||||
});
|
||||
this.fetching = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.photos
|
||||
background #fff
|
||||
border solid 1px rgba(0, 0, 0, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
> .title
|
||||
z-index 1
|
||||
margin 0
|
||||
padding 0 16px
|
||||
line-height 42px
|
||||
font-size 0.9em
|
||||
font-weight bold
|
||||
color #888
|
||||
box-shadow 0 1px rgba(0, 0, 0, 0.07)
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
> .stream
|
||||
display -webkit-flex
|
||||
display -moz-flex
|
||||
display -ms-flex
|
||||
display flex
|
||||
justify-content center
|
||||
flex-wrap wrap
|
||||
padding 8px
|
||||
|
||||
> .img
|
||||
flex 1 1 33%
|
||||
width 33%
|
||||
height 80px
|
||||
background-position center center
|
||||
background-size cover
|
||||
background-clip content-box
|
||||
border solid 2px transparent
|
||||
|
||||
> .initializing
|
||||
> .empty
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
</style>
|
||||
138
src/client/app/desktop/views/pages/user/user.profile.vue
Normal file
138
src/client/app/desktop/views/pages/user/user.profile.vue
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<div class="profile">
|
||||
<div class="friend-form" v-if="os.isSignedIn && os.i.id != user.id">
|
||||
<mk-follow-button :user="user" size="big"/>
|
||||
<p class="followed" v-if="user.isFollowed">%i18n:desktop.tags.mk-user.follows-you%</p>
|
||||
<p v-if="user.isMuted">%i18n:desktop.tags.mk-user.muted% <a @click="unmute">%i18n:desktop.tags.mk-user.unmute%</a></p>
|
||||
<p v-if="!user.isMuted"><a @click="mute">%i18n:desktop.tags.mk-user.mute%</a></p>
|
||||
</div>
|
||||
<div class="description" v-if="user.description">{{ user.description }}</div>
|
||||
<div class="birthday" v-if="user.host === null && user.account.profile.birthday">
|
||||
<p>%fa:birthday-cake%{{ user.account.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</p>
|
||||
</div>
|
||||
<div class="twitter" v-if="user.host === null && user.account.twitter">
|
||||
<p>%fa:B twitter%<a :href="`https://twitter.com/${user.account.twitter.screenName}`" target="_blank">@{{ user.account.twitter.screenName }}</a></p>
|
||||
</div>
|
||||
<div class="status">
|
||||
<p class="posts-count">%fa:angle-right%<a>{{ user.postsCount }}</a><b>投稿</b></p>
|
||||
<p class="following">%fa:angle-right%<a @click="showFollowing">{{ user.followingCount }}</a>人を<b>フォロー</b></p>
|
||||
<p class="followers">%fa:angle-right%<a @click="showFollowers">{{ user.followersCount }}</a>人の<b>フォロワー</b></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as age from 's-age';
|
||||
import MkFollowingWindow from '../../components/following-window.vue';
|
||||
import MkFollowersWindow from '../../components/followers-window.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['user'],
|
||||
computed: {
|
||||
age(): number {
|
||||
return age(this.user.account.profile.birthday);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showFollowing() {
|
||||
(this as any).os.new(MkFollowingWindow, {
|
||||
user: this.user
|
||||
});
|
||||
},
|
||||
|
||||
showFollowers() {
|
||||
(this as any).os.new(MkFollowersWindow, {
|
||||
user: this.user
|
||||
});
|
||||
},
|
||||
|
||||
mute() {
|
||||
(this as any).api('mute/create', {
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
this.user.isMuted = true;
|
||||
}, () => {
|
||||
alert('error');
|
||||
});
|
||||
},
|
||||
|
||||
unmute() {
|
||||
(this as any).api('mute/delete', {
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
this.user.isMuted = false;
|
||||
}, () => {
|
||||
alert('error');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.profile
|
||||
background #fff
|
||||
border solid 1px rgba(0, 0, 0, 0.075)
|
||||
border-radius 6px
|
||||
|
||||
> *:first-child
|
||||
border-top none !important
|
||||
|
||||
> .friend-form
|
||||
padding 16px
|
||||
border-top solid 1px #eee
|
||||
|
||||
> .mk-big-follow-button
|
||||
width 100%
|
||||
|
||||
> .followed
|
||||
margin 12px 0 0 0
|
||||
padding 0
|
||||
text-align center
|
||||
line-height 24px
|
||||
font-size 0.8em
|
||||
color #71afc7
|
||||
background #eefaff
|
||||
border-radius 4px
|
||||
|
||||
> .description
|
||||
padding 16px
|
||||
color #555
|
||||
border-top solid 1px #eee
|
||||
|
||||
> .birthday
|
||||
padding 16px
|
||||
color #555
|
||||
border-top solid 1px #eee
|
||||
|
||||
> p
|
||||
margin 0
|
||||
|
||||
> i
|
||||
margin-right 8px
|
||||
|
||||
> .twitter
|
||||
padding 16px
|
||||
color #555
|
||||
border-top solid 1px #eee
|
||||
|
||||
> p
|
||||
margin 0
|
||||
|
||||
> i
|
||||
margin-right 8px
|
||||
|
||||
> .status
|
||||
padding 16px
|
||||
color #555
|
||||
border-top solid 1px #eee
|
||||
|
||||
> p
|
||||
margin 8px 0
|
||||
|
||||
> i
|
||||
margin-left 8px
|
||||
margin-right 8px
|
||||
|
||||
</style>
|
||||
139
src/client/app/desktop/views/pages/user/user.timeline.vue
Normal file
139
src/client/app/desktop/views/pages/user/user.timeline.vue
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<div class="timeline">
|
||||
<header>
|
||||
<span :data-is-active="mode == 'default'" @click="mode = 'default'">投稿</span>
|
||||
<span :data-is-active="mode == 'with-replies'" @click="mode = 'with-replies'">投稿と返信</span>
|
||||
</header>
|
||||
<div class="loading" v-if="fetching">
|
||||
<mk-ellipsis-icon/>
|
||||
</div>
|
||||
<p class="empty" v-if="empty">%fa:R comments%このユーザーはまだ何も投稿していないようです。</p>
|
||||
<mk-posts ref="timeline" :posts="posts">
|
||||
<div slot="footer">
|
||||
<template v-if="!moreFetching">%fa:moon%</template>
|
||||
<template v-if="moreFetching">%fa:spinner .pulse .fw%</template>
|
||||
</div>
|
||||
</mk-posts>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
export default Vue.extend({
|
||||
props: ['user'],
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
mode: 'default',
|
||||
unreadCount: 0,
|
||||
posts: [],
|
||||
date: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mode() {
|
||||
this.fetch();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
empty(): boolean {
|
||||
return this.posts.length == 0;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.onDocumentKeydown);
|
||||
window.addEventListener('scroll', this.onScroll);
|
||||
|
||||
this.fetch(() => this.$emit('loaded'));
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.onDocumentKeydown);
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
},
|
||||
methods: {
|
||||
onDocumentKeydown(e) {
|
||||
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
|
||||
if (e.which == 84) { // [t]
|
||||
(this.$refs.timeline as any).focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
fetch(cb?) {
|
||||
(this as any).api('users/posts', {
|
||||
userId: this.user.id,
|
||||
untilDate: this.date ? this.date.getTime() : undefined,
|
||||
with_replies: this.mode == 'with-replies'
|
||||
}).then(posts => {
|
||||
this.posts = posts;
|
||||
this.fetching = false;
|
||||
if (cb) cb();
|
||||
});
|
||||
},
|
||||
more() {
|
||||
if (this.moreFetching || this.fetching || this.posts.length == 0) return;
|
||||
this.moreFetching = true;
|
||||
(this as any).api('users/posts', {
|
||||
userId: this.user.id,
|
||||
with_replies: this.mode == 'with-replies',
|
||||
untilId: this.posts[this.posts.length - 1].id
|
||||
}).then(posts => {
|
||||
this.moreFetching = false;
|
||||
this.posts = this.posts.concat(posts);
|
||||
});
|
||||
},
|
||||
onScroll() {
|
||||
const current = window.scrollY + window.innerHeight;
|
||||
if (current > document.body.offsetHeight - 16/*遊び*/) {
|
||||
this.more();
|
||||
}
|
||||
},
|
||||
warp(date) {
|
||||
this.date = date;
|
||||
this.fetch();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.timeline
|
||||
background #fff
|
||||
|
||||
> header
|
||||
padding 8px 16px
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
> span
|
||||
margin-right 16px
|
||||
line-height 27px
|
||||
font-size 18px
|
||||
color #555
|
||||
|
||||
&:not([data-is-active])
|
||||
color $theme-color
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .loading
|
||||
padding 64px 0
|
||||
|
||||
> .empty
|
||||
display block
|
||||
margin 0 auto
|
||||
padding 32px
|
||||
max-width 400px
|
||||
text-align center
|
||||
color #999
|
||||
|
||||
> [data-fa]
|
||||
display block
|
||||
margin-bottom 16px
|
||||
font-size 3em
|
||||
color #ccc
|
||||
|
||||
</style>
|
||||
53
src/client/app/desktop/views/pages/user/user.vue
Normal file
53
src/client/app/desktop/views/pages/user/user.vue
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<mk-ui>
|
||||
<div class="user" v-if="!fetching">
|
||||
<x-header :user="user"/>
|
||||
<x-home v-if="page == 'home'" :user="user"/>
|
||||
</div>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import parseAcct from '../../../../../../common/user/parse-acct';
|
||||
import Progress from '../../../../common/scripts/loading';
|
||||
import XHeader from './user.header.vue';
|
||||
import XHome from './user.home.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XHeader,
|
||||
XHome
|
||||
},
|
||||
props: {
|
||||
page: {
|
||||
default: 'home'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
user: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route: 'fetch'
|
||||
},
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
this.fetching = true;
|
||||
Progress.start();
|
||||
(this as any).api('users/show', parseAcct(this.$route.params.user)).then(user => {
|
||||
this.user = user;
|
||||
this.fetching = false;
|
||||
Progress.done();
|
||||
document.title = user.name + ' | Misskey';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue