diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8d547f500..43774adae 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1054,6 +1054,8 @@ _profile: metadataDescription: "プロフィールに表として4つまでの追加情報を表示することができます。" metadataLabel: "ラベル" metadataContent: "内容" + changeAvatar: "アバター画像を変更" + changeBanner: "バナー画像を変更" _exportOrImport: allNotes: "全てのノート" diff --git a/src/client/components/follow-button.vue b/src/client/components/follow-button.vue index 3b8f7454c..588e10119 100644 --- a/src/client/components/follow-button.vue +++ b/src/client/components/follow-button.vue @@ -1,9 +1,8 @@ <template> <button class="kpoogebi _button" - :class="{ wait, active: isFollowing || hasPendingFollowRequestFromYou, full }" + :class="{ wait, active: isFollowing || hasPendingFollowRequestFromYou, full, large }" @click="onClick" :disabled="wait" - v-if="isFollowing != null" > <template v-if="!wait"> <template v-if="hasPendingFollowRequestFromYou && user.isLocked"> @@ -44,6 +43,11 @@ export default defineComponent({ required: false, default: false, }, + large: { + type: Boolean, + required: false, + default: false, + }, }, data() { @@ -149,6 +153,12 @@ export default defineComponent({ font-size: 14px; } + &.large { + font-size: 16px; + height: 38px; + padding: 0 12px 0 16px; + } + &:not(.full) { width: 31px; } diff --git a/src/client/pages/about-misskey.vue b/src/client/pages/about-misskey.vue index fe7e84737..2f7993622 100644 --- a/src/client/pages/about-misskey.vue +++ b/src/client/pages/about-misskey.vue @@ -7,7 +7,7 @@ <div style="opacity: 0.5;">v{{ version }}</div> </div> </section> - <section class="_formItem" style="text-align: center;"> + <section class="_formItem" style="text-align: center; padding: 0 16px;"> {{ $t('_aboutMisskey.about') }} </section> <FormGroup> diff --git a/src/client/pages/channel.vue b/src/client/pages/channel.vue index ef4130854..fe9092880 100644 --- a/src/client/pages/channel.vue +++ b/src/client/pages/channel.vue @@ -20,7 +20,7 @@ </div> </div> - <XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed v-if="this.$store.getters.isSignedIn"/> + <XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed v-if="$store.getters.isSignedIn"/> <XTimeline class="_content _vMargin" src="channel" :channel="channelId" @before="before" @after="after"/> </div> diff --git a/src/client/pages/channels.vue b/src/client/pages/channels.vue index e42805128..c355623ba 100644 --- a/src/client/pages/channels.vue +++ b/src/client/pages/channels.vue @@ -1,6 +1,6 @@ <template> <div> - <div class="_section" style="padding: 0;" v-if="this.$store.getters.isSignedIn"> + <div class="_section" style="padding: 0;" v-if="$store.getters.isSignedIn"> <MkTab class="_content" v-model:value="tab"> <option value="featured"><Fa :icon="faFireAlt"/> {{ $t('_channel.featured') }}</option> <option value="following"><Fa :icon="faHeart"/> {{ $t('_channel.following') }}</option> diff --git a/src/client/pages/explore.vue b/src/client/pages/explore.vue index bca737370..3eecd8122 100644 --- a/src/client/pages/explore.vue +++ b/src/client/pages/explore.vue @@ -1,5 +1,5 @@ <template> -<div> +<div class="lznhrdub"> <div class="_section"> <MkInput v-model:value="query" :debounce="true" type="search"><template #icon><Fa :icon="faSearch"/></template><span>{{ $t('searchUser') }}</span></MkInput> @@ -186,6 +186,11 @@ export default defineComponent({ </script> <style lang="scss" scoped> +.lznhrdub { + max-width: 1400px; + margin: 0 auto; +} + .localfedi7 { color: #fff; padding: 16px; diff --git a/src/client/pages/pages.vue b/src/client/pages/pages.vue index 140bbcb87..5121e3641 100644 --- a/src/client/pages/pages.vue +++ b/src/client/pages/pages.vue @@ -1,6 +1,6 @@ <template> <div> - <MkTab v-model:value="tab" v-if="this.$store.getters.isSignedIn"> + <MkTab v-model:value="tab" v-if="$store.getters.isSignedIn"> <option value="featured"><Fa :icon="faFireAlt"/> {{ $t('_pages.featured') }}</option> <option value="my"><Fa :icon="faEdit"/> {{ $t('_pages.my') }}</option> <option value="liked"><Fa :icon="faHeart"/> {{ $t('_pages.liked') }}</option> diff --git a/src/client/pages/settings/profile.vue b/src/client/pages/settings/profile.vue index 4fc4783c4..b981cdcc1 100644 --- a/src/client/pages/settings/profile.vue +++ b/src/client/pages/settings/profile.vue @@ -1,8 +1,12 @@ <template> -<FormBase class="llvierxe"> - <div class="header _formItem" :style="{ backgroundImage: $store.state.i.bannerUrl ? `url(${ $store.state.i.bannerUrl })` : null }" @click="changeBanner"> - <MkAvatar class="avatar" :user="$store.state.i" :disable-preview="true" :disable-link="true" @click.stop="changeAvatar"/> - </div> +<FormBase> + <FormGroup> + <div class="_formItem _formPanel llvierxe" :style="{ backgroundImage: $store.state.i.bannerUrl ? `url(${ $store.state.i.bannerUrl })` : null }"> + <MkAvatar class="avatar" :user="$store.state.i"/> + </div> + <FormButton @click="changeAvatar" primary>{{ $t('_profile.changeAvatar') }}</FormButton> + <FormButton @click="changeBanner" primary>{{ $t('_profile.changeBanner') }}</FormButton> + </FormGroup> <FormInput v-model:value="name" :max="30"> <span>{{ $t('_profile.name') }}</span> @@ -245,30 +249,26 @@ export default defineComponent({ <style lang="scss" scoped> .llvierxe { - > .header { - position: relative; - height: 150px; - overflow: hidden; - background-size: cover; - background-position: center; - border-radius: 5px; - border: solid 1px var(--divider); - box-sizing: border-box; - cursor: pointer; + position: relative; + height: 150px; + background-size: cover; + background-position: center; - > .avatar { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: block; - width: 72px; - height: 72px; - margin: auto; - cursor: pointer; - box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.5); - } + > * { + pointer-events: none; + } + + > .avatar { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: block; + width: 72px; + height: 72px; + margin: auto; + box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.5); } } </style> diff --git a/src/client/pages/user/index.timeline.vue b/src/client/pages/user/index.timeline.vue index e60feca53..cca5f9baa 100644 --- a/src/client/pages/user/index.timeline.vue +++ b/src/client/pages/user/index.timeline.vue @@ -1,22 +1,24 @@ <template> -<div class="kjeftjfm" v-size="{ max: [500] }"> - <div class="with"> - <button class="_button" @click="with_ = null" :class="{ active: with_ === null }">{{ $t('notes') }}</button> - <button class="_button" @click="with_ = 'replies'" :class="{ active: with_ === 'replies' }">{{ $t('notesAndReplies') }}</button> - <button class="_button" @click="with_ = 'files'" :class="{ active: with_ === 'files' }">{{ $t('withFiles') }}</button> - </div> - <XNotes ref="timeline" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> +<div> + <MkTab v-model:value="with_" class="_vMargin"> + <option :value="null">{{ $t('notes') }}</option> + <option value="replies">{{ $t('notesAndReplies') }}</option> + <option value="files">{{ $t('withFiles') }}</option> + </MkTab> + <XNotes ref="timeline" class="_vMargin" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import XNotes from '@/components/notes.vue'; +import MkTab from '@/components/tab.vue'; import * as os from '@/os'; export default defineComponent({ components: { - XNotes + XNotes, + MkTab, }, props: { @@ -54,29 +56,3 @@ export default defineComponent({ }, }); </script> - -<style lang="scss" scoped> -.kjeftjfm { - > .with { - display: flex; - margin-bottom: var(--margin); - - > button { - flex: 1; - padding: 11px 8px 8px 8px; - border-bottom: solid 3px transparent; - - &.active { - color: var(--accent); - border-bottom-color: var(--accent); - } - } - } - - &.max-width_500px { - > .with { - font-size: 80%; - } - } -} -</style> diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue index ceafa7ba9..ec4dc4820 100644 --- a/src/client/pages/user/index.vue +++ b/src/client/pages/user/index.vue @@ -1,113 +1,191 @@ <template> -<div> - <div class="mk-user-page" v-if="user" v-size="{ max: [500] }" :class="{ _section: narrow === false }"> - <!-- 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="ftskorzw wide _section" v-if="user && narrow === false"> + <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"/> + <div class="name"> + <MkUserName :user="user" :nowrap="false" class="name"/> + <MkAcct :user="user" :detail="true" class="acct"/> + </div> + <div class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed"><span>{{ $t('followsYou') }}</span></div> + <div class="status"> + <MkA :to="userPage(user)" :class="{ active: page === 'index' }"> + <b>{{ number(user.notesCount) }}</b> + <span>{{ $t('notes') }}</span> + </MkA> + <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> + <b>{{ number(user.followingCount) }}</b> + <span>{{ $t('following') }}</span> + </MkA> + <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> + <b>{{ number(user.followersCount) }}</b> + <span>{{ $t('followers') }}</span> + </MkA> + </div> + <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> + </div> + <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> + <XActivity :user="user" :key="user.id" class="_vMargin"/> + <XPhotos :user="user" :key="user.id" class="_vMargin"/> + </div> <div class="main"> - <div class="profile _vMargin" :class="{ _section: narrow === true }"> - <MkRemoteCaution v-if="user.host != null" :href="user.url" class="_content _vMargin"/> - - <div class="_content _panel _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"/> - <div class="title"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <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> - <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> - </div> - <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"> - <MkA :to="userPage(user)" :class="{ active: page === 'index' }"> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $t('notes') }}</span> - </MkA> - <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $t('following') }}</span> - </MkA> - <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $t('followers') }}</span> - </MkA> - </div> + <div class="nav _vMargin"> + <MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link"> + <span>{{ $t('notes') }}</span> + </MkA> + <MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link"> + <span>{{ $t('clips') }}</span> + </MkA> + <MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link"> + <span>{{ $t('pages') }}</span> + </MkA> + <div class="actions"> + <button @click="menu" class="menu _button"><Fa :icon="faEllipsisH"/></button> + <MkFollowButton v-if="!$store.getters.isSignedIn || $store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> </div> </div> - <template v-if="page === 'index'"> - <div v-if="user.pinnedNotes.length > 0" :class="{ _section: narrow === true, _vMargin: narrow === false }"> - <XNote v-for="note in user.pinnedNotes" class="note _content _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/> + <div v-if="user.pinnedNotes.length > 0" class="_vMargin"> + <XNote v-for="note in user.pinnedNotes" class="note _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/> </div> - <div v-if="narrow === true" class="_section"> - <XPhotos class="_content _vMargin" :user="user" :key="user.id"/> - <XActivity class="_content _vMargin" :user="user" :key="user.id"/> - </div> - <div :class="{ _section: narrow === true, _vMargin: narrow === false }"> - <XUserTimeline :user="user" class="_content"/> + <div class="_vMargin"> + <XUserTimeline :user="user"/> </div> </template> - <XFollowList v-else-if="page === 'following'" :class="{ _section: narrow === true, _vMargin: narrow === false }" type="following" :user="user"/> - <XFollowList v-else-if="page === 'followers'" :class="{ _section: narrow === true, _vMargin: narrow === false }" type="followers" :user="user"/> - </div> - <div class="side" v-if="narrow === false"> - <XPhotos class="_vMargin" :user="user" :key="user.id"/> - <XActivity class="_vMargin" :user="user" :key="user.id"/> + <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_vMargin"/> + <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_vMargin"/> </div> </div> - <div v-else-if="error"> - <MkError @retry="fetch()"/> +</div> +<div class="ftskorzw narrow _section" v-else-if="user && narrow === true" 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 _content _vMargin"> + <MkRemoteCaution v-if="user.host != null" :href="user.url" class="_vMargin"/> + + <div class="_vMargin _panel 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="$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"/> + <div class="title"> + <MkUserName :user="user" :nowrap="false" class="name"/> + <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> + <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> + </div> + <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"> + <MkA :to="userPage(user)" :class="{ active: page === 'index' }"> + <b>{{ number(user.notesCount) }}</b> + <span>{{ $t('notes') }}</span> + </MkA> + <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> + <b>{{ number(user.followingCount) }}</b> + <span>{{ $t('following') }}</span> + </MkA> + <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> + <b>{{ number(user.followersCount) }}</b> + <span>{{ $t('followers') }}</span> + </MkA> + </div> + </div> </div> + + <template v-if="page === 'index'"> + <div class="_content _vMargin"> + <div 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"/> + </div> + <XPhotos :user="user" :key="user.id"/> + <XActivity :user="user" :key="user.id"/> + </div> + <div class="_content _vMargin"> + <XUserTimeline :user="user" class="_content"/> + </div> + </template> + <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _vMargin"/> + <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _vMargin"/> +</div> +<div v-else-if="error"> + <MkError @retry="fetch()"/> </div> </template> @@ -122,6 +200,7 @@ 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 MkTab from '@/components/tab.vue'; import Progress from '@/scripts/loading'; import parseAcct from '../../../misc/acct/parse'; import { getScrollPosition } from '@/scripts/scroll'; @@ -138,6 +217,7 @@ export default defineComponent({ MkContainer, MkRemoteCaution, MkFolder, + MkTab, XFollowList: defineAsyncComponent(() => import('./follow-list.vue')), XPhotos: defineAsyncComponent(() => import('./index.photos.vue')), XActivity: defineAsyncComponent(() => import('./index.activity.vue')), @@ -253,235 +333,392 @@ export default defineComponent({ </script> <style lang="scss" scoped> -.mk-user-page { - display: flex; - max-width: 1050px; +.ftskorzw.wide { + max-width: 1150px; margin: 0 auto; - - > .main { - flex: 1; - > .punished { - font-size: 0.8em; - padding: 16px; + > .banner-container { + position: relative; + height: 450px; + border-radius: 16px; + overflow: hidden; + 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; } + } - > .profile { - > ._content { - position: relative; - overflow: hidden; + > .contents { + display: flex; - > .banner-container { - position: relative; - height: 250px; - overflow: hidden; - background-size: cover; - background-position: center; + > .side { + width: 360px; - > .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; - } + > .avatar { + display: block; + width: 190px; + height: 190px; + margin: -140px auto 0 auto; + } - > .fade { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 78px; - background: linear-gradient(transparent, rgba(#000, 0.7)); - } + > .name { + padding: 16px 0px 20px 0; + text-align: center; - > .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: 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); - - > .bottom { - > * { - display: inline-block; - margin-right: 8px; - opacity: 0.8; - } - } - } - - > .avatar { + > .name { display: block; - position: absolute; - top: 170px; - left: 16px; - z-index: 2; - width: 120px; - height: 120px; - box-shadow: 1px 1px 3px rgba(#000, 0.2); + font-size: 2em; + font-weight: bold; } + } - > .description { - padding: 24px 24px 24px 154px; - font-size: 0.95em; + > .followed { + text-align: center; - > .empty { - margin: 0; - opacity: 0.5; - } + > span { + display: inline-block; + font-size: 80%; + padding: 8px 12px; + margin-bottom: 20px; + border: solid 1px var(--divider); + border-radius: 999px; } + } - > .fields { - padding: 24px; - font-size: 0.9em; - border-top: solid 1px var(--divider); + > .status { + display: flex; + padding: 20px 16px; + border-top: solid 1px var(--divider); + font-size: 90%; - > .field { - display: flex; - padding: 0; - margin: 0; - align-items: center; + > a { + flex: 1; + text-align: center; - &:not(:last-child) { - margin-bottom: 8px; - } - - > .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; - } + &.active { + color: var(--accent); } - &.system > .field > .name { + &:hover { + text-decoration: none; + } + + > b { + display: block; + line-height: 16px; + } + + > span { + font-size: 75%; } } + } - > .status { + > .description { + padding: 20px 16px; + border-top: solid 1px var(--divider); + font-size: 90%; + } + + > .fields { + padding: 20px 16px; + border-top: solid 1px var(--divider); + font-size: 90%; + + > .field { display: flex; - padding: 24px; - border-top: solid 1px var(--divider); + padding: 0; + margin: 0; + align-items: center; - > a { - flex: 1; - text-align: center; + &:not(:last-child) { + margin-bottom: 8px; + } - &.active { - color: var(--accent); - } + > .name { + width: 30%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: bold; + } - &:hover { - text-decoration: none; - } - - > b { - display: block; - line-height: 16px; - } - - > span { - font-size: 70%; - } + > .value { + width: 70%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0; } } } } - > .content { - margin-bottom: var(--margin); + > .main { + flex: 1; + margin-left: var(--margin); + + > .nav { + display: flex; + align-items: center; + margin-top: var(--margin); + font-size: 120%; + + > .link { + display: inline-block; + padding: 15px 24px 12px 24px; + text-align: center; + border-bottom: solid 3px transparent; + + &.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 { + > .main { + position: relative; + overflow: hidden; + + > .banner-container { + position: relative; + height: 250px; + overflow: hidden; + 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: 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); + + > .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); + } + + > .description { + padding: 24px 24px 24px 154px; + font-size: 0.95em; + + > .empty { + margin: 0; + opacity: 0.5; + } + } + + > .fields { + padding: 24px; + font-size: 0.9em; + border-top: solid 1px var(--divider); + + > .field { + display: flex; + padding: 0; + margin: 0; + align-items: center; + + &:not(:last-child) { + margin-bottom: 8px; + } + + > .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; + } + } + + &.system > .field > .name { + } + } + + > .status { + display: flex; + padding: 24px; + border-top: solid 1px var(--divider); + + > a { + flex: 1; + text-align: center; + + &.active { + color: var(--accent); + } + + &:hover { + text-decoration: none; + } + + > b { + display: block; + line-height: 16px; + } + + > span { + font-size: 70%; + } + } + } } } - > .side { - flex-basis: 300px; - margin-left: var(--margin); + > .content { + margin-bottom: var(--margin); } &.max-width_500px { - display: block; - - > .main > .profile > ._content { + > .profile > ._content { > .banner-container { height: 140px; diff --git a/src/client/style.scss b/src/client/style.scss index 104a19c1b..72a9ace7e 100644 --- a/src/client/style.scss +++ b/src/client/style.scss @@ -241,7 +241,7 @@ hr { background: var(--panel); border-radius: var(--radius); //border: var(--panelBorder); - box-shadow: var(--panelShadow); + //box-shadow: var(--panelShadow); overflow: hidden; } diff --git a/src/client/themes/d-black.json5 b/src/client/themes/d-black.json5 index b52e0fc39..497802c80 100644 --- a/src/client/themes/d-black.json5 +++ b/src/client/themes/d-black.json5 @@ -8,7 +8,7 @@ props: { divider: '#2d2d2d', - panel: '#0a0a0a', + panel: '#111111', panelHeaderBg: '@panel', panelHeaderDivider: '@divider', panelShadow: '" 0 8px 24px rgb(0 0 0 / 25%)', diff --git a/src/client/ui/default.vue b/src/client/ui/default.vue index d896f3230..c065b20a3 100644 --- a/src/client/ui/default.vue +++ b/src/client/ui/default.vue @@ -294,7 +294,7 @@ export default defineComponent({ -webkit-backdrop-filter: blur(32px); backdrop-filter: blur(32px); background-color: var(--header); - border-bottom: solid 1px var(--divider); + //border-bottom: solid 1px var(--divider); user-select: none; } diff --git a/src/client/ui/visitor.vue b/src/client/ui/visitor.vue index 0d8308888..ec9150d34 100644 --- a/src/client/ui/visitor.vue +++ b/src/client/ui/visitor.vue @@ -1,5 +1,5 @@ <template> -<DesignA/> +<DesignB/> <XCommon/> </template> diff --git a/src/client/ui/visitor/a.vue b/src/client/ui/visitor/a.vue index da09a9363..94866fdff 100644 --- a/src/client/ui/visitor/a.vue +++ b/src/client/ui/visitor/a.vue @@ -2,12 +2,6 @@ <div class="mk-app"> <div class="banner" v-if="$route.path === '/'" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> <div> - <header> - <MkA class="link" to="/">{{ $t('home') }}</MkA> - <MkA class="link" to="/announcements">{{ $t('announcements') }}</MkA> - <MkA class="link" to="/channels">{{ $t('channel') }}</MkA> - <MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName }) }}</MkA> - </header> <h1 v-if="meta"><img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1> <div class="about" v-if="meta"> <div class="desc" v-html="meta.description || $t('introMisskey')"></div> @@ -20,16 +14,6 @@ </div> <div class="banner-mini" v-else :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> <div> - <header> - <MkA class="link" to="/">{{ $t('home') }}</MkA> - <MkA class="link" to="/announcements">{{ $t('announcements') }}</MkA> - <MkA class="link" to="/channels">{{ $t('channel') }}</MkA> - <MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName }) }}</MkA> - <div class="action"> - <button class="_button primary" @click="signup()">{{ $t('signup') }}</button> - <button class="_button" @click="signin()">{{ $t('login') }}</button> - </div> - </header> <h1 v-if="meta"><img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1> </div> </div> @@ -62,10 +46,8 @@ import { host, instanceName } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; import MkPagination from '@/components/ui/pagination.vue'; -import XSigninDialog from '@/components/signin-dialog.vue'; -import XSignupDialog from '@/components/signup-dialog.vue'; import MkButton from '@/components/ui/button.vue'; -import XHeader from '../_common_/header.vue'; +import XHeader from './header.vue'; const DESKTOP_THRESHOLD = 1100; @@ -150,18 +132,6 @@ export default defineComponent({ onTransition() { if (window._scroll) window._scroll(); }, - - signin() { - os.popup(XSigninDialog, { - autoSet: true - }, {}, 'closed'); - }, - - signup() { - os.popup(XSignupDialog, { - autoSet: true - }, {}, 'closed'); - } } }); </script> @@ -264,59 +234,6 @@ export default defineComponent({ } > .main { - > header { - position: relative; - z-index: 1; - background: var(--panel); - padding: 0 32px; - text-align: left; - overflow: auto; - white-space: nowrap; - - > .link { - display: inline-block; - line-height: 60px; - padding: 0 0.7em; - - &.MkA-active { - box-shadow: 0 -2px 0 0 var(--accent) inset; - } - } - } - - > .banner { - position: relative; - width: 100%; - height: 200px; - background-size: cover; - background-position: center; - - &.asBg { - position: absolute; - left: 0; - height: 320px; - } - - &:after { - content: ""; - display: block; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 64px; - background: linear-gradient(transparent, var(--bg)); - } - - > h1 { - margin: 0; - text-align: center; - color: #fff; - text-shadow: 0 0 8px #000; - line-height: 200px; - } - } - > .contents { position: relative; z-index: 1; @@ -326,14 +243,6 @@ export default defineComponent({ top: 0; left: 0; z-index: 1000; - height: 60px; - width: 100%; - line-height: 60px; - text-align: center; - -webkit-backdrop-filter: blur(32px); - backdrop-filter: blur(32px); - background-color: var(--header); - border-bottom: 1px solid var(--divider); } > .powered-by { diff --git a/src/client/ui/visitor/b.vue b/src/client/ui/visitor/b.vue index 13f93a522..39ee4e3ed 100644 --- a/src/client/ui/visitor/b.vue +++ b/src/client/ui/visitor/b.vue @@ -8,7 +8,7 @@ <div class="desc" v-html="meta.description || $t('introMisskey')"></div> </div> <div class="action"> - <button class="_button primary" @click="signup()">{{ $t('signup') }}</button> + <button class="_buttonPrimary" @click="signup()">{{ $t('signup') }}</button> <button class="_button" @click="signin()">{{ $t('login') }}</button> </div> <div class="announcements panel"> @@ -27,13 +27,6 @@ </div> <div class="main"> - <header> - <MkA class="link" to="/">{{ $t('home') }}</MkA> - <MkA class="link" to="/announcements">{{ $t('announcements') }}</MkA> - <MkA class="link" to="/channels">{{ $t('channel') }}</MkA> - <MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName }) }}</MkA> - </header> - <div v-if="narrow" class="banner" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }"> <h1 v-if="meta"><img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1> </div> @@ -181,6 +174,7 @@ export default defineComponent({ left: 0; width: 500px; height: 100vh; + overflow: auto; background-position: center; background-size: cover; @@ -235,11 +229,9 @@ export default defineComponent({ box-sizing: border-box; text-align: center; border-radius: 999px; - background: var(--panel); - &.primary { - background: var(--accent); - color: #fff; + &._button { + background: var(--panel); } &:first-child { diff --git a/src/client/ui/visitor/header.vue b/src/client/ui/visitor/header.vue new file mode 100644 index 000000000..ff6fb9144 --- /dev/null +++ b/src/client/ui/visitor/header.vue @@ -0,0 +1,171 @@ +<template> +<div class="sqxihjet"> + <div class="content"> + <MkA to="/" class="link" active-class="active">{{ $t('home') }}</MkA> + <MkA to="/explore" class="link" active-class="active">{{ $t('explore') }}</MkA> + <MkA to="/featured" class="link" active-class="active">{{ $t('featured') }}</MkA> + <MkA to="/channels" class="link" active-class="active">{{ $t('channel') }}</MkA> + <div class="page link" v-if="info"> + <div class="title"> + <Fa v-if="info.icon" :icon="info.icon" :key="info.icon" class="icon"/> + <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true"/> + <span v-if="info.title" class="text">{{ info.title }}</span> + <MkUserName v-else-if="info.userName" :user="info.userName" :nowrap="false" class="text"/> + </div> + <button class="_button action" v-if="info.action" @click.stop="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button> + </div> + <div class="right"> + <button class="_button search" @click="search()"><Fa :icon="faSearch" class="icon"/><span>{{ $t('search') }}</span></button> + <button class="_buttonPrimary signup" @click="signup()">{{ $t('signup') }}</button> + <button class="_button login" @click="signin()">{{ $t('login') }}</button> + </div> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import { faSearch } from '@fortawesome/free-solid-svg-icons'; +import XSigninDialog from '@/components/signin-dialog.vue'; +import XSignupDialog from '@/components/signup-dialog.vue'; +import * as os from '@/os'; +import { search } from '@/scripts/search'; + +export default defineComponent({ + props: { + info: { + required: true + }, + }, + + data() { + return { + faSearch + }; + }, + + methods: { + signin() { + os.popup(XSigninDialog, { + autoSet: true + }, {}, 'closed'); + }, + + signup() { + os.popup(XSignupDialog, { + autoSet: true + }, {}, 'closed'); + }, + + search + } +}); +</script> + +<style lang="scss" scoped> +.sqxihjet { + $height: 60px; + line-height: $height; + background: var(--panel); + + > .content { + max-width: 1400px; + margin: 0 auto; + display: flex; + align-items: center; + + > .link { + display: inline-block; + padding: 0 16px; + line-height: $height - 4px; + border-top: solid 2px transparent; + border-bottom: solid 2px transparent; + + &.page { + border-bottom-color: var(--accent); + } + } + + > .page { + > .title { + display: inline-block; + vertical-align: bottom; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + + > .indicator { + position: absolute; + top: initial; + right: 8px; + top: 8px; + color: var(--indicator); + font-size: 12px; + animation: blink 1s infinite; + } + + > .icon + .text { + margin-left: 8px; + } + + > .avatar { + $size: 32px; + display: inline-block; + width: $size; + height: $size; + vertical-align: middle; + margin-right: 8px; + pointer-events: none; + } + + &._button { + &:hover { + color: var(--fgHighlighted); + } + } + + &.selected { + box-shadow: 0 -2px 0 0 var(--accent) inset; + color: var(--fgHighlighted); + } + } + + > .action { + padding: 0 0 0 16px; + } + } + + > .right { + margin-left: auto; + + > .search { + background: var(--bg); + border-radius: 999px; + width: 230px; + line-height: $height - 20px; + margin-right: 16px; + text-align: left; + + > * { + opacity: 0.7; + } + + > .icon { + padding: 0 16px; + } + } + + > .signup { + border-radius: 999px; + padding: 0 24px; + line-height: $height - 20px; + } + + > .login { + padding: 0 16px; + } + } + } +} +</style>