Newer
Older
<div class="mk-user-page" v-if="user" v-size="{ max: [500] }">
<!-- TODO -->
<!-- <div class="punished" v-if="user.isSuspended"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSuspended') }}</div> -->
<!-- <div class="punished" v-if="user.isSilenced"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSilenced') }}</div> -->
<div class="profile _section _fitBottom">
<MkRemoteCaution v-if="user.host != null" :href="user.url" class="_content _vMargin"/>
<div class="_content _vMargin" :key="user.id">
<div class="banner-container" :style="style">
<div class="banner" ref="banner" :style="style"></div>
<div class="fade"></div>
<div class="title">
<MkUserName class="name" :user="user" :nowrap="true"/>
<div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><Fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><Fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><Fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><Fa :icon="faRobot"/></span>
</div>
</div>
<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
<div class="actions" v-if="$store.getters.isSignedIn">
<button @click="menu" class="menu _button"><Fa :icon="faEllipsisH"/></button>
<MkFollowButton v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
</div>
</div>
<MkAvatar class="avatar" :user="user" :disable-preview="true"/>
<span class="username"><MkAcct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><Fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><Fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><Fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><Fa :icon="faRobot"/></span>
<div class="description">
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<div class="fields system">
<dl class="field" v-if="user.location">
<dt class="name"><Fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
<dd class="value">{{ user.location }}</dd>
</dl>
<dl class="field" v-if="user.birthday">
<dt class="name"><Fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
</dl>
<dl class="field">
<dt class="name"><Fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd>
</dl>
</div>
<div class="fields" v-if="user.fields.length > 0">
<dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name">
<Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
</dt>
<dd class="value">
<Mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
</dd>
</dl>
</div>
<div class="status">
<router-link :to="userPage(user)" :class="{ active: $route.name === 'user' }">
<b>{{ number(user.notesCount) }}</b>
<span>{{ $t('notes') }}</span>
</router-link>
<router-link :to="userPage(user, 'following')" :class="{ active: $route.name === 'userFollowing' }">
<b>{{ number(user.followingCount) }}</b>
<span>{{ $t('following') }}</span>
</router-link>
<router-link :to="userPage(user, 'followers')" :class="{ active: $route.name === 'userFollowers' }">
<b>{{ number(user.followersCount) }}</b>
<span>{{ $t('followers') }}</span>
</router-link>
<router-view :user="user"></router-view>
<template v-if="$route.name == 'user'">
<div class="_section">
<div class="_content _vMargin" v-if="user.pinnedNotes.length > 0">
<XNote v-for="note in user.pinnedNotes" class="note _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/>
<MkFolder :body-togglable="true" class="_content _vMargin" persist-key="user-images">
<template #header><Fa :icon="faImage" style="margin-right: 0.5em;"/>{{ $t('images') }}</template>
<div>
<XPhotos :user="user" :key="user.id"/>
</div>
</MkFolder>
<MkFolder :body-togglable="true" class="_content _vMargin" persist-key="user-activity">
<template #header><Fa :icon="faChartBar" style="margin-right: 0.5em;"/>{{ $t('activity') }}</template>
<div>
<XActivity :user="user" :key="user.id"/>
</div>
</MkFolder>
</div>
<div class="_section">
<XUserTimeline :user="user" class="_content"/>
</div>
import { defineComponent, defineAsyncComponent, computed } from 'vue';
import { faExclamationTriangle, faEllipsisH, faRobot, faLock, faBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons';
import { faCalendarAlt, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons';
import * as age from 's-age';
import XUserTimeline from './index.timeline.vue';
import XNote from '@/components/note.vue';
import MkFollowButton from '@/components/follow-button.vue';
import MkContainer from '@/components/ui/container.vue';
import MkFolder from '@/components/ui/folder.vue';
import MkRemoteCaution from '@/components/remote-caution.vue';
import Progress from '@/scripts/loading';
import { getScrollPosition } from '@/scripts/scroll';
import { getUserMenu } from '@/scripts/get-user-menu';
import number from '../../filters/number';
import { userPage, acct } from '../../filters/user';
import * as os from '@/os';
MkFolder,
XPhotos: defineAsyncComponent(() => import('./index.photos.vue')),
XActivity: defineAsyncComponent(() => import('./index.activity.vue')),
INFO: computed(() => this.user ? {
header: [{
userName: this.user,
avatar: this.user,
}],
action: {
icon: faEllipsisH,
handler: this.menu
}
} : null),
faExclamationTriangle, faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
};
},
computed: {
style(): any {
if (this.user.bannerUrl == null) return {};
return {
backgroundImage: `url(${ this.user.bannerUrl })`
};
},
age(): number {
return age(this.user.birthday);
}
},
watch: {
$route: 'fetch'
},
created() {
this.fetch();
},
mounted() {
window.requestAnimationFrame(this.parallaxLoop);
},
beforeUnmount() {
window.cancelAnimationFrame(this.parallaxAnimationId);
os.api('users/show', parseAcct(this.$route.params.user)).then(user => {
this.user = user;
}).catch(e => {
this.error = e;
}).finally(() => {
Progress.done();
});
},
menu(ev) {
os.modalMenu(getUserMenu(this.user), ev.currentTarget || ev.target);
},
parallaxLoop() {
this.parallaxAnimationId = window.requestAnimationFrame(this.parallaxLoop);
this.parallax();
},
parallax() {
const banner = this.$refs.banner as any;
if (banner == null) return;
if (top < 0) return;
const z = 1.75; // 奥行き(小さいほど奥)
const pos = -(top / z);
banner.style.backgroundPosition = `center calc(50% - ${pos}px)`;
},
pinnedNoteUpdated(oldValue, newValue) {
const i = this.user.pinnedNotes.findIndex(n => n === oldValue);
}
});
</script>
<style lang="scss" scoped>
.mk-user-page {
> .punished {
font-size: 0.8em;
padding: 16px;
}
> .banner-container {
position: relative;
height: 250px;
overflow: hidden;
border-radius: 12px;
> .banner {
height: 100%;
background-color: #4c5e6d;
background-size: cover;
background-position: center;
box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset;
will-change: background-position;
}
> .fade {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 78px;
background: linear-gradient(transparent, rgba(#000, 0.7));
}
> .followed {
position: absolute;
top: 12px;
left: 12px;
padding: 4px 8px;
background: rgba(0, 0, 0, 0.7);
font-size: 0.7em;
border-radius: 6px;
> .actions {
position: absolute;
top: 12px;
right: 12px;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
background: rgba(0, 0, 0, 0.2);
padding: 8px;
border-radius: 24px;
> .menu {
vertical-align: bottom;
height: 31px;
width: 31px;
color: #fff;
text-shadow: 0 0 8px #000;
font-size: 16px;
}
> .koudoku {
margin-left: 4px;
vertical-align: bottom;
}
> .title {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 0 0 8px 154px;
box-sizing: border-box;
color: #fff;
> .name {
display: block;
margin: 0;
line-height: 32px;
font-weight: bold;
font-size: 1.8em;
text-shadow: 0 0 8px #000;
}
> .bottom {
> * {
display: inline-block;
margin-right: 16px;
line-height: 20px;
opacity: 0.8;
&.username {
font-weight: bold;
}
}
}
}
> .title {
display: none;
text-align: center;
padding: 50px 8px 16px 8px;
font-weight: bold;
border-bottom: solid 1px var(--divider);
> .avatar {
display: block;
position: absolute;
top: 170px;
left: 16px;
z-index: 2;
width: 120px;
height: 120px;
box-shadow: 1px 1px 3px rgba(#000, 0.2);
> .description {
padding: 24px 24px 24px 154px;
font-size: 0.95em;
> .fields {
padding: 24px;
font-size: 0.9em;
border-top: solid 1px var(--divider);
> .field {
display: flex;
padding: 0;
margin: 0;
align-items: center;
> .name {
width: 30%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: bold;
text-align: center;
}
> .value {
width: 70%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
> .status {
display: flex;
padding: 24px;
border-top: solid 1px var(--divider);
}
}
}
}
> .content {
margin-bottom: var(--margin);
}
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
> .banner-container {
height: 140px;
> .fade {
display: none;
}
> .title {
display: none;
}
}
> .title {
display: block;
}
> .avatar {
top: 90px;
left: 0;
right: 0;
width: 92px;
height: 92px;
margin: auto;
}
> .description {
padding: 16px;
text-align: center;
}
> .fields {
padding: 16px;
}
> .status {
padding: 16px;
}
}
}