diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2020-10-17 20:12:00 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-10-17 20:12:00 +0900 |
| commit | 7199e6f4e0b3a2c2bc198e689c3e0cd0d0f0354a (patch) | |
| tree | 2263a06acec7fa21882366bae26d1a983ce21135 /src/client/pages/user | |
| parent | CW の input でも投稿ショートカットが動作するように (#6690) (diff) | |
| download | misskey-7199e6f4e0b3a2c2bc198e689c3e0cd0d0f0354a.tar.gz misskey-7199e6f4e0b3a2c2bc198e689c3e0cd0d0f0354a.tar.bz2 misskey-7199e6f4e0b3a2c2bc198e689c3e0cd0d0f0354a.zip | |
Migrate to Vue3 (#6587)
* Update reaction.vue
* fix bug
* wip
* wip
* wjio
* wip
* Revert "wip"
This reverts commit e427f2160adf4e8a4147006e25a89854edab0033.
* wip
* wip
* wip
* Update init.ts
* Update drive-window.vue
* wip
* wip
* Use PascalCase for components
* Use PascalCase for components
* update dep
* wip
* wip
* wip
* Update init.ts
* wip
* Update paging.ts
* Update test.vue
* watch deep
* wip
* lint
* wip
* wip
* wip
* wip
* wiop
* wip
* Update webpack.config.ts
* alllow null poll
* wip
* wip
* wip
* wiop
* UI redesign & refactor (#6714)
* wip
* wip
* wip
* wip
* wip
* Update drive.vue
* Update word-mute.vue
* wip
* wip
* wip
* clean up
* wip
* Update default.vue
* wip
* Update notes.vue
* Update mfm.ts
* Update index.home.vue
* Update post-form.vue
* Update post-form-attaches.vue
* wip
* Update post-form.vue
* Update sidebar.vue
* wip
* wip
* Update index.vue
* wip
* Update default.vue
* Update index.vue
* Update index.vue
* wip
* Update post-form-attaches.vue
* Update note.vue
* wip
* clean up
* Update notes.vue
* wip
* wip
* Update ja-JP.yml
* wip
* wip
* Update index.vue
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update default.vue
* wip
* Update _dark.json5
* wip
* wip
* wip
* clean up
* wip
* wip
* Update index.vue
* Update test.vue
* wip
* wip
* fix
* wip
* wip
* wip
* wip
* clena yop
* wip
* wip
* Update store.ts
* Update messaging-room.vue
* Update default.widgets.vue
* fix
* wip
* wip
* Update modal.vue
* wip
* Update os.ts
* Update os.ts
* Update deck.vue
* Update init.ts
* wip
* Update ja-JP.yml
* v-sizeは単にwindowのresizeを監視するだけで良いかもしれない
* Update modal.vue
* wip
* Update tooltip.ts
* wip
* wip
* wip
* wip
* wip
* Update image-viewer.vue
* wip
* wip
* Update style.scss
* Update style.scss
* Update visitor.vue
* wip
* Update init.ts
* Update init.ts
* wip
* wip
* Update visitor.vue
* Update visitor.vue
* Update visitor.vue
* Update visitor.vue
* wip
* wip
* Update modal.vue
* Update header.vue
* Update menu.vue
* Update about.vue
* Update about-misskey.vue
* wip
* wip
* Update visitor.vue
* Update tooltip.ts
* wip
* Update drive.vue
* wip
* Update style.scss
* Update header.vue
* wip
* wip
* Update users.user.vue
* Update announcements.vue
* wip
* wip
* wip
* Update emojis.vue
* wip
* Update emojis.vue
* Update style.scss
* Update users.vue
* wip
* Update style.scss
* wip
* Update welcome.entrance.vue
* Update radio.vue
* Update size.ts
* Update emoji-edit-dialog.vue
* wip
* Update emojis.vue
* wip
* Update emojis.vue
* Update emojis.vue
* Update emojis.vue
* wip
* wip
* wip
* wip
* Update file-dialog.vue
* wip
* wip
* Update token-generate-window.vue
* Update notification-setting-window.vue
* wip
* wip
* Update _error_.vue
* Update ja-JP.yml
* wip
* wip
* Update store.ts
* Update emojis.vue
* Update emojis.vue
* Update emojis.vue
* Update announcements.vue
* Update store.ts
* wip
* Update page-editor.vue
* wip
* wip
* Update modal.vue
* wip
* Update select-file.ts
* Update timeline.vue
* Update emojis.vue
* Update os.ts
* wip
* Update user-select.vue
* Update mfm.ts
* Update get-file-info.ts
* Update drive.vue
* Update init.ts
* Update mfm.ts
* wip
* wip
* Update window.vue
* Update note.vue
* wip
* wip
* Update user-info.vue
* wip
* wip
* wip
* wip
* wip
* Update header.vue
* Update header.vue
* wip
* Update explore.vue
* wip
* wip
* wip
* Update webpack.config.ts
* wip
* wip
* wip
* wip
* wip
* wip
* Update autocomplete.ts
* wip
* wip
* wip
* Update toast.vue
* wip
* Update post-form-dialog.vue
* wip
* wip
* wip
* wip
* wip
* Update users.vue
* wip
* Update explore.vue
* wip
* wip
* wip
* Update package.json
* wip
* Update icon-dialog.vue
* wip
* wip
* Update user-preview.ts
* wip
* wip
* wip
* wip
* wip
* Update instance.vue
* Update user-name.vue
* Update federation.vue
* Update instance.vue
* wip
* wip
* Update tag.vue
* wip
* wip
* wip
* wip
* wip
* Update instance.vue
* wip
* Update os.ts
* Update os.ts
* wip
* wip
* wip
* Update router.ts
* wip
* Update init.ts
* Update note.vue
* Update messages.vue
* wip
* wip
* wip
* wip
* wip
* google
* wip
* wip
* wip
* wip
* Update theme-editor.vue
* wip
* wip
* Update room.vue
* Update channel-editor.vue
* wip
* Update window.vue
* Update window.vue
* wip
* Update window.vue
* Update window.vue
* wip
* Update menu.vue
* wip
* wip
* wip
* wip
* Update messaging-room.vue
* wip
* Update post-form.vue
* Update default.widgets.vue
* Update window.vue
* wip
Diffstat (limited to 'src/client/pages/user')
| -rw-r--r-- | src/client/pages/user/follow-list.vue | 112 | ||||
| -rw-r--r-- | src/client/pages/user/index.activity.vue | 7 | ||||
| -rw-r--r-- | src/client/pages/user/index.photos.vue | 19 | ||||
| -rw-r--r-- | src/client/pages/user/index.timeline.vue | 9 | ||||
| -rw-r--r-- | src/client/pages/user/index.vue | 563 |
5 files changed, 330 insertions, 380 deletions
diff --git a/src/client/pages/user/follow-list.vue b/src/client/pages/user/follow-list.vue index 666e2d04fe..411109c890 100644 --- a/src/client/pages/user/follow-list.vue +++ b/src/client/pages/user/follow-list.vue @@ -1,31 +1,24 @@ <template> -<mk-pagination :pagination="pagination" #default="{items}" class="mk-following-or-followers" ref="list"> - <div class="user _panel" v-for="(user, i) in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id"> - <mk-avatar class="avatar" :user="user"/> - <div class="body"> - <div class="name"> - <router-link class="name" :to="user | userPage" v-user-preview="user.id"><mk-user-name :user="user"/></router-link> - <p class="acct">@{{ user | acct }}</p> - </div> - <div class="description" v-if="user.description" :title="user.description"> - <mfm :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :plain="true" :nowrap="true"/> - </div> - <mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/> +<div class="_section"> + <MkPagination :pagination="pagination" #default="{items}" class="mk-following-or-followers _content" ref="list"> + <div class="users"> + <MkUserInfo class="user" v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :user="user" :key="user.id"/> </div> - </div> -</mk-pagination> + </MkPagination> +</div> </template> <script lang="ts"> -import Vue from 'vue'; +import { defineComponent } from 'vue'; import parseAcct from '../../../misc/acct/parse'; -import MkFollowButton from '../../components/follow-button.vue'; -import MkPagination from '../../components/ui/pagination.vue'; +import MkUserInfo from '@/components/user-info.vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { userPage, acct } from '../../filters/user'; -export default Vue.extend({ +export default defineComponent({ components: { MkPagination, - MkFollowButton, + MkUserInfo, }, props: { @@ -55,83 +48,22 @@ export default Vue.extend({ '$route'() { this.$refs.list.reload(); } + }, + + methods: { + userPage, + + acct } }); </script> <style lang="scss" scoped> .mk-following-or-followers { - > .user { - display: flex; - padding: 16px; - - > .avatar { - display: block; - flex-shrink: 0; - margin: 0 12px 0 0; - width: 42px; - height: 42px; - border-radius: 8px; - } - - > .body { - display: flex; - width: calc(100% - 54px); - position: relative; - - > .name { - width: 45%; - - @media (max-width: 500px) { - width: 100%; - } - - > .name, - > .acct { - display: block; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - margin: 0; - } - - > .name { - font-size: 16px; - line-height: 24px; - } - - > .acct { - font-size: 15px; - line-height: 16px; - opacity: 0.7; - } - } - - > .description { - width: 55%; - line-height: 42px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - opacity: 0.7; - font-size: 14px; - padding-right: 40px; - padding-left: 8px; - box-sizing: border-box; - - @media (max-width: 500px) { - display: none; - } - } - - > .koudoku-button { - position: absolute; - top: 0; - bottom: 0; - right: 0; - margin: auto 0; - } - } + > .users { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-gap: var(--margin); } } </style> diff --git a/src/client/pages/user/index.activity.vue b/src/client/pages/user/index.activity.vue index 29dcca0664..30c02ec54a 100644 --- a/src/client/pages/user/index.activity.vue +++ b/src/client/pages/user/index.activity.vue @@ -5,10 +5,11 @@ </template> <script lang="ts"> -import Vue from 'vue'; +import { defineComponent } from 'vue'; import ApexCharts from 'apexcharts'; +import * as os from '@/os'; -export default Vue.extend({ +export default defineComponent({ props: { user: { type: Object, @@ -28,7 +29,7 @@ export default Vue.extend({ }; }, mounted() { - this.$root.api('charts/user/notes', { + os.api('charts/user/notes', { userId: this.user.id, span: 'day', limit: this.limit diff --git a/src/client/pages/user/index.photos.vue b/src/client/pages/user/index.photos.vue index 83a2618403..dcd4d1fce8 100644 --- a/src/client/pages/user/index.photos.vue +++ b/src/client/pages/user/index.photos.vue @@ -1,11 +1,11 @@ <template> <div class="ujigsodd"> - <mk-loading v-if="fetching"/> + <MkLoading v-if="fetching"/> <div class="stream" v-if="!fetching && images.length > 0"> <router-link v-for="(image, i) in images" :key="i" class="img" :style="`background-image: url(${thumbnail(image.file)})`" - :to="image.note | notePage" + :to="notePage(image.note)" ></router-link> </div> <p class="empty" v-if="!fetching && images.length == 0">{{ $t('nothing') }}</p> @@ -13,10 +13,12 @@ </template> <script lang="ts"> -import Vue from 'vue'; -import { getStaticImageUrl } from '../../scripts/get-static-image-url'; +import { defineComponent } from 'vue'; +import { getStaticImageUrl } from '@/scripts/get-static-image-url'; +import notePage from '../../filters/note'; +import * as os from '@/os'; -export default Vue.extend({ +export default defineComponent({ props: ['user'], data() { return { @@ -32,7 +34,7 @@ export default Vue.extend({ 'image/apng', 'image/vnd.mozilla.apng', ]; - this.$root.api('users/notes', { + os.api('users/notes', { userId: this.user.id, fileType: image, excludeNsfw: !this.$store.state.device.alwaysShowNsfw, @@ -57,18 +59,17 @@ export default Vue.extend({ ? getStaticImageUrl(image.thumbnailUrl) : image.thumbnailUrl; }, + notePage }, }); </script> <style lang="scss" scoped> .ujigsodd { - > .stream { display: flex; justify-content: center; flex-wrap: wrap; - padding: 8px; > .img { flex: 1 1 33%; @@ -79,7 +80,7 @@ export default Vue.extend({ background-size: cover; background-clip: content-box; border: solid 2px transparent; - border-radius: 4px; + border-radius: 6px; } } diff --git a/src/client/pages/user/index.timeline.vue b/src/client/pages/user/index.timeline.vue index 13ed49ea07..e60feca538 100644 --- a/src/client/pages/user/index.timeline.vue +++ b/src/client/pages/user/index.timeline.vue @@ -5,15 +5,16 @@ <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> - <x-notes ref="timeline" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> + <XNotes ref="timeline" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> </div> </template> <script lang="ts"> -import Vue from 'vue'; -import XNotes from '../../components/notes.vue'; +import { defineComponent } from 'vue'; +import XNotes from '@/components/notes.vue'; +import * as os from '@/os'; -export default Vue.extend({ +export default defineComponent({ components: { XNotes }, diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue index 21aa7bece0..bbbf15210b 100644 --- a/src/client/pages/user/index.vue +++ b/src/client/pages/user/index.vue @@ -1,144 +1,160 @@ <template> <div class="mk-user-page" v-if="user" v-size="{ max: [500] }"> - <portal to="title" v-if="user"><mk-user-name :user="user" :nowrap="false" class="name"/></portal> - <portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal> + <MkRemoteCaution v-if="user.host != null" :href="user.url" style="margin-bottom: var(--margin)"/> - <mk-remote-caution v-if="user.host != null" :href="user.url" style="margin-bottom: var(--margin)"/> - <div class="punished _panel" v-if="user.isSuspended"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSuspended') }}</div> - <div class="punished _panel" v-if="user.isSilenced"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSilenced') }}</div> - <div class="profile _panel" :key="user.id"> - <div class="banner-container" :style="style"> - <div class="banner" ref="banner" :style="style"></div> - <div class="fade"></div> + <!-- 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"> + <div class="_content" :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"> - <mk-user-name class="name" :user="user" :nowrap="true"/> + <MkUserName :user="user" :nowrap="false" class="name"/> <div class="bottom"> - <span class="username"><mk-acct :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> + <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" ref="menu"><fa :icon="faEllipsisH"/></button> - <mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> + <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> - <mk-avatar class="avatar" :user="user" :disable-preview="true"/> - <div class="title"> - <mk-user-name :user="user" :nowrap="false" class="name"/> - <div class="bottom"> - <span class="username"><mk-acct :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="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> </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() }} (<mk-time :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="user | userPage()" :class="{ active: $route.name === 'user' }"> - <b>{{ user.notesCount | number }}</b> - <span>{{ $t('notes') }}</span> - </router-link> - <router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }"> - <b>{{ user.followingCount | number }}</b> - <span>{{ $t('following') }}</span> - </router-link> - <router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }"> - <b>{{ user.followersCount | number }}</b> - <span>{{ $t('followers') }}</span> - </router-link> </div> </div> + <router-view :user="user"></router-view> <template v-if="$route.name == 'user'"> - <div class="pins"> - <x-note v-for="note in user.pinnedNotes" class="note" :note="note" @updated="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/> - </div> - <mk-container :body-togglable="true" class="content"> - <template #header><fa :icon="faImage"/>{{ $t('images') }}</template> - <div> - <x-photos :user="user" :key="user.id"/> + <div class="_section" v-if="user.pinnedNotes.length > 0"> + <div class="_content _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> - </mk-container> - <mk-container :body-togglable="true" class="content"> - <template #header><fa :icon="faChartBar"/>{{ $t('activity') }}</template> - <div style="padding:8px;"> - <x-activity :user="user" :key="user.id"/> - </div> - </mk-container> - <x-user-timeline :user="user"/> + <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> </template> </div> <div v-else-if="error"> - <mk-error @retry="fetch()"/> + <MkError @retry="fetch()"/> </div> </template> <script lang="ts"> -import Vue from 'vue'; +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 XUserMenu from '../../components/user-menu.vue'; -import XNote from '../../components/note.vue'; -import MkFollowButton from '../../components/follow-button.vue'; -import MkContainer from '../../components/ui/container.vue'; -import MkRemoteCaution from '../../components/remote-caution.vue'; -import Progress from '../../scripts/loading'; +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 parseAcct from '../../../misc/acct/parse'; -import { getScrollPosition } from '../../scripts/scroll'; +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'; -export default Vue.extend({ +export default defineComponent({ components: { XUserTimeline, XNote, MkFollowButton, MkContainer, MkRemoteCaution, - XPhotos: () => import('./index.photos.vue').then(m => m.default), - XActivity: () => import('./index.activity.vue').then(m => m.default), - }, - - metaInfo() { - return { - title: (this.user ? '@' + Vue.filter('acct')(this.user).replace('@', ' | ') : null) as string - }; + MkFolder, + XPhotos: defineAsyncComponent(() => import('./index.photos.vue')), + XActivity: defineAsyncComponent(() => import('./index.activity.vue')), }, data() { return { + INFO: computed(() => this.user ? { + header: [{ + userName: this.user, + avatar: this.user, + }], + action: { + icon: faEllipsisH, + handler: this.menu + } + } : null), user: null, error: null, parallaxAnimationId: null, @@ -169,15 +185,17 @@ export default Vue.extend({ mounted() { window.requestAnimationFrame(this.parallaxLoop); - this.$once('hook:beforeDestroy', () => { - window.cancelAnimationFrame(this.parallaxAnimationId); - }); + }, + + beforeUnmount() { + window.cancelAnimationFrame(this.parallaxAnimationId); }, methods: { fetch() { + if (this.$route.params.user == null) return; Progress.start(); - this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => { + os.api('users/show', parseAcct(this.$route.params.user)).then(user => { this.user = user; }).catch(e => { this.error = e; @@ -186,11 +204,8 @@ export default Vue.extend({ }); }, - menu() { - this.$root.new(XUserMenu, { - source: this.$refs.menu, - user: this.user - }); + menu(ev) { + os.modalMenu(getUserMenu(this.user), ev.currentTarget || ev.target); }, parallaxLoop() { @@ -213,8 +228,12 @@ export default Vue.extend({ pinnedNoteUpdated(oldValue, newValue) { const i = this.user.pinnedNotes.findIndex(n => n === oldValue); - Vue.set(this.user.pinnedNotes, i, newValue); + this.user.pinnedNotes[i] = newValue; }, + + number, + + userPage } }); </script> @@ -227,218 +246,214 @@ export default Vue.extend({ } > .profile { - position: relative; - margin-bottom: var(--margin); - overflow: hidden; - - > .banner-container { + > ._content { position: relative; - height: 250px; overflow: hidden; - background-size: cover; - background-position: center; - > .banner { - height: 100%; - background-color: #4c5e6d; + > .banner-container { + position: relative; + height: 250px; + overflow: hidden; background-size: cover; background-position: center; - box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; - will-change: background-position; - } + border-radius: 12px; - > .fade { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 78px; - background: linear-gradient(transparent, rgba(#000, 0.7)); - } + > .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; + } - > .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; - } + > .fade { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 78px; + background: linear-gradient(transparent, rgba(#000, 0.7)); + } - > .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; + > .followed { + position: absolute; + top: 12px; + left: 12px; + padding: 4px 8px; color: #fff; - text-shadow: 0 0 8px #000; - font-size: 16px; + background: rgba(0, 0, 0, 0.7); + font-size: 0.7em; + border-radius: 6px; } - > .koudoku { - margin-left: 4px; - vertical-align: bottom; - } - } + > .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; - > .title { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - padding: 0 0 8px 154px; - box-sizing: border-box; - color: #fff; + > .menu { + vertical-align: bottom; + height: 31px; + width: 31px; + color: #fff; + text-shadow: 0 0 8px #000; + font-size: 16px; + } - > .name { - display: block; - margin: 0; - line-height: 32px; - font-weight: bold; - font-size: 1.8em; - text-shadow: 0 0 8px #000; + > .koudoku { + margin-left: 4px; + vertical-align: bottom; + } } - > .bottom { - > * { - display: inline-block; - margin-right: 16px; - line-height: 20px; - opacity: 0.8; + > .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; + &.username { + font-weight: bold; + } } } } } - } - > .title { - display: none; - text-align: center; - padding: 50px 8px 16px 8px; - font-weight: bold; - border-bottom: solid 1px var(--divider); + > .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; + > .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); - } + > .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; + > .description { + padding: 24px 24px 24px 154px; + font-size: 0.95em; - > .empty { - margin: 0; - opacity: 0.5; + > .empty { + margin: 0; + opacity: 0.5; + } } - } - > .fields { - padding: 24px; - font-size: 0.9em; - border-top: solid 1px var(--divider); + > .fields { + padding: 24px; + font-size: 0.9em; + border-top: solid 1px var(--divider); - > .field { - display: flex; - padding: 0; - margin: 0; - align-items: center; + > .field { + display: flex; + padding: 0; + margin: 0; + align-items: center; - &:not(:last-child) { - margin-bottom: 8px; - } + &:not(:last-child) { + margin-bottom: 8px; + } - > .name { - width: 30%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-weight: bold; - text-align: 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; + > .value { + width: 70%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } } - } - &.system > .field > .name { + &.system > .field > .name { + } } - } - > .status { - display: flex; - padding: 24px; - border-top: solid 1px var(--divider); + > .status { + display: flex; + padding: 24px; + border-top: solid 1px var(--divider); - > a { - flex: 1; - text-align: center; + > a { + flex: 1; + text-align: center; - &.active { - color: var(--accent); - } + &.active { + color: var(--accent); + } - &:hover { - text-decoration: none; - } + &:hover { + text-decoration: none; + } - > b { - display: block; - line-height: 16px; - } + > b { + display: block; + line-height: 16px; + } - > span { - font-size: 70%; + > span { + font-size: 70%; + } } } } } - > .pins { - > .note { - margin-bottom: var(--margin); - } - } - > .content { margin-bottom: var(--margin); } &.max-width_500px { - > .profile { + > .profile > ._content { > .banner-container { height: 140px; |