Newer
Older
<transition name="fade" mode="out-in">
<div class="ftskorzw wide" v-if="user && narrow === false">
<MkRemoteCaution v-if="user.host != null" :href="user.url"/>
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<div class="banner-container" :style="style">
<div class="banner" ref="banner" :style="style"></div>
</div>
<div class="contents">
<div class="side _forceContainerFull_">
<MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
<div class="name">
<MkUserName :user="user" :nowrap="false" class="name"/>
<MkAcct :user="user" :detail="true" class="acct"/>
</div>
<div class="followed" v-if="$i && $i.id != user.id && user.isFollowed"><span>{{ $ts.followsYou }}</span></div>
<div class="status">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }">
<b>{{ number(user.notesCount) }}</b>
<span>{{ $ts.notes }}</span>
</MkA>
<MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
<b>{{ number(user.followingCount) }}</b>
<span>{{ $ts.following }}</span>
</MkA>
<MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
<b>{{ number(user.followersCount) }}</b>
<span>{{ $ts.followers }}</span>
</MkA>
</div>
<div class="description">
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/>
<p v-else class="empty">{{ $ts.noAccountDescription }}</p>
</div>
<div class="fields system">
<dl class="field" v-if="user.location">
<dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt>
<dd class="value">{{ user.location }}</dd>
</dl>
<dl class="field" v-if="user.birthday">
<dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt>
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
</dl>
<dl class="field">
<dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.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="$i" :custom-emojis="user.emojis" :colored="false"/>
</dd>
</dl>
</div>
<XActivity :user="user" :key="user.id" class="_gap"/>
<XPhotos :user="user" :key="user.id" class="_gap"/>
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<div class="main">
<div class="actions">
<button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
<MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
</div>
<template v-if="page === 'index'">
<div v-if="user.pinnedNotes.length > 0" class="_gap">
<XNote v-for="note in user.pinnedNotes" class="note _gap" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/>
</div>
<div class="_gap">
<XUserTimeline :user="user"/>
</div>
</template>
<XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_gap"/>
<XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_gap"/>
<XClips v-else-if="page === 'clips'" :user="user" class="_gap"/>
<XPages v-else-if="page === 'pages'" :user="user" class="_gap"/>
</div>
</div>
</div>
<MkSpacer v-else-if="user && narrow === true" :content-max="800">
<div class="ftskorzw narrow" v-size="{ max: [500] }">
<!-- TODO -->
<!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> -->
<!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> -->
<div class="profile">
<MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/>
<div class="_block main" :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="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span>
<span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span>
<span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span>
</div>
</div>
<span class="followed" v-if="$i && $i.id != user.id && user.isFollowed">{{ $ts.followsYou }}</span>
<div class="actions" v-if="$i">
<button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
<MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
</div>
</div>
<MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
<div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span>
<span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span>
<span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span>
</div>
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<div class="description">
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/>
<p v-else class="empty">{{ $ts.noAccountDescription }}</p>
</div>
<div class="fields system">
<dl class="field" v-if="user.location">
<dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt>
<dd class="value">{{ user.location }}</dd>
</dl>
<dl class="field" v-if="user.birthday">
<dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt>
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
</dl>
<dl class="field">
<dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.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="$i" :custom-emojis="user.emojis" :colored="false"/>
</dd>
</dl>
</div>
<div class="status">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }" v-click-anime>
<b>{{ number(user.notesCount) }}</b>
<span>{{ $ts.notes }}</span>
</MkA>
<MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }" v-click-anime>
<b>{{ number(user.followingCount) }}</b>
<span>{{ $ts.following }}</span>
</MkA>
<MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }" v-click-anime>
<b>{{ number(user.followersCount) }}</b>
<span>{{ $ts.followers }}</span>
</MkA>
<div class="contents">
<template v-if="page === 'index'">
<div>
<div v-if="user.pinnedNotes.length > 0" class="_gap">
<XNote v-for="note in user.pinnedNotes" class="note _block" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/>
<MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo>
<XPhotos :user="user" :key="user.id"/>
<XActivity :user="user" :key="user.id" style="margin-top: var(--margin);"/>
<div>
<XUserTimeline :user="user"/>
</div>
</template>
<XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/>
<XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/>
<XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/>
<XClips v-else-if="page === 'clips'" :user="user" class="_gap"/>
<XPages v-else-if="page === 'pages'" :user="user" class="_gap"/>
<XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/>
</div>
</MkSpacer>
<MkError v-else-if="error" @retry="fetch()"/>
<MkLoading v-else/>
</transition>
import { defineComponent, defineAsyncComponent, computed } from 'vue';
import * as age from 's-age';
import XUserTimeline from './index.timeline.vue';
import XNote from '@client/components/note.vue';
import MkFollowButton from '@client/components/follow-button.vue';
import MkContainer from '@client/components/ui/container.vue';
import MkFolder from '@client/components/ui/folder.vue';
import MkRemoteCaution from '@client/components/remote-caution.vue';
import MkTab from '@client/components/tab.vue';
import Progress from '@client/scripts/loading';
import { getScrollPosition } from '@client/scripts/scroll';
import { getUserMenu } from '@client/scripts/get-user-menu';
import number from '@client/filters/number';
import { userPage, acct as getAcct } from '@client/filters/user';
XFollowList: defineAsyncComponent(() => import('./follow-list.vue')),
XReactions: defineAsyncComponent(() => import('./reactions.vue')),
XClips: defineAsyncComponent(() => import('./clips.vue')),
XPages: defineAsyncComponent(() => import('./pages.vue')),
XPhotos: defineAsyncComponent(() => import('./index.photos.vue')),
XActivity: defineAsyncComponent(() => import('./index.activity.vue')),
props: {
acct: {
type: String,
required: true
},
page: {
type: String,
required: false,
default: 'index'
}
},
title: this.user.name ? `${this.user.name} (@${this.user.username})` : `@${this.user.username}`,
bg: 'var(--bg)',
tabs: [{
active: this.page === 'index',
title: this.$ts.overview,
icon: 'fas fa-home',
onClick: () => { this.$router.push('/@' + getAcct(this.user)); },
}, ...(this.$i && (this.$i.id === this.user.id)) || this.user.publicReactions ? [{
active: this.page === 'reactions',
title: this.$ts.reaction,
icon: 'fas fa-laugh',
onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/reactions'); },
active: this.page === 'clips',
title: this.$ts.clips,
icon: 'fas fa-paperclip',
onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/clips'); },
}, {
active: this.page === 'pages',
title: this.$ts.pages,
icon: 'fas fa-file-alt',
onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/pages'); },
}, {
active: this.page === 'gallery',
title: this.$ts.gallery,
icon: 'fas fa-icons',
onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/gallery'); },
};
},
computed: {
style(): any {
if (this.user.bannerUrl == null) return {};
return {
backgroundImage: `url(${ this.user.bannerUrl })`
};
},
age(): number {
return age(this.user.birthday);
}
},
watch: {
},
created() {
this.fetch();
},
mounted() {
window.requestAnimationFrame(this.parallaxLoop);
},
beforeUnmount() {
window.cancelAnimationFrame(this.parallaxAnimationId);
this.user = user;
}).catch(e => {
this.error = e;
}).finally(() => {
Progress.done();
});
},
os.popupMenu(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);
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.125s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
background-size: cover;
background-position: center;
> .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;
> .name {
padding: 16px 0px 20px 0;
text-align: center;
> .name {
display: block;
font-weight: bold;
}
}
> .followed {
text-align: center;
> span {
display: inline-block;
font-size: 80%;
padding: 8px 12px;
margin-bottom: 20px;
border-radius: 999px;
}
}
> .status {
display: flex;
padding: 20px 16px;
font-size: 90%;
> a {
flex: 1;
text-align: center;
&.active {
color: var(--accent);
font-size: 90%;
> .field {
display: flex;
padding: 0;
margin: 0;
align-items: center;
&:not(:last-child) {
margin-bottom: 8px;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: bold;
}
white-space: nowrap;
text-overflow: ellipsis;
margin: 0;
}
}
}
}
> .nav {
display: flex;
align-items: center;
margin-top: var(--margin);
> .link {
display: inline-block;
padding: 15px 24px 12px 24px;
text-align: center;
border-bottom: solid 3px transparent;
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
&.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
&:not(.active):hover {
color: var(--fgHighlighted);
}
> .icon {
margin-right: 6px;
}
}
> .actions {
display: flex;
align-items: center;
margin-left: auto;
> .menu {
padding: 12px 16px;
}
}
}
}
}
}
.ftskorzw.narrow {
> .punished {
font-size: 0.8em;
padding: 16px;
}
> .profile {
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
background-size: cover;
background-position: center;
> .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;
color: #fff;
background: rgba(0, 0, 0, 0.7);
font-size: 0.7em;
border-radius: 6px;
}
> .actions {
position: absolute;
top: 12px;
right: 12px;
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, 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;
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;
}
> .title {
display: none;
text-align: center;
padding: 50px 8px 16px 8px;
font-weight: bold;
> .bottom {
> * {
display: inline-block;
margin-right: 8px;
opacity: 0.8;
}
}
> .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);
}
> .field {
display: flex;
padding: 0;
margin: 0;
align-items: center;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: bold;
text-align: center;
> b {
display: block;
line-height: 16px;
}
> span {
font-size: 70%;
> .contents {
> .content {
margin-bottom: var(--margin);
}
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
> .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;
}
}
> .contents {
> .nav {
font-size: 80%;
}