user profile work

This commit is contained in:
Henry Jameson 2026-08-11 20:25:43 +03:00
commit 1f3b9a3905
7 changed files with 53 additions and 58 deletions

View file

@ -115,6 +115,7 @@ export const fetchTimeline = ({
publicAndExternal: MASTODON_PUBLIC_TIMELINE, publicAndExternal: MASTODON_PUBLIC_TIMELINE,
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
user: MASTODON_USER_TIMELINE_URL, user: MASTODON_USER_TIMELINE_URL,
userPinned: MASTODON_USER_TIMELINE_URL,
media: MASTODON_USER_TIMELINE_URL, media: MASTODON_USER_TIMELINE_URL,
list: MASTODON_LIST_TIMELINE_URL, list: MASTODON_LIST_TIMELINE_URL,
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
@ -130,6 +131,7 @@ export const fetchTimeline = ({
const twoArgs = new Set([ const twoArgs = new Set([
'user', 'user',
'userPinned',
'media', 'media',
'list', 'list',
'publicFavorites', 'publicFavorites',
@ -147,6 +149,7 @@ export const fetchTimeline = ({
const id = (() => { const id = (() => {
switch (timeline) { switch (timeline) {
case 'user': case 'user':
case 'userPinned':
case 'media': case 'media':
return userId return userId
case 'list': case 'list':
@ -163,6 +166,9 @@ export const fetchTimeline = ({
if (timeline === 'media') { if (timeline === 'media') {
params.onlyMedia = true params.onlyMedia = true
} }
if (timeline === 'userPinned') {
params.pinned = true
}
if (timeline === 'public') { if (timeline === 'public') {
params.local = true params.local = true
} }

View file

@ -28,12 +28,11 @@ library.add(faCircleNotch, faCog, faMinus, faArrowUp, faCirclePlus, faCheck)
const Timeline = { const Timeline = {
props: { props: {
timelineRef: Object, timelineRef: Object,
argument: String,
embedded: Boolean,
count: Number, count: Number,
pinnedStatusIds: Set,
inProfile: Boolean,
footerSlipgate: Object, // reference to an element where we should put our footer footerSlipgate: Object, // reference to an element where we should put our footer
embedded: Boolean,
inProfile: Boolean,
skipPinned: Boolean,
}, },
data() { data() {
return { return {
@ -58,17 +57,8 @@ const Timeline = {
}, },
filteredVisibleStatuses() { filteredVisibleStatuses() {
return [...this.timeline.visibleStatusesIds.keys()] return [...this.timeline.visibleStatusesIds.keys()]
.filter(
(id) =>
this.timelineRef.name !== 'user' ||
(id >= this.timeline.minId && id <= this.timeline.maxId),
)
.map((id) => this.timeline.statuses.get(id)) .map((id) => this.timeline.statuses.get(id))
}, .filter(({ pinned }) => this.skipPinned ? !pinned : true)
filteredPinnedStatusIds() {
return (this.pinnedStatusIds || []).filter(
(statusId) => this.timeline.statusesObject[statusId],
)
}, },
newStatusCount() { newStatusCount() {
return this.timeline.newStatusCount return this.timeline.newStatusCount
@ -109,17 +99,11 @@ const Timeline = {
), ),
} }
}, },
// id map of statuses which need to be hidden in the main list due to pinning logic
pinnedStatusIdsObject() {
return keyBy(this.pinnedStatusIds)
},
statusesToDisplay() { statusesToDisplay() {
const amount = this.timeline.visibleStatusesIds.size const amount = this.timeline.visibleStatusesIds.size
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80)) const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
const nonPinnedIndex = const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
this.virtualScrollIndex - this.filteredPinnedStatusIds.length const max = Math.min(amount, this.virtualScrollIndex + statusesPerSide)
const min = Math.max(0, nonPinnedIndex - statusesPerSide)
const max = Math.min(amount, nonPinnedIndex + statusesPerSide)
return new Set( return new Set(
[...this.timeline.visibleStatusesIds.keys()].slice(min, max), [...this.timeline.visibleStatusesIds.keys()].slice(min, max),
) )
@ -159,6 +143,11 @@ const Timeline = {
}, },
methods: { methods: {
timelineChange(newTimeline, oldTimeline) { timelineChange(newTimeline, oldTimeline) {
// TODO this might not be necessary if we optimize mergeOrAdd
const sameName = newTimeline?.name === oldTimeline?.name
const sameArgument = newTimeline?.argument === oldTimeline?.argument
if (sameName && sameArgument) return
if (oldTimeline && oldTimeline.name !== 'friends') { if (oldTimeline && oldTimeline.name !== 'friends') {
useTimelinesStore().clearTimeline(oldTimeline.name) useTimelinesStore().clearTimeline(oldTimeline.name)
} }

View file

@ -71,17 +71,6 @@
class="timeline" class="timeline"
role="feed" role="feed"
> >
<Conversation
v-for="statusId in filteredPinnedStatusIds"
:key="statusId + '-pinned'"
role="listitem"
class="status-fadein"
:status-id="statusId"
:pinned-status-ids-object="pinnedStatusIdsObject"
:in-profile="inProfile"
:profile-user-id="userId"
collapsable
/>
<Conversation <Conversation
v-for="status in filteredVisibleStatuses" v-for="status in filteredVisibleStatuses"
:key="status.id" :key="status.id"

View file

@ -9,7 +9,7 @@ import UserCard from 'src/components/user_card/user_card.vue'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useTimelinesStore } from 'src/stores/statuses.js' import { useTimelinesStore } from 'src/stores/timelines.js'
import { useUsersStore } from 'src/stores/users.js' import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -38,7 +38,6 @@ const UserProfile = {
useInterfaceStore().setForeignProfileBackground(this.user?.background_image) useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
}, },
unmounted() { unmounted() {
this.stopFetching()
useInterfaceStore().setForeignProfileBackground(null) useInterfaceStore().setForeignProfileBackground(null)
this.$store.dispatch('clearFollowers', this.userId) this.$store.dispatch('clearFollowers', this.userId)
this.$store.dispatch('clearFriends', this.userId) this.$store.dispatch('clearFriends', this.userId)
@ -101,6 +100,7 @@ const UserProfile = {
}, },
load(userNameOrId) { load(userNameOrId) {
const loadById = (userId) => { const loadById = (userId) => {
console.log('LOAD', userId)
this.userId = userId this.userId = userId
} }
@ -119,10 +119,11 @@ const UserProfile = {
if (user) { if (user) {
loadById(user.id) loadById(user.id)
} else { } else {
;(maybeId const promise = maybeId
? this.$store.dispatch('fetchUser', maybeId) ? useUsersStore().fetchUser(maybeId)
: this.$store.dispatch('fetchUserByName', maybeName) : useUsersStore().fetchUserByName(maybeName)
)
promise
.then(({ id }) => loadById(id)) .then(({ id }) => loadById(id))
.catch((reason) => { .catch((reason) => {
const errorMessage = get(reason, 'error.error') const errorMessage = get(reason, 'error.error')
@ -138,6 +139,7 @@ const UserProfile = {
} }
}, },
switchUser(userNameOrId) { switchUser(userNameOrId) {
console.log('USER SWITCH')
this.load(userNameOrId) this.load(userNameOrId)
}, },
onTabSwitch(tab) { onTabSwitch(tab) {

View file

@ -14,22 +14,32 @@
/> />
</div> </div>
<tab-switcher <tab-switcher
v-if="userId"
:active-tab="tab" :active-tab="tab"
:render-only-focused="true" :render-only-focused="true"
:on-switch="onTabSwitch" :on-switch="onTabSwitch"
> >
<div <div
key="statuses"
class="statuses" class="statuses"
:label="$t('user_card.statuses')" :label="$t('user_card.statuses')"
:count="user.statuses_count" :count="user.statuses_count"
:title="$t('user_profile.timeline_title')" :title="$t('user_profile.timeline_title')"
> >
<!--
<Timeline <Timeline
key="statuses" key="statuses"
:embedded="true" :timeline-ref="{ name: 'userPinned', argument: userId }"
timeline-name="user" embedded
:argument="userId" in-profile
:in-profile="true" :footer-slipgate="footerRef"
/>
-->
<Timeline
:timeline-ref="{ name: 'user', argument: userId }"
embedded
in-profile
skip-pinned
:footer-slipgate="footerRef" :footer-slipgate="footerRef"
/> />
</div> </div>
@ -71,24 +81,23 @@
<Timeline <Timeline
key="media" key="media"
:label="$t('user_card.media')" :label="$t('user_card.media')"
:disabled="!media.visibleStatuses.length" :disabled="!media.visibleStatusesIds.size"
:embedded="true"
:title="$t('user_card.media')" :title="$t('user_card.media')"
timeline-name="media" :timeline-ref="{ name: 'media', argument: userId }"
:argument="userId" embedded
:in-profile="true" in-profile
:footer-slipgate="footerRef" :footer-slipgate="footerRef"
/> />
<Timeline <Timeline
v-if="favoritesTabVisible" v-if="favoritesTabVisible"
key="favorites" key="favorites"
:label="$t('user_card.favorites')" :label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length" :disabled="!favorites.visibleStatusesIds.size"
:embedded="true"
:title="$t('user_card.favorites')" :title="$t('user_card.favorites')"
timeline-name="favorites" :timeline-ref="{ name: 'favorites', argument: userId }"
:argument="isUs ? undefined : userId" :argument="isUs ? undefined : userId"
:in-profile="true" embedded
in-profile
:footer-slipgate="footerRef" :footer-slipgate="footerRef"
/> />
</tab-switcher> </tab-switcher>

View file

@ -100,7 +100,7 @@ const fetchAndUpdate = ({
}) })
} }
const timelineFetcher = (timeline, argument, argumentKey, credentials) => { const timelineFetcher = (timeline, argument, credentials) => {
const state = { const state = {
interval: null interval: null
} }
@ -113,7 +113,6 @@ const timelineFetcher = (timeline, argument, argumentKey, credentials) => {
} = {}) => fetchAndUpdate({ } = {}) => fetchAndUpdate({
timeline, timeline,
argument, argument,
argumentKey,
credentials, credentials,
}, { }, {
maxId, maxId,

View file

@ -21,7 +21,7 @@ const emptyTl = (name, argument = null) => {
fetcher: null, fetcher: null,
} }
const property = USER_TIMELINES.has(name) ? 'userId' : ARGUMENT_MAP[name] const property = ARGUMENT_MAP[name]
if (property) { if (property) {
result[property] = argument result[property] = argument
@ -36,6 +36,9 @@ export const ARGUMENT_MAP = {
bookmarks: 'bookmarkFolderId', bookmarks: 'bookmarkFolderId',
quotes: 'statusId', quotes: 'statusId',
search: 'query', search: 'query',
user: 'userId',
userPinned: 'userId',
media: 'userId',
} }
export const defaultState = () => { export const defaultState = () => {
@ -58,7 +61,6 @@ export const defaultState = () => {
].map((name) => [name, emptyTl(name)])) ].map((name) => [name, emptyTl(name)]))
} }
const USER_TIMELINES = new Set(['user', 'userPinned', 'media', 'favorites'])
//const CUSTOM_SORT = new Set(['bookmarks', 'favorites']) //const CUSTOM_SORT = new Set(['bookmarks', 'favorites'])
export const useTimelinesStore = defineStore('timelines', { export const useTimelinesStore = defineStore('timelines', {
@ -82,7 +84,7 @@ export const useTimelinesStore = defineStore('timelines', {
// user. I.e. opening different user profiles makes request which could // user. I.e. opening different user profiles makes request which could
// return data late after user already viewing different user profile // return data late after user already viewing different user profile
// Same can happen with tags etc. // Same can happen with tags etc.
const property = USER_TIMELINES.has(name) ? 'userId' : ARGUMENT_MAP[name] const property = ARGUMENT_MAP[name]
if (property && timeline[property] !== argument) { if (property && timeline[property] !== argument) {
return return
@ -148,7 +150,6 @@ export const useTimelinesStore = defineStore('timelines', {
timeline.fetcher = timelineFetcher( timeline.fetcher = timelineFetcher(
timeline, timeline,
argument, argument,
ARGUMENT_MAP[timeline.name],
useOAuthStore().token, useOAuthStore().token,
) )