Re-implement user timeline in profile

This commit is contained in:
AkiraFukushima 2023-01-29 17:42:10 +09:00
parent 1724c808bf
commit 6be10b2e09
No known key found for this signature in database
GPG Key ID: B6E51BAC4DE1A957
6 changed files with 134 additions and 22 deletions

View File

@ -36,6 +36,7 @@ module.exports = {
'space-before-function-paren': 'off',
'vue/multi-word-component-names': 'off',
'vue/attributes-order': 'off',
'vue/attribute-hyphenation': 'off'
'vue/attribute-hyphenation': 'off',
'vue/no-v-html': 'off'
}
}

View File

@ -198,7 +198,7 @@ export default defineComponent({
}
.detail {
width: 340px;
width: 380px;
height: 100%;
border-left: 1px solid var(--theme-border-color);
}

View File

@ -19,7 +19,6 @@
:server="account.server"
v-on:update="updateToot"
v-on:delete="deleteToot"
@focusRight="focusSidebar"
@selectToot="focusToot(item)"
>
</toot>
@ -41,7 +40,6 @@ import { useRoute } from 'vue-router'
import { useStore } from '@/store'
import Toot from '@/components/organisms/Toot.vue'
import StatusLoading from '@/components/organisms/StatusLoading.vue'
import { EventEmitter } from '@/components/event'
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Home'
import { MUTATION_TYPES as SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
import { LocalAccount } from '~/src/types/localAccount'
@ -215,9 +213,6 @@ export default defineComponent({
const focusToot = (message: Entity.Status) => {
focusedId.value = message.uri + message.id
}
const focusSidebar = () => {
EventEmitter.emit('focus-sidebar')
}
return {
filteredTimeline,
@ -231,7 +226,6 @@ export default defineComponent({
deleteToot,
focusNext,
focusPrev,
focusSidebar,
focusToot,
openSideBar,
heading,

View File

@ -1,6 +1,6 @@
<template>
<div id="account_profile" role="article" aria-label="account profile" v-if="user">
<div class="header-background" v-bind:style="{ backgroundImage: 'url(' + user.header + ')' }">
<div class="header-background" :style="{ backgroundImage: 'url(' + user.header + ')' }">
<div class="header">
<div class="relationship" v-if="relationship !== null && !isOwnProfile">
<div class="follower-status">
@ -93,13 +93,18 @@
<dd v-html="data.value" @click.capture.prevent="metadataClick"></dd>
</dl>
</div>
<div class="timeline"></div>
<el-tabs class="timeline" v-model="activeTab" stretch v-if="account.account && account.server">
<el-tab-pane :label="precision(user.statuses_count) + ' Posts'" name="posts"
><Posts :user="user" :account="account.account" :server="account.server"
/></el-tab-pane>
<el-tab-pane :label="precision(user.following_count) + ' Following'" name="following">Following</el-tab-pane>
<el-tab-pane :label="precision(user.followers_count) + ' Followers'" name="followers">Followers</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref, reactive, onMounted, watch } from 'vue'
import { useI18next } from 'vue3-i18next'
import { useRoute } from 'vue-router'
import generator, { Entity, MegalodonInterface } from 'megalodon'
import { useStore } from '@/store'
@ -111,17 +116,18 @@ import { LocalServer } from '~/src/types/localServer'
import { ACTION_TYPES as LIST_MEMBERSHIP_ACTION } from '@/store/TimelineSpace/Modals/ListMembership'
import { ACTION_TYPES as MUTE_ACTION } from '@/store/TimelineSpace/Modals/MuteConfirm'
import FailoverImg from '@/components/atoms/FailoverImg.vue'
import Posts from './Profile/Posts.vue'
export default defineComponent({
name: 'Profile',
components: {
FailoverImg
FailoverImg,
Posts
},
setup() {
const win = (window as any) as MyWindow
const store = useStore()
const route = useRoute()
const i18n = useI18next()
const theme = computed(() => {
return {
@ -149,6 +155,7 @@ export default defineComponent({
return false
})
const identityProofs = ref<Array<Entity.IdentityProof>>([])
const activeTab = ref<'posts' | 'following' | 'followers'>('posts')
onMounted(async () => {
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
@ -274,6 +281,16 @@ export default defineComponent({
}
}
const precision = (num: number) => {
if (num > 1000) {
return `${(num / 1000).toPrecision(3)}K`
} else if (num > 1000000) {
return `${(num / 1000000).toPrecision(3)}M`
} else {
return num.toString()
}
}
return {
user,
relationship,
@ -293,7 +310,10 @@ export default defineComponent({
note,
noteClick,
identityProofs,
metadataClick
metadataClick,
precision,
activeTab,
account
}
}
})
@ -494,6 +514,10 @@ export default defineComponent({
.timeline {
font-size: calc(var(--base-font-size) * 0.85);
}
.timeline :deep(.el-tabs__item) {
--el-text-color-primary: var(--theme-secondary-color);
}
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<DynamicScroller :items="statuses" :min-item-size="86">
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<Toot
v-if="account && server"
:message="item"
:focused="item.uri + item.id === focusedId"
:overlaid="modalOpened"
:account="account"
:server="server"
@update="updateToot"
@delete="deleteToot"
@select-toot="focusToot(item)"
/>
</DynamicScrollerItem>
</template>
</DynamicScroller>
</template>
<script lang="ts">
import { defineComponent, PropType, onMounted, ref, watch, toRefs, computed } from 'vue'
import generator, { Entity, MegalodonInterface } from 'megalodon'
import Toot from '@/components/organisms/Toot.vue'
import { LocalAccount } from '~/src/types/localAccount'
import { LocalServer } from '~/src/types/localServer'
import { useStore } from '@/store'
export default defineComponent({
name: 'Posts',
components: { Toot },
props: {
user: {
type: Object as PropType<Entity.Account>,
required: true
},
account: {
type: Object as PropType<LocalAccount>,
required: true
},
server: {
type: Object as PropType<LocalServer>,
required: true
}
},
setup(props) {
const { user, account, server } = toRefs(props)
const store = useStore()
const statuses = ref<Array<Entity.Status>>([])
const client = ref<MegalodonInterface | null>(null)
const focusedId = ref<string | null>(null)
const userAgent = computed(() => store.state.App.userAgent)
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
onMounted(async () => {
client.value = generator(server.value.sns, server.value.baseURL, account.value.accessToken, userAgent.value)
const res = await client.value.getAccountStatuses(user.value.id)
statuses.value = res.data
})
watch(user, async current => {
if (client.value) {
const res = await client.value.getAccountStatuses(current.id)
statuses.value = res.data
}
})
const updateToot = (toot: Entity.Status) => {
statuses.value = statuses.value.map(s => {
if (s.id === toot.id) {
return toot
}
return s
})
}
const deleteToot = (toot: Entity.Status) => {
statuses.value = statuses.value.filter(s => s.id !== toot.id)
}
const focusToot = (toot: Entity.Status) => {
focusedId.value = toot.uri + toot.id
}
return {
statuses,
modalOpened,
updateToot,
deleteToot,
focusedId,
focusToot
}
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -305,7 +305,7 @@ export default defineComponent({
required: true
}
},
emits: ['selectToot', 'focusRight', 'focusLeft', 'update', 'delete', 'sizeChanged'],
emits: ['selectToot', 'update', 'delete', 'sizeChanged'],
setup(props, ctx) {
const store = useStore()
const route = useRoute()
@ -313,7 +313,7 @@ export default defineComponent({
const i18n = useI18next()
const win = (window as any) as MyWindow
const { focused, overlaid, message, filters, account, server } = toRefs(props)
const { l, h, r, b, f, o, p, i, x } = useMagicKeys()
const { r, b, f, o, p, i, x } = useMagicKeys()
const statusRef = ref<any>(null)
const showContent = ref(store.state.App.ignoreCW)
@ -397,12 +397,6 @@ export default defineComponent({
client.value = generator(server.value.sns, server.value.baseURL, account.value.accessToken, userAgent.value)
})
whenever(logicAnd(l, shortcutEnabled), () => {
ctx.emit('focusRight')
})
whenever(logicAnd(h, shortcutEnabled), () => {
ctx.emit('focusLeft')
})
whenever(logicAnd(r, shortcutEnabled), () => {
openReply()
})