Merge pull request 'Users/Statuses/Notifications Pinia migration and refactor' (#3555) from users-statuses-pinia into develop

Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3555
This commit is contained in:
HJ 2026-09-01 16:32:31 +00:00
commit d603aac31b
217 changed files with 7961 additions and 5697 deletions

View file

@ -0,0 +1 @@
More timelines now can utilize WebSocket streaming

View file

@ -0,0 +1 @@
switches between streaming and polling should be more reliable now

View file

@ -0,0 +1,3 @@
Added an indicator next to instance's name showing WebSocket connection status (if enabled).
Timeline no longer show "loading" indicator at the bottom when fetching newer posts.
Added small indicator on top of timeline when new posts are being fetched

View file

@ -21,6 +21,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useShoutStore } from 'src/stores/shout.js'
import { useUsersStore } from 'src/stores/users.js'
// Helper to unwrap reactive proxies
window.toValue = (x) => JSON.parse(JSON.stringify(x))
@ -153,11 +154,8 @@ export default {
...(navbarColumnStretch ? ['-column-stretch'] : []),
]
},
currentUser() {
return this.$store.state.users.currentUser
},
userBackground() {
return this.currentUser.background_image
return this.currentUser?.background_image
},
foreignProfileBackground() {
return (
@ -246,6 +244,7 @@ export default {
'styleDataUsed',
'layoutType',
]),
...mapState(useUsersStore, ['currentUser']),
...mapState(useInstanceStore, ['styleDataUsed']),
...mapState(useInstanceCapabilitiesStore, [
'suggestionsEnabled',

View file

@ -26,7 +26,7 @@
class="column -scrollable"
:class="{ '-show-scrollbar': showScrollbars }"
>
<user-panel />
<UserPanel />
<template v-if="layoutType !== 'mobile'">
<NavPanel />
<InstanceSpecificPanel v-if="showInstanceSpecificPanel" />

View file

@ -63,7 +63,7 @@ export const paramsString = (params = {}) => {
}
export const promisedRequest = async ({
method,
method = 'GET',
url,
payload,
formData,
@ -124,7 +124,7 @@ export const promisedRequest = async ({
const { ok, status } = response
if (ok) {
return { response, status, data }
return { response, status, data, timestamp: Date.now() }
} else {
throw new StatusCodeError(response.status, data, { url, options }, response)
}

View file

@ -13,12 +13,12 @@ const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
const MASTODON_PASSWORD_RESET_URL = ({ email }) =>
`/auth/password${paramsString({ email })}`
const MASTODON_FOLLOWING_URL = (
export const MASTODON_FOLLOWING_URL = (
id,
{ minId, maxId, sinceId, limit, withRelationships },
) =>
`/api/v1/accounts/${id}/following${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
const MASTODON_FOLLOWERS_URL = (
export const MASTODON_FOLLOWERS_URL = (
id,
{ minId, maxId, sinceId, limit, withRelationships },
) =>
@ -26,15 +26,17 @@ const MASTODON_FOLLOWERS_URL = (
export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}`
const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context`
const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source`
const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history`
export const MASTODON_STATUS_SOURCE_URL = (id) =>
`/api/v1/statuses/${id}/source`
export const MASTODON_STATUS_HISTORY_URL = (id) =>
`/api/v1/statuses/${id}/history`
const MASTODON_USER_URL = '/api/v1/accounts'
const MASTODON_USER_LOOKUP_URL = ({ acct }) =>
`/api/v1/accounts/lookup${paramsString({ acct })}`
const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}`
const MASTODON_STATUS_FAVORITEDBY_URL = (id) =>
export const MASTODON_STATUS_FAVORITEDBY_URL = (id) =>
`/api/v1/statuses/${id}/favourited_by`
const MASTODON_STATUS_REBLOGGEDBY_URL = (id) =>
export const MASTODON_STATUS_REBLOGGEDBY_URL = (id) =>
`/api/v1/statuses/${id}/reblogged_by`
const MASTODON_SEARCH_2 = ({
q,
@ -51,7 +53,7 @@ const MASTODON_SEARCH_2 = ({
const MASTODON_USER_SEARCH_URL = ({ q, resolve }) =>
`/api/v1/accounts/search${paramsString({ q, resolve })}`
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
const PLEROMA_EMOJI_REACTIONS_URL = (id) =>
export const PLEROMA_EMOJI_REACTIONS_URL = (id) =>
`/api/v1/pleroma/statuses/${id}/reactions`
const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) =>
`/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}`
@ -174,15 +176,21 @@ export const fetchStatusSource = ({ id, credentials }) =>
credentials,
}).then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) }))
export const fetchStatusHistory = ({ status, credentials }) =>
export const fetchStatusHistory = ({ id, credentials }) =>
promisedRequest({
url: MASTODON_STATUS_HISTORY_URL(status.id),
url: MASTODON_STATUS_HISTORY_URL(id),
credentials,
}).then(({ data, ...rest }) => {
return [...data].reverse().map((item) => {
item.originalStatus = status
return { ...rest, data: parseStatus(item) }
})
return {
...rest,
data: [...data].reverse().map((item) => {
// History data is missing a lot of stuff present in original
// but we're really only missing the id for the timeago, the
// rest seem to render just fine.
item.id = id
return parseStatus(item)
}),
}
})
export const listEmojiPacks = ({ page, pageSize, credentials }) =>

View file

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

View file

@ -18,7 +18,7 @@ const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
const ALIASES_URL = '/api/pleroma/aliases'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
export const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
@ -27,15 +27,16 @@ const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
const MASTODON_DISMISS_NOTIFICATION_URL = (id) =>
export const MASTODON_DISMISS_NOTIFICATION_URL = (id) =>
`/api/v1/notifications/${id}/dismiss`
const MASTODON_FAVORITE_URL = (id) => `/api/v1/statuses/${id}/favourite`
const MASTODON_UNFAVORITE_URL = (id) => `/api/v1/statuses/${id}/unfavourite`
const MASTODON_RETWEET_URL = (id) => `/api/v1/statuses/${id}/reblog`
const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog`
const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}`
const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow`
const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow`
export const MASTODON_FAVORITE_URL = (id) => `/api/v1/statuses/${id}/favourite`
export const MASTODON_UNFAVORITE_URL = (id) =>
`/api/v1/statuses/${id}/unfavourite`
export const MASTODON_RETWEET_URL = (id) => `/api/v1/statuses/${id}/reblog`
export const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog`
export const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}`
export const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow`
export const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow`
const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
const MASTODON_APPROVE_USER_URL = (id) =>
@ -43,49 +44,54 @@ const MASTODON_APPROVE_USER_URL = (id) =>
const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject`
const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) =>
`/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}`
const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
export const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
export const MASTODON_LIST_URL = (id = '') => `/api/v1/lists/${id}`
export const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts`
const MASTODON_USER_BLOCKS_URL = ({
export const MASTODON_USER_BLOCKS_URL = ({
maxId,
sinceId,
limit,
withRelationships,
}) =>
`/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}`
const MASTODON_USER_MUTES_URL = ({
export const MASTODON_USER_MUTES_URL = ({
maxId,
sinceId,
limit,
withRelationships,
}) =>
`/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}`
const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block`
const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock`
const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute`
const MASTODON_UNMUTE_USER_URL = (id) => `/api/v1/accounts/${id}/unmute`
const MASTODON_REMOVE_USER_FROM_FOLLOWERS = (id) =>
export const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block`
export const MASTODON_UNBLOCK_USER_URL = (id) =>
`/api/v1/accounts/${id}/unblock`
export const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute`
export const MASTODON_UNMUTE_USER_URL = (id) => `/api/v1/accounts/${id}/unmute`
export const MASTODON_REMOVE_USER_FROM_FOLLOWERS_URL = (id) =>
`/api/v1/accounts/${id}/remove_from_followers`
const MASTODON_USER_NOTE_URL = (id) => `/api/v1/accounts/${id}/note`
const MASTODON_BOOKMARK_STATUS_URL = (id) => `/api/v1/statuses/${id}/bookmark`
const MASTODON_UNBOOKMARK_STATUS_URL = (id) =>
export const MASTODON_USER_NOTE_URL = (id) => `/api/v1/accounts/${id}/note`
export const MASTODON_BOOKMARK_STATUS_URL = (id) =>
`/api/v1/statuses/${id}/bookmark`
export const MASTODON_UNBOOKMARK_STATUS_URL = (id) =>
`/api/v1/statuses/${id}/unbookmark`
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
const MASTODON_VOTE_URL = (id) => `/api/v1/polls/${id}/votes`
const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
const MASTODON_REPORT_USER_URL = '/api/v1/reports'
const MASTODON_PIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/pin`
const MASTODON_UNPIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/unpin`
const MASTODON_MUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/unmute`
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
export const MASTODON_PIN_OWN_STATUS_URL = (id) => `/api/v1/statuses/${id}/pin`
export const MASTODON_UNPIN_OWN_STATUS_URL = (id) =>
`/api/v1/statuses/${id}/unpin`
export const MASTODON_MUTE_CONVERSATION_URL = (id) =>
`/api/v1/statuses/${id}/mute`
export const MASTODON_UNMUTE_CONVERSATION_URL = (id) =>
`/api/v1/statuses/${id}/unmute`
export const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements'
const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) =>
`/api/v1/announcements/${id}/dismiss`
const PLEROMA_EMOJI_REACT_URL = (id, emoji) =>
export const PLEROMA_EMOJI_REACT_URL = (id, emoji) =>
`/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) =>
export const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) =>
`/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
@ -143,39 +149,39 @@ export const bookmarkStatus = ({ id, credentials, ...options }) =>
payload: {
folder_id: options.folder_id,
},
})
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const unbookmarkStatus = ({ id, credentials }) =>
promisedRequest({
url: MASTODON_UNBOOKMARK_STATUS_URL(id),
credentials,
method: 'POST',
})
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const pinOwnStatus = ({ id, credentials }) =>
promisedRequest({
url: MASTODON_PIN_OWN_STATUS(id),
url: MASTODON_PIN_OWN_STATUS_URL(id),
credentials,
method: 'POST',
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const unpinOwnStatus = ({ id, credentials }) =>
promisedRequest({
url: MASTODON_UNPIN_OWN_STATUS(id),
url: MASTODON_UNPIN_OWN_STATUS_URL(id),
credentials,
method: 'POST',
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const muteConversation = ({ id, credentials }) =>
promisedRequest({
url: MASTODON_MUTE_CONVERSATION(id),
url: MASTODON_MUTE_CONVERSATION_URL(id),
credentials,
method: 'POST',
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const unmuteConversation = ({ id, credentials }) =>
promisedRequest({
url: MASTODON_UNMUTE_CONVERSATION(id),
url: MASTODON_UNMUTE_CONVERSATION_URL(id),
credentials,
method: 'POST',
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
@ -656,7 +662,7 @@ export const fetchUserInLists = ({ id, credentials }) =>
export const removeUserFromFollowers = ({ id, credentials }) =>
promisedRequest({
url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id),
url: MASTODON_REMOVE_USER_FROM_FOLLOWERS_URL(id),
credentials,
method: 'POST',
})

View file

@ -26,8 +26,26 @@ const PLEROMA_STREAMING_EVENTS = new Set([
'pleroma:respond',
])
export const WSConnectionStatus = Object.freeze({
JOINED: 1,
CLOSED: 2,
ERROR: 3,
DISABLED: 4,
STARTING: 5,
STARTING_INITIAL: 6,
})
export class WSEvent extends Event {
data
constructor(name, data, original) {
super(name)
this.data = data
}
}
// A thin wrapper around WebSocket API that allows adding a pre-processor to it
// Uses EventTarget and a CustomEvent to proxy events
// Uses EventTarget and a WSEvent to proxy events
export const ProcessedWS = ({
url,
preprocessor = handleMastoWS,
@ -39,9 +57,7 @@ export const ProcessedWS = ({
if (!socket) throw new Error(`Failed to create socket ${id}`)
const proxy = (original, eventName, processor = (a) => a) => {
original.addEventListener(eventName, (eventData) => {
eventTarget.dispatchEvent(
new CustomEvent(eventName, { detail: processor(eventData) }),
)
eventTarget.dispatchEvent(new WSEvent(eventName, processor(eventData)))
})
}
socket.addEventListener('open', (wsEvent) => {
@ -75,7 +91,7 @@ export const ProcessedWS = ({
/**/
const onAuthenticated = () => {
eventTarget.dispatchEvent(new CustomEvent('pleroma:authenticated'))
eventTarget.dispatchEvent(new WSEvent('pleroma:authenticated'))
}
proxy(socket, 'open')
@ -126,14 +142,14 @@ export const handleMastoWS = (
const { data } = wsEvent
if (!data) return
const parsedEvent = JSON.parse(data)
const { event, payload } = parsedEvent
const { event, stream, payload } = parsedEvent
if (
MASTODON_STREAMING_EVENTS.has(event) ||
PLEROMA_STREAMING_EVENTS.has(event)
) {
// MastoBE and PleromaBE both send payload for delete as a PLAIN string
if (event === 'delete') {
return { event, id: payload }
return { event, stream, id: payload }
}
const data = payload ? JSON.parse(payload) : null
if (event === 'pleroma:respond') {
@ -150,25 +166,16 @@ export const handleMastoWS = (
}
return null
} else if (event === 'update') {
return { event, status: parseStatus(data) }
return { event, stream, status: parseStatus(data) }
} else if (event === 'status.update') {
return { event, status: parseStatus(data) }
return { event, stream, status: parseStatus(data) }
} else if (event === 'notification') {
return { event, notification: parseNotification(data) }
return { event, stream, notification: parseNotification(data) }
} else if (event === 'pleroma:chat_update') {
return { event, chatUpdate: parseChat(data) }
return { event, stream, chatUpdate: parseChat(data) }
}
} else {
console.warn('Unknown event', wsEvent)
return null
}
}
export const WSConnectionStatus = Object.freeze({
JOINED: 1,
CLOSED: 2,
ERROR: 3,
DISABLED: 4,
STARTING: 5,
STARTING_INITIAL: 6,
})

View file

@ -29,6 +29,7 @@ import {
import routes from './routes'
import { useAuthFlowStore } from 'src/stores/auth_flow'
import { useChatsStore } from 'src/stores/chats.js'
import { useEmojiStore } from 'src/stores/emoji.js'
import { useI18nStore } from 'src/stores/i18n'
import { useInstanceStore } from 'src/stores/instance.js'
@ -36,9 +37,12 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
import { useInterfaceStore } from 'src/stores/interface.js'
import { useLocalConfigStore } from 'src/stores/local_config.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import { useUsersStore } from 'src/stores/users.js'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import {
@ -454,7 +458,7 @@ const setConfig = async ({ store }) => {
const checkOAuthToken = async ({ store }) => {
const oauth = useOAuthStore()
if (oauth.userToken) {
return store.dispatch('loginUser', oauth.userToken)
return useUsersStore().loginUser(oauth.userToken)
}
return
}
@ -541,7 +545,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
window.highlightConfig = useUserHighlightStore()
FaviconService.initFaviconService()
initServiceWorker(store)
initServiceWorker(useNotificationsStore())
window.addEventListener('focus', () => updateFocus())
@ -589,6 +593,11 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
useI18nStore().setI18n(i18n)
// Global WS handlers
useChatsStore().attachSocket()
useInterfaceStore().attachSocket()
useStatusesStore().attachSocket()
app.use(router)
app.use(store)
app.use(i18n)

View file

@ -1,22 +1,16 @@
import AuthForm from 'src/components/auth_form/auth_form.js'
import BookmarkTimeline from 'src/components/bookmark_timeline/bookmark_timeline.vue'
import BubbleTimeline from 'src/components/bubble_timeline/bubble_timeline.vue'
import ConversationPage from 'src/components/conversation-page/conversation-page.vue'
import DMs from 'src/components/dm_timeline/dm_timeline.vue'
import FriendsTimeline from 'src/components/friends_timeline/friends_timeline.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
import PublicAndExternalTimeline from 'src/components/public_and_external_timeline/public_and_external_timeline.vue'
import PublicTimeline from 'src/components/public_timeline/public_timeline.vue'
import QuotesTimeline from 'src/components/quotes_timeline/quotes_timeline.vue'
import RemoteUserResolver from 'src/components/remote_user_resolver/remote_user_resolver.vue'
import TagTimeline from 'src/components/tag_timeline/tag_timeline.vue'
import Timeline from 'src/components/timeline/timeline.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useUsersStore } from 'src/stores/users.js'
export default (store) => {
export default () => {
const validateAuthenticatedRoute = (to, from, next) => {
if (store.state.users.currentUser) {
if (useUsersStore().currentUser) {
next()
} else {
next(
@ -31,7 +25,7 @@ export default (store) => {
path: '/',
redirect: () => {
return (
(store.state.users.currentUser
(useUsersStore().currentUser
? useInstanceStore().instanceIdentity.redirectRootLogin
: useInstanceStore().instanceIdentity.redirectRootNoLogin) ||
'/main/all'
@ -41,22 +35,52 @@ export default (store) => {
{
name: 'public-external-timeline',
path: '/main/all',
component: PublicAndExternalTimeline,
component: Timeline,
props: () => ({
timelineRef: { name: 'publicAndExternal' },
}),
},
{
name: 'public-timeline',
path: '/main/public',
component: PublicTimeline,
component: Timeline,
props: () => ({
timelineRef: { name: 'public' },
}),
},
{
name: 'friends',
path: '/main/friends',
component: FriendsTimeline,
component: Timeline,
beforeEnter: validateAuthenticatedRoute,
props: () => ({
timelineRef: { name: 'friends' },
}),
},
{
name: 'tag-timeline',
path: '/tag/:id',
component: Timeline,
props: (route) => ({
timelineRef: { name: 'tag', argument: route.params.id },
}),
},
{
name: 'bookmarks',
path: '/bookmarks',
component: Timeline,
props: (route) => ({
timelineRef: { name: 'bookmarks', argument: null },
}),
},
{
name: 'bubble',
path: '/bubble',
component: Timeline,
props: () => ({
timelineRef: { name: 'bubble' },
}),
},
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
{
name: 'conversation',
path: '/notice/:id',
@ -71,7 +95,14 @@ export default (store) => {
meta: { dontScroll: true },
beforeEnter: validateAuthenticatedRoute,
},
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{
name: 'quotes',
path: '/notice/:id/quotes',
component: Timeline,
props: (route) => ({
timelineRef: { name: 'quotes', argument: route.params.id },
}),
},
{
name: 'remote-user-profile-acct',
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
@ -104,8 +135,11 @@ export default (store) => {
{
name: 'dms',
path: '/users/:username/dms',
component: DMs,
component: Timeline,
beforeEnter: validateAuthenticatedRoute,
props: () => ({
timelineRef: { name: 'dms' },
}),
},
{
name: 'registration',
@ -202,8 +236,10 @@ export default (store) => {
{
name: 'lists-timeline',
path: '/lists/:id',
component: () =>
import('src/components/lists_timeline/lists_timeline.vue'),
component: Timeline,
props: (route) => ({
timelineRef: { name: 'list', argument: route.params.id },
}),
},
{
name: 'lists-edit',
@ -237,7 +273,10 @@ export default (store) => {
{
name: 'bookmark-folder',
path: '/bookmarks/:id',
component: BookmarkTimeline,
component: Timeline,
props: (route) => ({
timelineRef: { name: 'bookmarks', argument: route.params.id },
}),
},
{
name: 'bookmark-folder-edit',

View file

@ -8,6 +8,7 @@ import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useReportsStore } from 'src/stores/reports'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
@ -47,10 +48,10 @@ const AccountActions = {
this.showingConfirmBlock = false
},
showRepeats() {
this.$store.dispatch('showReblogs', this.user.id)
useUsersStore().showReblogs(this.user.id)
},
hideRepeats() {
this.$store.dispatch('hideReblogs', this.user.id)
useUsersStore().hideReblogs(this.user.id)
},
blockUser() {
if (this.$refs.timedBlockDialog) {
@ -64,11 +65,11 @@ const AccountActions = {
}
},
doBlockUser() {
this.$store.dispatch('blockUser', { id: this.user.id })
useUsersStore().blockUser(this.user.id)
this.hideConfirmBlock()
},
unblockUser() {
this.$store.dispatch('unblockUser', this.user.id)
useUsersStore().unblockUser(this.user.id)
},
removeUserFromFollowers() {
if (!this.shouldConfirmRemoveUserFromFollowers) {
@ -78,7 +79,7 @@ const AccountActions = {
}
},
doRemoveUserFromFollowers() {
this.$store.dispatch('removeUserFromFollowers', this.user.id)
useUsersStore().removeUserFromFollowers(this.user.id)
this.hideConfirmRemoveUserFromFollowers()
},
reportUser() {
@ -88,8 +89,8 @@ const AccountActions = {
this.$router.push({
name: 'chat',
params: {
username: this.$store.state.users.currentUser.screen_name,
recipient_id: this.user.id,
username: useUsersStore().currentUser.screen_name,
chatUserId: this.user.id,
},
})
},

View file

@ -1,9 +1,10 @@
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import AnnouncementEditor from 'src/components/announcement_editor/announcement_editor.vue'
import localeService from '../../services/locale/locale.service.js'
import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useUsersStore } from 'src/stores/users.js'
const Announcement = {
components: {
@ -25,9 +26,7 @@ const Announcement = {
announcement: Object,
},
computed: {
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapState(useUsersStore, ['currentUser']),
canEditAnnouncement() {
return this.currentUser?.privileges.has(
'announcements_manage_announcements',

View file

@ -1,9 +1,10 @@
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import Announcement from 'src/components/announcement/announcement.vue'
import AnnouncementEditor from 'src/components/announcement_editor/announcement_editor.vue'
import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useUsersStore } from 'src/stores/users.js'
const AnnouncementsPage = {
components: {
@ -26,9 +27,7 @@ const AnnouncementsPage = {
useAnnouncementsStore().fetchAnnouncements()
},
computed: {
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapState(useUsersStore, ['currentUser']),
announcements() {
return useAnnouncementsStore().announcements
},

View file

@ -1,14 +1,19 @@
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useUsersStore } from 'src/stores/users.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const AvatarList = {
props: ['users'],
props: {
userIds: Set,
},
computed: {
slicedUsers() {
return this.users ? this.users.slice(0, 15) : []
return [...(this.userIds ?? [])]
.slice(0, 15)
.map((id) => useUsersStore().findUser(id))
},
},
components: {

View file

@ -7,7 +7,7 @@
class="avatars-item"
>
<UserAvatar
:user="user"
:user-id="user.id"
class="avatar-small"
/>
</router-link>

View file

@ -11,7 +11,7 @@
>
<UserAvatar
class="user-avatar avatar"
:user="user"
:user-id="user.id"
@click.prevent
/>
</UserPopover>
@ -41,7 +41,7 @@
{{ $t('admin_dash.users.labels.handle_colon') }}
{{ ' ' }}
</strong>
<user-link
<UserLink
class="basic-user-card-screen-name"
:user="user"
/>

View file

@ -4,15 +4,16 @@ import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useUsersStore } from 'src/stores/users.js'
const BlockCard = {
props: ['userId'],
computed: {
user() {
return this.$store.getters.findUser(this.userId)
return useUsersStore().findUser(this.userId)
},
relationship() {
return this.$store.getters.relationship(this.userId)
return useUsersStore().relationship(this.userId)
},
blocked() {
return this.relationship.blocking
@ -35,13 +36,13 @@ const BlockCard = {
},
methods: {
unblockUser() {
this.$store.dispatch('unblockUser', this.user.id)
useUsersStore().unblockUser(this.user.id)
},
blockUser() {
if (this.blockExpiration) {
this.$refs.timedBlockDialog.optionallyPrompt()
} else {
this.$store.dispatch('blockUser', { id: this.user.id })
useUsersStore().blockUser(this.user.id)
}
},
},

View file

@ -1,38 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const Bookmarks = {
created() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'bookmarks',
bookmarkFolderId: this.folderId || null,
})
},
components: {
Timeline,
},
computed: {
folderId() {
return this.$route.params.id
},
timeline() {
return this.$store.state.statuses.timelines.bookmarks
},
},
watch: {
folderId() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
this.$store.dispatch('startFetchingTimeline', {
timeline: 'bookmarks',
bookmarkFolderId: this.folderId || null,
})
},
},
unmounted() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
},
}
export default Bookmarks

View file

@ -1,10 +0,0 @@
<template>
<Timeline
:title="$t('nav.bookmarks')"
:timeline="timeline"
:timeline-name="'bookmarks'"
:bookmark-folder-id="folderId"
/>
</template>
<script src="./bookmark_timeline.js"></script>

View file

@ -1,20 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const BubbleTimeline = {
components: {
Timeline,
},
computed: {
timeline() {
return this.$store.state.statuses.timelines.bubble
},
},
created() {
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
},
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'bubble')
},
}
export default BubbleTimeline

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.bubble')"
:timeline="timeline"
:timeline-name="'bubble'"
/>
</template>
<script src="./bubble_timeline.js"></script>

View file

@ -1,11 +1,11 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import ChatListItem from 'src/components/chat_list_item/chat_list_item.vue'
import ChatNew from 'src/components/chat_new/chat_new.vue'
import List from 'src/components/list/list.vue'
import { useChatsStore } from 'src/stores/chats.js'
import { useUsersStore } from 'src/stores/users.js'
const ChatList = {
components: {
@ -14,10 +14,8 @@ const ChatList = {
ChatNew,
},
computed: {
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapPiniaState(useChatsStore, ['sortedChatList']),
...mapState(useUsersStore, ['currentUser']),
...mapState(useChatsStore, ['sortedChatList']),
},
data() {
return {

View file

@ -1,4 +1,4 @@
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import AvatarList from 'src/components/avatar_list/avatar_list.vue'
import ChatTitle from 'src/components/chat_title/chat_title.vue'
@ -6,6 +6,8 @@ import StatusBody from 'src/components/status_content/status_content.vue'
import Timeago from 'src/components/timeago/timeago.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import { useUsersStore } from 'src/stores/users.js'
const ChatListItem = {
name: 'ChatListItem',
props: ['chat'],
@ -17,9 +19,7 @@ const ChatListItem = {
StatusBody,
},
computed: {
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapState(useUsersStore, ['currentUser']),
attachmentInfo() {
if (this.chat.lastMessage.attachments.length === 0) {
return

View file

@ -5,7 +5,7 @@
>
<div class="chat-list-item-left">
<UserAvatar
:user="chat.account"
:user-id="chat.account.id"
height="48px"
width="48px"
/>

View file

@ -1,6 +1,5 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue'
import { mapState } from 'vuex'
import Attachment from 'src/components/attachment/attachment.vue'
import ChatMessageDate from 'src/components/chat_message_date/chat_message_date.vue'
@ -20,6 +19,8 @@ import UserPopover from 'src/components/user_popover/user_popover.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -79,7 +80,7 @@ const ChatMessage = {
return this.isStatus ? this.message.user.id : this.message.account_id
},
author() {
return this.$store.getters.findUser(this.authorId)
return useUsersStore().findUser(this.authorId)
},
isCurrentUser() {
// mini-hack/optimizaiton:
@ -100,25 +101,21 @@ const ChatMessage = {
return !this.message.in_reply_to_status_id
},
customReplyTo() {
return this.$store.state.statuses.allStatusesObject[
this.message.in_reply_to_status_id
]
return useStatusesStore().allStatuses.get(
this.message.in_reply_to_status_id,
)
},
replyToName() {
if (this.message.in_reply_to_screen_name) {
return this.message.in_reply_to_screen_name
} else {
const user = this.$store.getters.findUser(
this.message.in_reply_to_user_id,
)
const user = useUsersStore().findUser(this.message.in_reply_to_user_id)
return user?.screen_name_ui
}
},
replyProfileLink() {
if (this.isCustomReply) {
const user = this.$store.getters.findUser(
this.message.in_reply_to_user_id,
)
const user = useUsersStore().findUser(this.message.in_reply_to_user_id)
// FIXME Why user not found sometimes???
return user ? user.statusnet_profile_url : 'NOT_FOUND'
}
@ -167,14 +164,12 @@ const ChatMessage = {
},
// Global stuff
...mapPiniaState(useInterfaceStore, {
...mapState(useInterfaceStore, {
betterShadow: (store) => store.browserSupport.cssFilter,
}),
...mapState({
currentUser: (state) => state.users.currentUser,
restrictedNicknames: (state) => useInstanceStore().restrictedNicknames,
}),
...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
...mapState(useUsersStore, ['currentUser']),
...mapState(useInstanceStore, ['restrictedNicknames']),
...mapState(useMergedConfigStore, ['mergedConfig']),
},
data() {
return {

View file

@ -83,7 +83,7 @@
<UserAvatar
v-if="author"
:compact="true"
:user="author"
:user-id="author.id"
/>
</UserPopover>
<div

View file

@ -1,9 +1,11 @@
import { mapGetters, mapState } from 'vuex'
import { mapState } from 'pinia'
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useSearchStore } from 'src/stores/search.js'
import { useUsersStore } from 'src/stores/users.js'
import { chats } from 'src/api/chats.js'
@ -42,17 +44,14 @@ const chatNew = {
return this.suggestions
}
},
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapGetters(['findUser']),
...mapState(useUsersStore, ['currentUser', 'findUser']),
},
methods: {
goBack() {
this.$emit('cancel')
},
goToChat(user) {
this.$router.push({ name: 'chat', params: { recipient_id: user.id } })
this.$router.push({ name: 'chat', params: { chatUserId: user.id } })
},
onInput() {
this.search(this.query)
@ -73,7 +72,8 @@ const chatNew = {
this.loading = true
this.userIds = []
this.$store
.dispatch('search', { q: query, resolve: true, type: 'accounts' })
useSearchStore()
.search({ q: query, resolve: true, type: 'accounts' })
.then((data) => {
this.loading = false
this.userIds = data.accounts.map((a) => a.id)

View file

@ -10,7 +10,7 @@
>
<UserAvatar
class="titlebar-avatar"
:user="user"
:user-id="user.id"
/>
</UserPopover>
<RichContent

View file

@ -1,7 +1,6 @@
import { get, maxBy, minBy, sortBy, throttle } from 'lodash'
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'pinia'
import { nextTick } from 'vue'
import { mapState } from 'vuex'
import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue'
import ChatTitle from 'src/components/chat_title/chat_title.vue'
@ -19,6 +18,9 @@ import { useChatsStore } from 'src/stores/chats.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useStreamingStore } from 'src/stores/streaming.js'
import { useUsersStore } from 'src/stores/users.js'
import {
chatMessages,
@ -85,14 +87,18 @@ const Chat = {
// Internal network stuff
fetcher: null,
socket: null,
streaming: false,
fetching: true,
errorLoadingChat: false,
messageRetriers: {},
idempotencyKeyIndex: {},
}
},
created() {
async created() {
if (this.testMode) return
this.startFetching()
await this.activate()
this.attachSocket()
},
mounted() {
window.addEventListener('resize', this.handleResize)
@ -118,10 +124,14 @@ const Chat = {
this.handleVisibilityChange,
false,
)
if (this.testMode) return
this.deactivate()
this.detachSocket()
},
computed: {
conversationId() {
const status = this.$store.state.statuses.allStatusesObject[this.statusId]
const status = useStatusesStore().allStatuses.get(this.statusId)
return get(
status,
'retweeted_status.statusnet_conversation_id',
@ -157,17 +167,14 @@ const Chat = {
if (this.isConversation) return false // Unsupported
return (
this.mergedConfig.useStreamingApi &&
this.mastoUserSocketStatus === WSConnectionStatus.JOINED
useStreamingStore().state === WSConnectionStatus.JOINED
)
},
...mapPiniaState(useInterfaceStore, {
...mapState(useInterfaceStore, {
mobileLayout: (store) => store.layoutType === 'mobile',
}),
...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
...mapState({
mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
currentUser: (state) => state.users.currentUser,
}),
...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useUsersStore, ['currentUser']),
},
watch: {
messages(old, neu) {
@ -226,16 +233,93 @@ const Chat = {
return
}
this.clear()
this.startFetching()
},
mastoUserSocketStatus(newValue) {
if (newValue === WSConnectionStatus.JOINED) {
this.fetchChat({ isFirstFetch: true })
}
this.deactivate()
this.activate()
},
},
methods: {
async activate() {
if (!this.isConversation) {
try {
const result = await getOrCreateChat({
accountId: this.chatUserId,
credentials: useOAuthStore().token,
})
const { data } = result
useUsersStore().addNewUsers({ ...result, data: data.account })
data.account = useUsersStore().findUser(data.account.id)
this.chat = data
this.maxId = this.chat.lastMessage?.id
} catch (e) {
console.error('Error creating or getting a chat', e)
this.errorLoadingChat = true
}
}
if (this.isConversation || this.chat) {
this.startFetching('Chat activated', true)
this.$nextTick(() => {
this.scrollDown({ forceRead: true })
})
}
},
deactivate() {
this.clear()
if (this.fetching) {
this.stopFetching('Chat deactivated')
}
},
attachSocket() {
const et = new EventTarget()
const socket = {
name: 'chatview',
et,
}
et.addEventListener('update', this.onStreamMessage)
et.addEventListener('pleroma:chat_update', this.onChatUpdate)
et.addEventListener('open', this.onStreamConnect)
et.addEventListener('close', this.onStreamDisconnect)
this.socket = socket
useStreamingStore().addSubscriber(this.socket)
},
detachSocket() {
const { et } = this.socket
et.removeEventListener('update', this.onStreamMessage)
et.removeEventListener('pleroma:chat_update', this.onChatUpdate)
et.removeEventListener('open', this.onStreamConnect)
et.removeEventListener('close', this.onStreamDisconnect)
useStreamingStore().removeSubscriber(this.socket)
},
// Poll & Push
onStreamConnect() {
this.streaming = true
this.stopFetching('Socket connected')
},
onStreamDisconnect(closeEvent) {
this.streaming = false
this.startFetching('Socket disconnected')
},
startFetching(reason, isFirstFetch) {
console.debug('[Chat View] Started fetching', 'Reason:', reason)
this.fetcher = promiseInterval(
() => this.fetchChat({ fetchLatest: true }),
5000,
)
this.fetchChat({ isFirstFetch })
this.fetching = true
},
stopFetching(reason) {
console.debug('[Chat View] Stopped fetching', 'Reason:', reason)
this.fetcher.stop()
this.fetcher = null
this.fetching = false
},
// Actions
async readChat() {
if (this.conversationId) return // Unsupported
@ -259,18 +343,8 @@ const Chat = {
this.lastReadMessageId = this.maxId
this.newMessageCount = 0
},
scrollDown(options = {}) {
const { behavior = 'auto', forceRead = false } = options
this.$nextTick(() => {
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior,
})
})
if (forceRead) {
this.readChat()
}
},
// Clears
cullOlder() {
const maxIndex = this.messages.length
const minIndex = maxIndex - 50
@ -366,35 +440,16 @@ const Chat = {
})
}
},
async startFetching() {
if (!this.isConversation) {
try {
const { data } = await getOrCreateChat({
accountId: this.chatUserId,
credentials: useOAuthStore().token,
})
this.$store.commit('addNewUsers', [data.account])
data.account = this.$store.getters.findUser(data.account.id)
this.chat = data
} catch (e) {
console.error('Error creating or getting a chat', e)
this.errorLoadingChat = true
}
}
if (this.isConversation || this.chat) {
this.$nextTick(() => {
this.scrollDown({ forceRead: true })
})
this.doStartFetching()
}
},
doStartFetching() {
this.fetcher = promiseInterval(
() => this.fetchChat({ fetchLatest: true }),
5000,
onStreamMessage({ data }) {
const messages = data.filter(
({ statusnet_conversation_id }) =>
statusnet_conversation_id === this.conversationId,
)
this.fetchChat({ isFirstFetch: true })
this.addMessages({ messages })
},
onChatUpdate({ data: { chatUpdate } }) {
const messages = [chatUpdate.lastMessage]
this.addMessages({ messages })
},
addMessages({ messages: newMessages }) {
for (let i = 0; i < newMessages.length; i++) {
@ -402,17 +457,23 @@ const Chat = {
// Sanity check
if (!this.isConversation && message.chat_id !== this.chat.id) {
// This is spammy, we get chat updates from a global chat update
// handler, which naturally receives updates for ALL chats.
// There is no way to subscribe to specific chat updates and listen
// to that in the API.
/*
console.warn(
`Chat message doesn't belong to current chat (id: ${this.chat.id})!!`,
message,
)
*/
return
}
// Clear any known pending messages
if (message.idempotency_key) {
if (this.pendingMessagesIndex[message.idempotencyKeyIndex]) {
delete this.pendingMessagesIndex[message.idempotencyKeyIndex]
if (this.pendingMessagesIndex[message.idempotency_key]) {
delete this.pendingMessagesIndex[message.idempotency_key]
this.pendingMessages = this.pendingMessages.filter(
({ idempotency_key }) =>
idempotency_key !== message.idempotency_key,
@ -438,9 +499,6 @@ const Chat = {
}
}
},
goBack() {
this.$router.back()
},
// Optimistic posting (chats only)
async sendMessage({ status, media, idempotencyKey }) {
@ -621,6 +679,23 @@ const Chat = {
})
},
// Misc
scrollDown(options = {}) {
const { behavior = 'auto', forceRead = false } = options
this.$nextTick(() => {
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior,
})
})
if (forceRead) {
this.readChat()
}
},
goBack() {
this.$router.back()
},
// Ugly
// TODO move to ChatMessage
async deleteChatMessage({ chatId, messageId }) {

View file

@ -4,6 +4,8 @@ import { defineAsyncComponent } from 'vue'
import Select from 'src/components/select/select.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
export default {
props: ['type', 'user', 'status'],
@ -33,9 +35,7 @@ export default {
return this.status.conversation_muted
},
domainIsMuted() {
return new Set(this.$store.state.users.currentUser.domainMutes).has(
this.domain,
)
return new Set(useUsersStore().currentUser.domainMutes).has(this.domain)
},
shouldConfirm() {
switch (this.type) {
@ -70,17 +70,17 @@ export default {
switch (this.type) {
case 'domain': {
if (!this.domainIsMuted) {
this.$store.dispatch('muteDomain', this.domain)
useUsersStore().muteDomain(this.domain)
} else {
this.$store.dispatch('unmuteDomain', this.domain)
useUsersStore().unmuteDomain(this.domain)
}
break
}
case 'conversation': {
if (!this.conversationIsMuted) {
this.$store.dispatch('muteConversation', { id: this.status.id })
useStatusesStore().muteConversation(this.status.id)
} else {
this.$store.dispatch('unmuteConversation', { id: this.status.id })
useStatusesStore().unmuteConversation(this.status.id)
}
break
}

View file

@ -1,6 +1,5 @@
import { clone, filter, findIndex, get, reduce } from 'lodash'
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { get, reduce } from 'lodash'
import { mapState } from 'pinia'
import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue'
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
@ -9,9 +8,11 @@ import QuickViewSettings from 'src/components/quick_view_settings/quick_view_set
import RichContent from 'src/components/rich_content/rich_content.jsx'
import ThreadTree from 'src/components/thread_tree/thread_tree.vue'
import { useInterfaceStore } from 'src/stores/interface'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useStreamingStore } from 'src/stores/streaming.js'
import { fetchConversation, fetchStatus } from 'src/api/public.js'
import { WSConnectionStatus } from 'src/api/websocket.js'
@ -51,20 +52,6 @@ const sortById = (a, b) => {
}
}
const sortAndFilterConversation = (conversation, statusoid) => {
if (statusoid.type === 'retweet') {
conversation = filter(
conversation,
(status) =>
status.type === 'retweet' ||
status.id !== statusoid.retweeted_status.id,
)
} else {
conversation = filter(conversation, (status) => status.type !== 'retweet')
}
return conversation.filter(Boolean).sort(sortById)
}
const conversation = {
props: {
statusId: {
@ -106,6 +93,7 @@ const conversation = {
default: false,
},
},
emits: ['update:virtualHeight'],
data() {
return {
focused: null,
@ -114,6 +102,7 @@ const conversation = {
inlineDivePosition: null,
loadStatusError: null,
unsuspendibleIds: new Set(),
virtualHeight: 120,
}
},
created() {
@ -121,7 +110,13 @@ const conversation = {
this.fetchConversation()
}
},
mounted() {
this.updateVirtualHeight()
},
computed: {
status() {
return useStatusesStore().allStatuses.get(this.statusId)
},
maxDepthToShowByDefault() {
// maxDepthInThread = max number of depths that is *visible*
// since our depth starts with 0 and "showing" means "showing children"
@ -160,14 +155,11 @@ const conversation = {
return this.otherRepliesButtonPosition === 'inside'
},
suspendable() {
return this.unsuspendibleIds.size > 0
return this.unsuspendibleIds.size === 0
},
hideStatus() {
hide() {
return this.virtualHidden && this.suspendable
},
status() {
return this.$store.state.statuses.allStatusesObject[this.statusId]
},
originalStatusId() {
if (this.status.retweeted_status) {
return this.status.retweeted_status.id
@ -187,15 +179,14 @@ const conversation = {
return [this.status]
}
const conversation = clone(
this.$store.state.statuses.conversationsObject[this.conversationId],
const conversation = useStatusesStore().conversations.get(
this.conversationId,
)
const statusIndex = findIndex(conversation, { id: this.originalStatusId })
if (statusIndex !== -1) {
conversation[statusIndex] = this.status
}
return sortAndFilterConversation(conversation, this.status)
return [...conversation.keys()]
.map((k) => useStatusesStore().allStatuses.get(k))
.filter((status) => status.type != 'repeat') // Old backend behavior?
.toSorted(sortById)
},
statusMap() {
return this.conversation.reduce((res, s) => {
@ -375,8 +366,7 @@ const conversation = {
return !!(this.expanded || this.isPage)
},
hiddenStyle() {
const height = this.status?.virtualHeight || '120px'
return this.virtualHidden ? { height } : {}
return { height: this.virtualHeight + 'px' }
},
threadDisplayStatus() {
return this.conversation.reduce((a, k) => {
@ -403,11 +393,11 @@ const conversation = {
maybeFocused() {
return this.isExpanded ? this.focused : null
},
...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
...mapState({
mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useStreamingStore, {
mastoUserSocketStatus: (state) => state.state,
}),
...mapPiniaState(useInterfaceStore, {
...mapState(useInterfaceStore, {
mobileLayout: (store) => store.layoutType === 'mobile',
}),
},
@ -441,10 +431,7 @@ const conversation = {
}
},
virtualHidden() {
this.$store.dispatch('setVirtualHeight', {
statusId: this.statusId,
height: `${this.$el.clientHeight}px`,
})
this.updateVirtualHeight()
},
},
methods: {
@ -453,9 +440,12 @@ const conversation = {
fetchConversation({
id: this.statusId,
credentials: useOAuthStore().token,
}).then(({ data: { ancestors, descendants } }) => {
this.$store.dispatch('addNewStatuses', { statuses: ancestors })
this.$store.dispatch('addNewStatuses', { statuses: descendants })
}).then(({ data: { ancestors, descendants }, timestamp }) => {
useStatusesStore().addNewStatuses({ statuses: ancestors, timestamp })
useStatusesStore().addNewStatuses({
statuses: descendants,
timestamp,
})
this.setFocused(this.originalStatusId)
})
} else {
@ -465,7 +455,7 @@ const conversation = {
credentials: useOAuthStore().token,
})
.then(({ data: status }) => {
this.$store.dispatch('addNewStatuses', { statuses: [status] })
useStatusesStore().addNewStatuses({ statuses: [status] })
this.fetchConversation()
})
.catch((error) => {
@ -482,17 +472,17 @@ const conversation = {
this.focused = id
if (!this.streamingEnabled) {
this.$store.dispatch('fetchStatus', id)
useStatusesStore().fetchStatus(id)
}
this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id)
useStatusesStore().fetchFavsAndRepeats(id)
useStatusesStore().fetchEmojiReactions(id)
},
toggleExpanded() {
this.expanded = !this.expanded
},
getConversationId(statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId]
const status = useStatusesStore().allStatuses.get(statusId)
return get(
status,
'retweeted_status.statusnet_conversation_id',
@ -630,6 +620,18 @@ const conversation = {
this.$router.push({ name: 'conversation', params: { id: data.id } })
}
},
updateVirtualHeight() {
if (this.hide) return // no updates when not rendering
if (!this.status) return // not loaded yet
this.$nextTick(() => {
this.virtualHeight = this.$refs.body.getBoundingClientRect().height
this.$emit('update:virtualHeight', {
id: this.status.id,
height: this.virtualHeight,
top: this.$el.clientTop,
})
})
},
},
}

View file

@ -1,7 +1,6 @@
<template>
<div
v-if="!hideStatus"
:style="hiddenStyle"
v-if="!hide"
class="Conversation"
:class="{ '-expanded' : isExpanded, 'panel' : isExpanded }"
>
@ -40,6 +39,7 @@
<div
v-if="isPage && !status"
class="conversation-body"
ref="body"
:class="{ 'panel-body': isExpanded }"
>
<p v-if="!loadStatusError">
@ -56,6 +56,7 @@
<div
v-else
class="conversation-body"
ref="body"
:class="{ 'panel-body': isExpanded }"
>
<div
@ -99,7 +100,7 @@
ref="statusComponent"
class="conversation-status status-fadein panel-body"
:statusoid="status"
:status-id="status.id"
:replies="getReplies(status.id)"
:expandable="!isExpanded"
@ -116,6 +117,7 @@
@goto="setFocused"
@dive="() => diveIntoStatus(status.id)"
@suspendable-state-change="onStatusSuspendStateChange"
@height-change="updateVirtualHeight"
/>
<div
v-if="showOtherRepliesButtonBelowStatus && getReplies(status.id).length > 1"
@ -152,7 +154,7 @@
ref="statusComponent"
:depth="0"
:status="status"
:status-id="status.id"
:in-profile="inProfile"
:conversation="conversation"
:collapsable="collapsable"
@ -174,6 +176,7 @@
@goto="setFocused"
@dive="diveIntoStatus"
@suspendable-state-change="onStatusSuspendStateChange"
@height-change="updateVirtualHeight"
/>
</div>
<div
@ -186,7 +189,7 @@
:key="status.id"
ref="statusComponent"
class="conversation-status status-fadein panel-body"
:statusoid="status"
:status-id="status.id"
:replies="getReplies(status.id)"
:expandable="!isExpanded"
@ -200,6 +203,7 @@
@goto="setFocused"
@toggle-expanded="toggleExpanded"
@suspendable-state-change="onStatusSuspendStateChange"
@height-change="updateVirtualHeight"
/>
</article>
</div>

View file

@ -5,6 +5,10 @@ import { defineAsyncComponent } from 'vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStreamingStore } from 'src/stores/streaming.js'
import { useUsersStore } from 'src/stores/users.js'
import { WSConnectionStatus } from 'src/api/websocket.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -14,6 +18,8 @@ import {
faComments,
faHome,
faInfoCircle,
faPlug,
faPlugCircleXmark,
faSearch,
faSignInAlt,
faSignOutAlt,
@ -33,6 +39,8 @@ library.add(
faTachometerAlt,
faCog,
faInfoCircle,
faPlug,
faPlugCircleXmark,
)
export default {
@ -91,11 +99,23 @@ export default {
sitename: (store) => store.instanceIdentity.name,
hideSitename: (store) => store.instanceIdentity.hideSitename,
}),
currentUser() {
return this.$store.state.users.currentUser
},
...mapState(useUsersStore, ['currentUser']),
...mapState(useStreamingStore, {
streamingConnected: (store) => store.state === WSConnectionStatus.JOINED,
}),
...mapState(useMergedConfigStore, ['mergedConfig']),
shouldConfirmLogout() {
return useMergedConfigStore().mergedConfig.modalOnLogout
return this.mergedConfig.modalOnLogout
},
streamingEnabled() {
return this.mergedConfig.useStreamingApi
},
streamingTooltip() {
if (this.streamingConnected) {
return this.$t('timeline.socket_reconnected')
} else {
return this.$t('timeline.socket_disconnected')
}
},
},
methods: {
@ -115,9 +135,9 @@ export default {
this.showConfirmLogout()
}
},
doLogout() {
async doLogout() {
await useUsersStore().logout()
this.$router.replace('/main/public')
this.$store.dispatch('logout')
this.hideConfirmLogout()
},
onSearchBarToggled(hidden) {

View file

@ -15,6 +15,24 @@
>
{{ sitename }}
</router-link>
<div
class="nav-icon"
v-if="streamingEnabled"
:title="streamingTooltip"
>
<FAIcon
v-if="streamingConnected"
fixed-width
class="fa-scale-110 fa-old-padding"
icon="plug"
/>
<FAIcon
v-else
fixed-width
class="fa-scale-110 fa-old-padding"
icon="plug-circle-xmark"
/>
</div>
</div>
<router-link
class="logo"

View file

@ -1,14 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const DMs = {
computed: {
timeline() {
return this.$store.state.statuses.timelines.dms
},
},
components: {
Timeline,
},
}
export default DMs

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.dms')"
:timeline="timeline"
:timeline-name="'dms'"
/>
</template>
<script src="./dm_timeline.js"></script>

View file

@ -1,5 +1,7 @@
import ProgressButton from 'src/components/progress_button/progress_button.vue'
import { useUsersStore } from 'src/stores/users.js'
const DomainMuteCard = {
props: ['domain'],
components: {
@ -7,18 +9,18 @@ const DomainMuteCard = {
},
computed: {
user() {
return this.$store.state.users.currentUser
return useUsersStore().currentUser
},
muted() {
return this.user.domainMutes.includes(this.domain)
return this.user.domainMutes.has(this.domain)
},
},
methods: {
unmuteDomain() {
return this.$store.dispatch('unmuteDomain', this.domain)
return useUsersStore().unmuteDomain(this.domain)
},
muteDomain() {
return this.$store.dispatch('muteDomain', this.domain)
return useUsersStore().muteDomain(this.domain)
},
},
}

View file

@ -6,6 +6,7 @@ import PostStatusForm from 'src/components/post_status_form/post_status_form.vue
import StatusContent from 'src/components/status_content/status_content.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faPollH } from '@fortawesome/free-solid-svg-icons'
@ -65,7 +66,7 @@ const Draft = {
},
refStatus() {
return this.draft.refId
? this.$store.state.statuses.allStatusesObject[this.draft.refId]
? useStatusesStore().allStatuses.get(this.draft.refId)
: undefined
},
localCollapseSubjectDefault() {

View file

@ -1,9 +1,11 @@
import { get } from 'lodash'
import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue'
import Modal from 'src/components/modal/modal.vue'
import { useEditStatusStore } from 'src/stores/editStatus.js'
import { useUsersStore } from 'src/stores/users.js'
const EditStatusModal = {
components: {
@ -18,18 +20,16 @@ const EditStatusModal = {
}
},
computed: {
isLoggedIn() {
return !!this.$store.state.users.currentUser
},
modalActivated() {
return useEditStatusStore().modalActivated
},
isFormVisible() {
return this.isLoggedIn && !this.resettingForm && this.modalActivated
return this.loggedIn && !this.resettingForm && this.modalActivated
},
params() {
return useEditStatusStore().params || {}
},
...mapState(useUsersStore, ['loggedIn']),
},
watch: {
params(newVal, oldVal) {

View file

@ -1,3 +1,6 @@
import { useSearchStore } from 'src/stores/search.js'
import { useUsersStore } from 'src/stores/users.js'
/**
* suggest - generates a suggestor function to be used by emoji-input
* data: object providing source information for specific types of suggestions:
@ -69,7 +72,7 @@ export const suggestEmoji = (emojis) => (input, nameKeywordLocalizer) => {
})
}
export const suggestUsers = ({ dispatch, state }) => {
export const suggestUsers = () => {
// Keep some persistent values in closure, most importantly for the
// custom debounce to work. Lodash debounce does not return a promise.
let suggestions = []
@ -77,7 +80,7 @@ export const suggestUsers = ({ dispatch, state }) => {
let timeout = null
let cancelUserSearch = null
const userSearch = (query) => dispatch('searchUsers', { query })
const userSearch = (query) => useSearchStore().searchUsers({ query })
const debounceUserSearch = (query) => {
cancelUserSearch?.()
return new Promise((resolve, reject) => {
@ -105,7 +108,7 @@ export const suggestUsers = ({ dispatch, state }) => {
await debounceUserSearch(noPrefix)
}
const newSuggestions = state.users.users
const newSuggestions = [...useUsersStore().users.values()]
.filter(
(user) =>
user.screen_name &&

View file

@ -3,6 +3,8 @@ import UserListPopover from 'src/components/user_list_popover/user_list_popover.
import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCheck, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
@ -35,12 +37,12 @@ const EmojiReactions = {
},
accountsForEmoji() {
return this.status.emoji_reactions.reduce((acc, reaction) => {
acc[reaction.name] = reaction.accounts || []
acc.set(reaction.name, new Set(reaction.account_ids))
return acc
}, {})
}, new Map())
},
loggedIn() {
return !!this.$store.state.users.currentUser
return !!useUsersStore().currentUser
},
remoteInteractionLink() {
return useInstanceStore().getRemoteInteractionLink({
@ -58,25 +60,22 @@ const EmojiReactions = {
reactedWith(emoji) {
return this.status.emoji_reactions.find((r) => r.name === emoji).me
},
async fetchEmojiReactionsByIfMissing() {
async fetchEmojiReactionsIfMissing() {
const hasNoAccounts = this.status.emoji_reactions.find((r) => !r.accounts)
if (hasNoAccounts) {
return await this.$store.dispatch(
'fetchEmojiReactionsBy',
this.status.id,
)
return await useStatusesStore().fetchEmojiReactions(this.status.id)
}
},
reactWith(emoji) {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
useStatusesStore().reactWithEmoji(this.status.id, emoji)
},
unreact(emoji) {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
useStatusesStore().unreactWithEmoji(this.status.id, emoji)
},
async emojiOnClick(emoji) {
if (!this.loggedIn) return
await this.fetchEmojiReactionsByIfMissing()
await this.fetchEmojiReactionsIfMissing()
if (this.reactedWith(emoji)) {
this.unreact(emoji)
} else {

View file

@ -52,11 +52,11 @@
</FALayers>
</component>
<UserListPopover
:users="accountsForEmoji[reaction.name]"
:user-ids="accountsForEmoji.get(reaction.name)"
class="emoji-reaction-popover"
:normal-button="true"
:trigger-attrs="counterTriggerAttrs(reaction)"
@show="fetchEmojiReactionsByIfMissing()"
@show="fetchEmojiReactionsIfMissing()"
>
<span class="emoji-reaction-counts">{{ reaction.count }}</span>
</UserListPopover>

View file

@ -6,6 +6,7 @@ import { useChatsStore } from 'src/stores/chats.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -52,7 +53,7 @@ const ExtraNotifications = {
)
},
currentUser() {
return this.$store.state.users.currentUser
return useUsersStore().currentUser
},
...mapGetters(['followRequestCount']),
...mapState(useAnnouncementsStore, {

View file

@ -1,11 +1,8 @@
import { defineAsyncComponent } from 'vue'
import {
requestFollow,
requestUnfollow,
} from '../../services/follow_manipulate/follow_manipulate'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useUsersStore } from 'src/stores/users.js'
export default {
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
components: {
@ -64,9 +61,11 @@ export default {
},
follow() {
this.inProgress = true
requestFollow(this.relationship.id, this.$store).then(() => {
this.inProgress = false
})
useUsersStore()
.followUser(this.relationship.id)
.finally(() => {
this.inProgress = false
})
},
unfollow() {
if (this.shouldConfirmUnfollow) {
@ -76,15 +75,12 @@ export default {
}
},
doUnfollow() {
const store = this.$store
this.inProgress = true
requestUnfollow(this.relationship.id, store).then(() => {
this.inProgress = false
store.commit('removeStatus', {
timeline: 'friends',
userId: this.relationship.id,
useUsersStore()
.unfollowUser(this.relationship.id)
.finally(() => {
this.inProgress = false
})
})
this.hideConfirmUnfollow()
},

View file

@ -3,6 +3,8 @@ import FollowButton from 'src/components/follow_button/follow_button.vue'
import RemoteFollow from 'src/components/remote_follow/remote_follow.vue'
import RemoveFollowerButton from 'src/components/remove_follower_button/remove_follower_button.vue'
import { useUsersStore } from 'src/stores/users.js'
const FollowCard = {
props: ['user', 'noFollowsYou'],
components: {
@ -13,13 +15,13 @@ const FollowCard = {
},
computed: {
isMe() {
return this.$store.state.users.currentUser.id === this.user.id
return useUsersStore().currentUser?.id === this.user.id
},
loggedIn() {
return this.$store.state.users.currentUser
return useUsersStore().currentUser
},
relationship() {
return this.$store.getters.relationship(this.user.id)
return useUsersStore().relationships.get(this.user.id)
},
},
}

View file

@ -1,5 +1,5 @@
<template>
<basic-user-card :user="user">
<BasicUserCard :user="user">
<div class="follow-card-content-container">
<span
v-if="isMe || (!noFollowsYou && relationship.followed_by)"
@ -30,7 +30,7 @@
/>
</template>
</div>
</basic-user-card>
</BasicUserCard>
</template>
<script src="./follow_card.js"></script>

View file

@ -1,9 +1,9 @@
import { defineAsyncComponent } from 'vue'
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { approveUser, denyUser } from 'src/api/user.js'
@ -24,7 +24,7 @@ const FollowRequestCard = {
},
methods: {
findFollowRequestNotificationId() {
const notif = notificationsFromStore(this.$store).find(
const notif = useNotificationsStore().data.find(
(notif) =>
notif.from_profile.id === this.user.id &&
notif.type === 'follow_request',
@ -55,16 +55,11 @@ const FollowRequestCard = {
id: this.user.id,
credentials: useOAuthStore().token,
})
// TODO fix
this.$store.dispatch('removeFollowRequest', this.user)
const notifId = this.findFollowRequestNotificationId()
this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId })
this.$store.dispatch('updateNotification', {
id: notifId,
updater: (notification) => {
notification.type = 'follow'
},
})
useNotificationsStore().markSingleNotificationAsSeen(notifId)
this.hideApproveConfirmDialog()
},
denyUser() {
@ -81,7 +76,8 @@ const FollowRequestCard = {
id: this.user.id,
credentials: useOAuthStore().token,
}).then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
useNotificationsStore().dismissNotificationLocal(notifId)
// TODO fix
this.$store.dispatch('removeFollowRequest', this.user)
})
this.hideDenyConfirmDialog()

View file

@ -1,14 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const FriendsTimeline = {
components: {
Timeline,
},
computed: {
timeline() {
return this.$store.state.statuses.timelines.friends
},
},
}
export default FriendsTimeline

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.timeline')"
:timeline="timeline"
:timeline-name="'friends'"
/>
</template>
<script src="./friends_timeline.js"></script>

View file

@ -1,6 +1,8 @@
import Notifications from 'src/components/notifications/notifications.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import { useUsersStore } from 'src/stores/users.js'
const tabModeDict = {
mentions: ['mention'],
statuses: ['status'],
@ -14,10 +16,9 @@ const tabModeDict = {
const Interactions = {
data() {
return {
allowFollowingMove:
this.$store.state.users.currentUser.allow_following_move,
allowFollowingMove: useUsersStore().currentUser.allow_following_move,
filterMode: tabModeDict.mentions,
canSeeReports: this.$store.state.users.currentUser.privileges.has(
canSeeReports: useUsersStore().currentUser.privileges.has(
'reports_manage_reports',
),
}

View file

@ -21,8 +21,8 @@ const List = {
default: () => '',
},
preSelect: {
type: Array,
default: [],
type: Set,
default: new Set(),
},
nonInteractive: {
type: Boolean,
@ -48,7 +48,7 @@ const List = {
data() {
return {
items: [],
selected: new Set(this.preSelect),
selected: new Set(this.preSelect), // clone
loading: false,
bottomedOut: true,
error: null,
@ -99,11 +99,11 @@ const List = {
this.fetchFunction(this.page)
.then((result) => {
this.loading = false
this.bottomedOut = isEmpty(result.items)
this.bottomedOut = isEmpty(result)
if (this.externalItems) return
this.page += 1
this.total = result.count
this.items.push(...result.items)
this.total = result.length
this.items.push(...result)
})
.catch((error) => {
this.loading = false

View file

@ -1,5 +1,4 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapGetters, mapState } from 'vuex'
import { mapState } from 'pinia'
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
import ListsUserSearch from 'src/components/lists_user_search/lists_user_search.vue'
@ -9,6 +8,7 @@ import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useListsStore } from 'src/stores/lists.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
@ -47,8 +47,8 @@ const ListsNew = {
.fetchListAccounts({ listId: this.id })
.then(() => {
this.membersUserIds = this.findListAccounts(this.id)
this.membersUserIds.forEach((userId) => {
this.$store.dispatch('fetchUserIfMissing', userId)
this.membersUserIds.forEach((id) => {
useUsersStore().fetchUserIfMissing({ id })
})
})
},
@ -66,11 +66,8 @@ const ListsNew = {
.map((userId) => this.findUser(userId))
.filter(Boolean)
},
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']),
...mapGetters(['findUser']),
...mapState(useUsersStore, ['currentUser', 'findUser']),
...mapState(useListsStore, ['findListTitle', 'findListAccounts']),
},
methods: {
onInput() {

View file

@ -1,10 +1,10 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import { getListEntries } from 'src/components/navigation/filter.js'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { useListsStore } from 'src/stores/lists.js'
import { useUsersStore } from 'src/stores/users.js'
export const ListsMenuContent = {
props: ['showPin'],
@ -12,12 +12,10 @@ export const ListsMenuContent = {
NavigationEntry,
},
computed: {
...mapPiniaState(useListsStore, {
...mapState(useListsStore, {
lists: getListEntries,
}),
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapState(useUsersStore, ['currentUser']),
},
}

View file

@ -1,47 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
import { useListsStore } from 'src/stores/lists.js'
const ListsTimeline = {
data() {
return {
listId: null,
}
},
components: {
Timeline,
},
computed: {
timeline() {
return this.$store.state.statuses.timelines.list
},
},
watch: {
$route: function (route) {
if (route.name === 'lists-timeline' && route.params.id !== this.listId) {
this.listId = route.params.id
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'list',
listId: this.listId,
})
}
},
},
created() {
this.listId = this.$route.params.id
useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'list',
listId: this.listId,
})
},
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
},
}
export default ListsTimeline

View file

@ -1,10 +0,0 @@
<template>
<Timeline
title="list.name"
:timeline="timeline"
:list-id="listId"
timeline-name="list"
/>
</template>
<script src="./lists_timeline.js"></script>

View file

@ -2,6 +2,8 @@ import { debounce } from 'lodash'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import { useSearchStore } from 'src/stores/search.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
@ -32,8 +34,8 @@ const ListsUserSearch = {
this.loading = true
this.$emit('loading')
this.userIds = []
this.$store
.dispatch('search', {
useSearchStore()
.search({
q: query,
resolve: true,
type: 'accounts',

View file

@ -1,9 +1,9 @@
import { mapActions, mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { mapActions, mapState } from 'pinia'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useUsersStore } from 'src/stores/users.js'
import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js'
@ -18,12 +18,10 @@ const LoginForm = {
error: false,
}),
computed: {
...mapState({
loggingIn: (state) => state.users.loggingIn,
}),
...mapPiniaState(useOAuthStore, ['clientId', 'clientSecret']),
...mapPiniaState(useInstanceStore, ['server', 'registrationOpen']),
...mapPiniaState(useAuthFlowStore, {
...mapState(useUsersStore, ['loggingIn']),
...mapState(useOAuthStore, ['clientId', 'clientSecret']),
...mapState(useInstanceStore, ['server', 'registrationOpen']),
...mapState(useAuthFlowStore, {
isTokenAuth: (store) => store.requiredToken,
isPasswordAuth: (store) => !store.requiredToken,
}),

View file

@ -1,5 +1,4 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
@ -12,6 +11,7 @@ import {
import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import { useUsersStore } from 'src/stores/users.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -29,7 +29,7 @@ const MentionLink = {
},
props: {
url: {
required: true,
required: false,
type: String,
},
content: {
@ -75,11 +75,11 @@ const MentionLink = {
},
computed: {
user() {
return this.url && this.$store?.getters.findUserByUrl(this.url)
return this.url ? useUsersStore().findUserByUrl(this.url) : null
},
isYou() {
// FIXME why user !== currentUser???
return this.user?.id === this.currentUser.id
if (!this.currentUser) return false
return this.user === this.currentUser
},
userName() {
return this.user && this.userNameFullUi.split('@')[0]
@ -156,11 +156,9 @@ const MentionLink = {
shouldFadeDomain() {
return this.mergedConfig.mentionLinkFadeDomain
},
...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
...mapPiniaState(useUserHighlightStore, ['highlight']),
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useUserHighlightStore, ['highlight']),
...mapState(useUsersStore, ['currentUser']),
},
}

View file

@ -31,7 +31,7 @@
<UserAvatar
v-if="shouldShowAvatar"
class="mention-avatar"
:user="user"
:user-id="user.id"
/><span
class="shortName"
>@<span

View file

@ -1,14 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const Mentions = {
computed: {
timeline() {
return this.$store.state.statuses.timelines.mentions
},
},
components: {
Timeline,
},
}
export default Mentions

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.interactions')"
:timeline="timeline"
:timeline-name="'mentions'"
/>
</template>
<script src="./mentions.js"></script>

View file

@ -5,13 +5,15 @@ import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import GestureService from '../../services/gesture_service/gesture_service'
import {
countExtraNotifications,
unseenNotificationsFromStore,
unseenNotifications,
} from '../../services/notification_utils/notification_utils'
import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -53,11 +55,10 @@ const MobileNav = {
},
computed: {
currentUser() {
return this.$store.state.users.currentUser
return useUsersStore().currentUser
},
unseenNotifications() {
return unseenNotificationsFromStore(
this.$store,
return unseenNotifications(
useMergedConfigStore().mergedConfig.notificationVisibility,
useMergedConfigStore().mergedConfig.ignoreInactionableSeen,
)
@ -144,12 +145,12 @@ const MobileNav = {
}
},
doLogout() {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
useUsersStore().logout()
this.hideConfirmLogout()
this.$router.replace('/main/public')
},
markNotificationsAsSeen() {
this.$store.dispatch('markNotificationsAsSeen')
useNotificationsStore().markNotificationsAsSeen()
},
onScroll({ target: { scrollTop, clientHeight, scrollHeight } }) {
this.notificationsAtTop = scrollTop > 0

View file

@ -1,7 +1,9 @@
import { debounce } from 'lodash'
import { mapState } from 'pinia'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { usePostStatusStore } from 'src/stores/post_status.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faPen } from '@fortawesome/free-solid-svg-icons'
@ -33,9 +35,6 @@ const MobilePostStatusButton = {
window.removeEventListener('resize', this.handleOSK)
},
computed: {
isLoggedIn() {
return !!this.$store.state.users.currentUser
},
isHidden() {
if (HIDDEN_FOR_PAGES.has(this.$route.name)) {
return true
@ -51,6 +50,7 @@ const MobilePostStatusButton = {
autohideFloatingPostButton() {
return !!useMergedConfigStore().mergedConfig.autohideFloatingPostButton
},
...mapState(useUsersStore, ['loggedIn']),
},
watch: {
autohideFloatingPostButton: function (isEnabled) {

View file

@ -1,6 +1,6 @@
<template>
<button
v-if="isLoggedIn"
v-if="loggedIn"
class="MobilePostButton button-default new-status-button"
:class="{ 'hidden': isHidden, 'always-show': isPersistent }"
:title="$t('post_status.new_status')"

View file

@ -5,6 +5,7 @@ import Popover from 'src/components/popover/popover.vue'
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronDown } from '@fortawesome/free-solid-svg-icons'
@ -405,7 +406,7 @@ const ModerationTools = {
)
},
isAdmin() {
return this.$store.state.users.currentUser.role === 'admin'
return useUsersStore().currentUser.role === 'admin'
},
},
methods: {
@ -452,7 +453,7 @@ const ModerationTools = {
},
privileged(privilege) {
if (this.isAdmin) return true
return this.$store.state.users.currentUser.privileges.has(privilege)
return useUsersStore().currentUser.privileges.has(privilege)
},
setTag(tag, value) {
useAdminSettingsStore().setUsersTags({

View file

@ -1,14 +1,16 @@
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
import { useUsersStore } from 'src/stores/users.js'
const MuteCard = {
props: ['userId'],
computed: {
user() {
return this.$store.getters.findUser(this.userId)
return useUsersStore().findUser(this.userId)
},
relationship() {
return this.$store.getters.relationship(this.userId)
return useUsersStore().relationship(this.userId)
},
muted() {
return this.relationship.muting
@ -30,7 +32,7 @@ const MuteCard = {
},
methods: {
unmuteUser() {
this.$store.dispatch('unmuteUser', this.userId)
useUsersStore().unmuteUser(this.user.id)
},
muteUser() {
this.$refs.timedMuteDialog.optionallyPrompt()

View file

@ -1,5 +1,5 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import { mapState as mapVuexState } from 'vuex'
import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
@ -14,6 +14,7 @@ import { useChatsStore } from 'src/stores/chats.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -110,29 +111,29 @@ const NavPanel = {
},
},
computed: {
...mapPiniaState(useAnnouncementsStore, {
...mapState(useAnnouncementsStore, {
unreadAnnouncementCount: 'unreadAnnouncementCount',
supportsAnnouncements: (store) => store.supportsAnnouncements,
}),
...mapPiniaState(useInstanceCapabilitiesStore, [
...mapState(useInstanceCapabilitiesStore, [
'pleromaChatMessagesAvailable',
'pleromaBookmarkFoldersAvailable',
'localBubble',
]),
...mapPiniaState(useInstanceStore, ['federating']),
...mapPiniaState(useInstanceStore, {
...mapState(useInstanceStore, ['federating']),
...mapState(useInstanceStore, {
privateMode: (store) => store.private,
}),
...mapPiniaState(useSyncConfigStore, {
...mapState(useSyncConfigStore, {
collapsed: (store) => store.prefsStorage.simple.collapseNav,
pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems),
}),
...mapState({
currentUser: (state) => state.users.currentUser,
...mapState(useUsersStore, ['currentUser']),
...mapVuexState({
followRequestCount: (state) => state.api.followRequests.length,
}),
...mapPiniaState(useChatsStore, ['unreadChatsCount']),
...mapState(useChatsStore, ['unreadChatsCount']),
timelinesItems() {
return filterNavigation(
Object.entries({ ...TIMELINES })

View file

@ -1,11 +1,11 @@
import { mapState as mapPiniaState, mapStores } from 'pinia'
import { mapState } from 'vuex'
import { mapState, mapStores } from 'pinia'
import { routeTo } from 'src/components/navigation/navigation.js'
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
@ -44,10 +44,8 @@ const NavigationEntry = {
return this.$store.getters
},
...mapStores(useAnnouncementsStore),
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapPiniaState(useSyncConfigStore, {
...mapState(useUsersStore, ['currentUser']),
...mapState(useSyncConfigStore, {
pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems),
}),

View file

@ -1,5 +1,5 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import { mapState as mapVuexState } from 'vuex'
import {
filterNavigation,
@ -18,6 +18,7 @@ import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useListsStore } from 'src/stores/lists'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -58,26 +59,26 @@ const NavPanel = {
getters() {
return this.$store.getters
},
...mapPiniaState(useListsStore, {
...mapState(useListsStore, {
lists: getListEntries,
}),
...mapPiniaState(useAnnouncementsStore, {
...mapState(useAnnouncementsStore, {
supportsAnnouncements: (store) => store.supportsAnnouncements,
}),
...mapPiniaState(useBookmarkFoldersStore, {
...mapState(useBookmarkFoldersStore, {
bookmarks: getBookmarkFolderEntries,
}),
...mapPiniaState(useSyncConfigStore, {
...mapState(useSyncConfigStore, {
pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems),
}),
...mapPiniaState(useInstanceStore, ['privateMode', 'federating']),
...mapPiniaState(useInstanceCapabilitiesStore, [
...mapState(useInstanceStore, ['privateMode', 'federating']),
...mapState(useInstanceCapabilitiesStore, [
'pleromaChatMessagesAvailable',
'localBubble',
]),
...mapState({
currentUser: (state) => state.users.currentUser,
...mapState(useUsersStore, ['currentUser']),
...mapVuexState({
followRequestCount: (state) => state.api.followRequests.length,
}),
pinnedList() {

View file

@ -1,5 +1,5 @@
import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue'
import { mapState } from 'vuex'
import Report from 'src/components/report/report.vue'
import StatusContent from 'src/components/status_content/status_content.vue'
@ -7,7 +7,7 @@ import Timeago from 'src/components/timeago/timeago.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import UserLink from 'src/components/user_link/user_link.vue'
import UserPopover from 'src/components/user_popover/user_popover.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { isStatusNotification } from '../../services/notification_utils/notification_utils_sw.js'
import {
highlightClass,
highlightStyle,
@ -15,8 +15,11 @@ import {
import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import { useUsersStore } from 'src/stores/users.js'
import { approveUser, denyUser } from 'src/api/user.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -115,9 +118,6 @@ const Notification = {
useInstanceStore().restrictedNicknames,
)
},
getUser(notification) {
return this.$store.state.users.usersObject[notification.from_profile.id]
},
interacted() {
this.$emit('interacted')
},
@ -148,16 +148,9 @@ const Notification = {
id: this.user.id,
credentials: useOAuthStore().token,
})
// TODO Fix this
this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('markSingleNotificationAsSeen', {
id: this.notification.id,
})
this.$store.dispatch('updateNotification', {
id: this.notification.id,
updater: (notification) => {
notification.type = 'follow'
},
})
useNotificationsStore().markSingleNotificationAsSeen(this.notification.id)
this.hideApproveConfirmDialog()
},
denyUser() {
@ -172,15 +165,20 @@ const Notification = {
id: this.user.id,
credentials: useOAuthStore().token,
}).then(() => {
this.$store.dispatch('dismissNotificationLocal', {
id: this.notification.id,
})
useNotificationsStore().dismissNotificationLocal(this.notification.id)
// TODO Fix this
this.$store.dispatch('removeFollowRequest', this.user)
})
this.hideDenyConfirmDialog()
},
},
computed: {
status() {
// Used for StatusContent
if (this.notification.status) {
return useStatusesStore().allStatuses.get(this.notification.status.id)
}
},
userClass() {
return highlightClass(this.notification.from_profile)
},
@ -194,19 +192,19 @@ const Notification = {
)
},
user() {
return this.$store.getters.findUser(this.notification.from_profile.id)
return useUsersStore().findUser(this.notification.from_profile.id)
},
userProfileLink() {
return this.generateUserProfileLink(this.user)
},
targetUser() {
return this.$store.getters.findUser(this.notification.target.id)
return useUsersStore().findUser(this.notification.target.id)
},
targetUserProfileLink() {
return this.generateUserProfileLink(this.targetUser)
},
needMute() {
return this.$store.getters.relationship(this.user.id).muting
return useUsersStore().relationship(this.user.id).muting
},
isStatusNotification() {
return isStatusNotification(this.notification.type)
@ -229,9 +227,7 @@ const Notification = {
shouldConfirmDeny() {
return this.mergedConfig.modalOnDenyFollow
},
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapState(useUsersStore, ['currentUser']),
},
}

View file

@ -6,7 +6,7 @@
<Status
class="Notification panel-body"
:compact="true"
:statusoid="notification.status"
:status-id="notification.status?.id"
@click="interacted"
/>
</article>
@ -24,7 +24,7 @@
class="Notification container -muted"
>
<small>
<user-link
<UserLink
:user="notification.from_profile"
:at="false"
/>
@ -57,7 +57,7 @@
<UserAvatar
class="post-avatar"
:compact="true"
:user="notification.from_profile"
:user-id="notification.from_profile.id"
/>
</UserPopover>
</a>
@ -215,7 +215,7 @@
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
class="follow-text"
>
<user-link
<UserLink
class="follow-name"
:user="notification.from_profile"
/>
@ -249,7 +249,7 @@
v-else-if="notification.type === 'move'"
class="move-text"
>
<user-link
<UserLink
:user="notification.target"
/>
</div>
@ -261,7 +261,7 @@
<StatusContent
class="status-content"
:compact="!statusExpanded"
:status="notification.status"
:status="status"
:collapse="!statusExpanded"
@click="onContentClick"
/>

View file

@ -7,17 +7,16 @@ import FaviconService from '../../services/favicon_service/favicon_service.js'
import {
ACTIONABLE_NOTIFICATION_TYPES,
countExtraNotifications,
filteredNotificationsFromStore,
notificationsFromStore,
unseenNotificationsFromStore,
filteredNotifications,
unseenNotifications,
} from '../../services/notification_utils/notification_utils.js'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import NotificationFilters from './notification_filters.vue'
import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -53,7 +52,6 @@ const Notifications = {
data() {
return {
showScrollTop: false,
bottomedOut: false,
// How many seen notifications to display in the list. The more there are,
// the heavier the page becomes. This count is increased when loading
// older notifications, and cut back to default whenever hitting "Read!".
@ -70,14 +68,13 @@ const Notifications = {
return this.minimalMode ? '' : 'panel panel-default'
},
notifications() {
return notificationsFromStore(this.$store)
return useNotificationsStore().data
},
error() {
return this.$store.state.notifications.error
return useNotificationsStore().error
},
unseenNotifications() {
return unseenNotificationsFromStore(
this.$store,
return unseenNotifications(
useMergedConfigStore().mergedConfig.notificationVisibility,
useMergedConfigStore().mergedConfig.ignoreInactionableSeen,
)
@ -85,18 +82,15 @@ const Notifications = {
filteredNotifications() {
if (this.unseenAtTop) {
return [
...filteredNotificationsFromStore(
this.$store,
...filteredNotifications(
useMergedConfigStore().mergedConfig.notificationVisibility,
).filter((n) => this.shouldShowUnseen(n)),
...filteredNotificationsFromStore(
this.$store,
...filteredNotifications(
useMergedConfigStore().mergedConfig.notificationVisibility,
).filter((n) => !this.shouldShowUnseen(n)),
]
} else {
return filteredNotificationsFromStore(
this.$store,
return filteredNotifications(
useMergedConfigStore().mergedConfig.notificationVisibility,
this.filterMode,
)
@ -127,7 +121,10 @@ const Notifications = {
)
},
loading() {
return this.$store.state.notifications.loading
return useNotificationsStore().fetcher.loading
},
bottomedOut() {
return useNotificationsStore().fetcher.bottomedOut
},
noHeading() {
const { layoutType } = useInterfaceStore()
@ -224,14 +221,14 @@ const Notifications = {
*/
notificationClicked(notification) {
const { id } = notification
this.$store.dispatch('notificationClicked', { id })
useNotificationsStore().notificationClicked(id)
},
notificationInteracted(notification) {
const { id } = notification
this.$store.dispatch('markSingleNotificationAsSeen', { id })
useNotificationsStore().markSingleNotificationAsSeen(id)
},
markAsSeen() {
this.$store.dispatch('markNotificationsAsSeen')
useNotificationsStore().markNotificationsAsSeen()
this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT
},
fetchOlderNotifications() {
@ -250,22 +247,7 @@ const Notifications = {
this.seenToDisplayCount = seenCount
}
const store = this.$store
const credentials = store.state.users.currentUser.credentials
store.commit('setNotificationsLoading', { value: true })
notificationsFetcher
.fetchAndUpdate({
store,
credentials,
older: true,
})
.then((notifs) => {
store.commit('setNotificationsLoading', { value: false })
if (notifs.length === 0) {
this.bottomedOut = true
}
this.seenToDisplayCount += notifs.length
})
useNotificationsStore().fetcher.fetchOlder()
},
},
}

View file

@ -1,5 +1,6 @@
import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useUsersStore } from 'src/stores/users.js'
import { getToken } from 'src/api/oauth.js'
@ -15,9 +16,10 @@ const oac = {
clientSecret,
instance: useInstanceStore().server,
code: this.code,
}).then(({ data: result }) => {
}).then(async ({ data: result }) => {
oauthStore.setToken(result.access_token)
this.$store.dispatch('loginUser', result.access_token)
await useUsersStore().loginUser(result.access_token)
this.$router.push({ name: 'friends' })
})
}

View file

@ -1,7 +1,7 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import { useInstanceStore } from 'src/stores/instance.js'
import { useUsersStore } from 'src/stores/users.js'
import { resetPassword } from 'src/api/public.js'
@ -21,13 +21,11 @@ const passwordReset = {
error: null,
}),
computed: {
...mapState({
signedIn: (state) => !!state.users.currentUser,
}),
...mapPiniaState(useInstanceStore, ['mailerEnabled']),
...mapState(useUsersStore, ['loggedIn']),
...mapState(useInstanceStore, ['mailerEnabled']),
},
created() {
if (this.signedIn) {
if (this.loggedIn) {
this.$router.push({ name: 'root' })
}
},

View file

@ -5,6 +5,7 @@ import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { usePollsStore } from 'src/stores/polls.js'
import { useUsersStore } from 'src/stores/users.js'
export default {
name: 'Poll',
@ -64,7 +65,7 @@ export default {
return useMergedConfigStore().mergedConfig.scaleMfm
},
loggedIn() {
return this.$store.state.users.currentUser
return useUsersStore().currentUser
},
showResults() {
return this.poll.voted || this.expired || !this.loggedIn

View file

@ -31,6 +31,7 @@ import { useInterfaceStore } from 'src/stores/interface.js'
import { useMediaViewerStore } from 'src/stores/media_viewer.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
import { pollFormToMasto } from 'src/services/poll/poll.service.js'
@ -570,9 +571,7 @@ const PostStatusForm = {
},
// Global stuff
currentUser() {
return this.$store.state.users.currentUser
},
...mapState(useUsersStore, ['currentUser']),
...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useInterfaceStore, {
mobileLayout: (store) => store.mobileLayout,

View file

@ -1,9 +1,11 @@
import { get } from 'lodash'
import { mapState } from 'pinia'
import Modal from 'src/components/modal/modal.vue'
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
import { usePostStatusStore } from 'src/stores/post_status.js'
import { useUsersStore } from 'src/stores/users.js'
const PostStatusModal = {
components: {
@ -16,18 +18,16 @@ const PostStatusModal = {
}
},
computed: {
isLoggedIn() {
return !!this.$store.state.users.currentUser
},
modalActivated() {
return usePostStatusStore().modalActivated
},
isFormVisible() {
return this.isLoggedIn && !this.resettingForm && this.modalActivated
return this.loggedIn && !this.resettingForm && this.modalActivated
},
params() {
return usePostStatusStore().params || {}
},
...mapState(useUsersStore, ['loggedIn']),
},
watch: {
params(newVal, oldVal) {

View file

@ -1,6 +1,6 @@
<template>
<Modal
v-if="isLoggedIn && !resettingForm"
v-if="loggedIn && !resettingForm"
:is-open="modalActivated"
class="post-form-modal-view"
@backdrop-clicked="closeModal"

View file

@ -1,22 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const PublicAndExternalTimeline = {
components: {
Timeline,
},
computed: {
timeline() {
return this.$store.state.statuses.timelines.publicAndExternal
},
},
created() {
this.$store.dispatch('startFetchingTimeline', {
timeline: 'publicAndExternal',
})
},
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
},
}
export default PublicAndExternalTimeline

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.twkn')"
:timeline="timeline"
:timeline-name="'publicAndExternal'"
/>
</template>
<script src="./public_and_external_timeline.js"></script>

View file

@ -1,20 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const PublicTimeline = {
components: {
Timeline,
},
computed: {
timeline() {
return this.$store.state.statuses.timelines.public
},
},
created() {
this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
},
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'public')
},
}
export default PublicTimeline

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.public_tl')"
:timeline="timeline"
:timeline-name="'public'"
/>
</template>
<script src="./public_timeline.js"></script>

View file

@ -6,6 +6,8 @@ import { useInterfaceStore } from 'src/stores/interface.js'
import { useLocalConfigStore } from 'src/stores/local_config.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useTimelinesStore } from 'src/stores/timelines.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
@ -26,13 +28,14 @@ const QuickFilterSettings = {
path: 'replyVisibility',
value: visibility,
})
this.$store.dispatch('queueFlushAll')
useTimelinesStore().requireReloadAll()
},
openTab(tab) {
useInterfaceStore().openSettingsModalTab(tab)
},
},
computed: {
...mapState(useUsersStore, ['loggedIn']),
...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useInterfaceStore, {
mobileLayout: (state) => state.layoutType === 'mobile',
@ -53,9 +56,6 @@ const QuickFilterSettings = {
return 'dropdown-item'
}
},
loggedIn() {
return !!this.$store.state.users.currentUser
},
replyVisibilitySelf: {
get() {
return this.mergedConfig.replyVisibility === 'self'

View file

@ -6,6 +6,7 @@ import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filt
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -35,9 +36,7 @@ const QuickViewSettings = {
...mapState(useInterfaceStore, {
mobileLayout: (state) => state.layoutType === 'mobile',
}),
loggedIn() {
return !!this.$store.state.users.currentUser
},
...mapState(useUsersStore, ['loggedIn']),
conversationDisplay: {
get() {
return this.mergedConfig.conversationDisplay

View file

@ -1,3 +1,5 @@
import { useStatusesStore } from 'src/stores/statuses.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@ -45,7 +47,7 @@ export default {
computed: {
quotedStatus() {
return this.statusId
? this.$store.state.statuses.allStatusesObject[this.statusId]
? useStatusesStore().allStatuses.get(this.statusId)
: undefined
},
shouldDisplayQuote() {
@ -79,8 +81,8 @@ export default {
this.fetchAttempted = true
this.fetching = true
this.$emit('loading', true)
this.$store
.dispatch('fetchStatus', this.statusId)
useStatusesStore()
.fetchStatus(this.statusId)
.then(() => {
this.displayQuote = true
})

View file

@ -4,6 +4,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
import Quote from './quote.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useSearchStore } from 'src/stores/search.js'
export default {
components: {
@ -93,8 +94,8 @@ export default {
this.$emit('update:id', notice[3])
} else if (value) {
this.loading = true
this.$store
.dispatch('search', {
useSearchStore()
.search({
q: value,
resolve: true,
offset: 0,

View file

@ -1,36 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const QuotesTimeline = {
created() {
this.$store.commit('clearTimeline', { timeline: 'quotes' })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'quotes',
statusId: this.statusId,
})
},
components: {
Timeline,
},
computed: {
statusId() {
return this.$route.params.id
},
timeline() {
return this.$store.state.statuses.timelines.quotes
},
},
watch: {
statusId() {
this.$store.commit('clearTimeline', { timeline: 'quotes' })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'quotes',
statusId: this.statusId,
})
},
},
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'quotes')
},
}
export default QuotesTimeline

View file

@ -1,10 +0,0 @@
<template>
<Timeline
:title="$t('nav.quotes')"
:timeline="timeline"
:timeline-name="'quotes'"
:status-id="statusId"
/>
</template>
<script src='./quotes_timeline.js'></script>

View file

@ -1,14 +1,16 @@
import useVuelidate from '@vuelidate/core'
import { required, requiredIf, sameAs } from '@vuelidate/validators'
import { mapState as mapPiniaState } from 'pinia'
import { mapActions, mapState } from 'vuex'
import { mapActions, mapState } from 'pinia'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import TermsOfServicePanel from 'src/components/terms_of_service_panel/terms_of_service_panel.vue'
import localeService from '../../services/locale/locale.service.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useUsersStore } from 'src/stores/users.js'
import { getCaptcha, register } from 'src/api/public.js'
import { DAY } from 'src/services/date_utils/date_utils.js'
const registration = {
@ -26,6 +28,9 @@ const registration = {
reason: '',
language: [''],
},
signUpPending: false,
signUpErrors: [],
signUpNotice: {},
captcha: {},
}),
components: {
@ -58,7 +63,7 @@ const registration = {
}
},
created() {
if ((!this.registrationOpen && !this.token) || this.signedIn) {
if ((!this.registrationOpen && !this.token) || this.loggedIn) {
this.$router.push({ name: 'root' })
}
@ -100,7 +105,10 @@ const registration = {
)
)
},
...mapPiniaState(useInstanceStore, {
hasSignUpNotice(state) {
return this.signUpNotice.message
},
...mapState(useInstanceStore, {
registrationOpen: (store) => store.registrationOpen,
embeddedToS: (store) => store.embeddedToS,
termsOfService: (store) => store.tos,
@ -109,16 +117,49 @@ const registration = {
birthdayRequired: (store) => store.birthdayRequired,
birthdayMinAge: (store) => store.birthdayMinAge,
}),
...mapState({
signedIn: (state) => !!state.users.currentUser,
isPending: (state) => state.users.signUpPending,
serverValidationErrors: (state) => state.users.signUpErrors,
signUpNotice: (state) => state.users.signUpNotice,
hasSignUpNotice: (state) => !!state.users.signUpNotice.message,
}),
...mapState(useUsersStore, ['loggedIn']),
},
methods: {
...mapActions(['signUp', 'getCaptcha']),
...mapActions(useUsersStore, ['loginUser']),
getCaptcha(store) {
return getCaptcha({
credentials: useOAuthStore().token,
}).then(({ data }) => data)
},
async signUp(userInfo) {
const oauthStore = useOAuthStore()
this.signUpPending = true
this.signUpErrors = []
this.signUpNotice = {}
try {
const token = await oauthStore.ensureAppToken()
const { data } = await register({
credentials: token,
params: { ...userInfo },
})
if (data.access_token) {
this.signUpPending = false
oauthStore.setToken(data.access_token)
await this.loginUser(data.access_token)
return 'ok'
} else {
// Request succeeded, but user cannot login yet.
this.signUpErrors = []
this.signUpNotice = data
return 'request_sent'
}
} catch (e) {
const errors = e.message
this.signUpErrors = errors
this.signUpNotice = {}
throw e
} finally {
this.signUpPending = false
}
},
async submit() {
this.user.nickname = this.user.username
this.user.token = this.token

View file

@ -28,7 +28,7 @@
<input
id="sign-up-username"
v-model.trim="v$.user.username.$model"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
:aria-required="true"
:placeholder="$t('registration.username_placeholder')"
@ -56,7 +56,7 @@
<input
id="sign-up-fullname"
v-model.trim="v$.user.fullname.$model"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
:aria-required="true"
:placeholder="$t('registration.fullname_placeholder')"
@ -84,7 +84,7 @@
<input
id="email"
v-model="v$.user.email.$model"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
type="email"
:aria-required="accountActivationRequired"
@ -109,7 +109,7 @@
<textarea
id="bio"
v-model="user.bio"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
:placeholder="bioPlaceholder"
/>
@ -126,7 +126,7 @@
<input
id="sign-up-password"
v-model="user.password"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
type="password"
:aria-required="true"
@ -154,7 +154,7 @@
<input
id="sign-up-password-confirmation"
v-model="user.confirm"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
type="password"
:aria-required="true"
@ -187,7 +187,7 @@
<input
id="sign-up-birthday"
v-model="user.birthday"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
type="date"
:max="birthdayRequired ? birthdayMinAttr : undefined"
@ -232,7 +232,7 @@
<textarea
id="reason"
v-model="user.reason"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
:placeholder="reasonPlaceholder"
/>
@ -259,7 +259,7 @@
<input
id="captcha-answer"
v-model="captcha.solution"
:disabled="isPending"
:disabled="signUpPending"
class="input form-control"
type="text"
autocomplete="off"
@ -285,7 +285,7 @@
</div>
<div class="form-group">
<button
:disabled="isPending"
:disabled="signUpPending"
type="submit"
class="btn button-default"
>
@ -303,12 +303,12 @@
<!-- eslint-enable vue/no-v-html -->
</div>
<div
v-if="serverValidationErrors.length"
v-if="signUpErrors.length"
class="form-group"
>
<div class="alert error">
<span
v-for="error in serverValidationErrors"
v-for="error in signUpErrors"
:key="error"
>{{ error }}</span>
</div>

View file

@ -1,4 +1,5 @@
import { useOAuthStore } from 'src/stores/oauth.js'
import { useUsersStore } from 'src/stores/users.js'
import { fetchUser } from 'src/api/public.js'
@ -16,17 +17,14 @@ const RemoteUserResolver = {
id,
credentials: useOAuthStore().token,
})
.then(({ data: externalUser }) => {
if (externalUser.error) {
this.error = true
} else {
this.$store.commit('addNewUsers', [externalUser])
const id = externalUser.id
this.$router.replace({
name: 'external-user-profile',
params: { id },
})
}
.then((result) => {
const { data: externalUser } = result
useUsersStore().addNewUsers(result)
const id = externalUser.id
this.$router.replace({
name: 'external-user-profile',
params: { id },
})
})
.catch(() => {
this.error = true

View file

@ -1,6 +1,7 @@
import { defineAsyncComponent } from 'vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useUsersStore } from 'src/stores/users.js'
export default {
props: ['user', 'relationship'],
@ -43,9 +44,9 @@ export default {
},
doRemoveUserFromFollowers() {
this.inProgress = true
this.$store
.dispatch('removeUserFromFollowers', this.relationship.id)
.then(() => {
useUsersStore()
.removeUserFromFollowers(this.relationship.id)
.finally(() => {
this.inProgress = false
})
this.hideConfirmRemoveUserFromFollowers()

View file

@ -4,6 +4,10 @@ import Conversation from 'src/components/conversation/conversation.vue'
import FollowCard from 'src/components/follow_card/follow_card.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import { useSearchStore } from 'src/stores/search.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch, faSearch } from '@fortawesome/free-solid-svg-icons'
@ -34,14 +38,14 @@ const Search = {
},
computed: {
users() {
return this.userIds.map((userId) => this.$store.getters.findUser(userId))
return this.userIds.map((userId) => useUsersStore().findUser(userId))
},
visibleStatuses() {
const allStatusesObject = this.$store.state.statuses.allStatusesObject
const allStatuses = useStatusesStore().allStatuses
return this.statuses.filter(
(status) =>
allStatusesObject[status.id] && !allStatusesObject[status.id].deleted,
allStatuses.has(status.id) && !allStatuses.get(status.id).deleted,
)
},
},
@ -76,8 +80,8 @@ const Search = {
this.lastStatusFetchCount = 0
}
this.$store
.dispatch('search', {
useSearchStore()
.search({
q: query,
resolve: true,
offset: this.statusesOffset,

View file

@ -119,7 +119,7 @@
>
<router-link
class="list-item hashtag"
:to="{ name: 'tag-timeline', params: { tag: hashtag.name } }"
:to="{ name: 'tag-timeline', params: { id: hashtag.name } }"
>
<span class="name">
#{{ hashtag.name }}

View file

@ -1,6 +1,8 @@
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
import { useUsersStore } from 'src/stores/users.js'
const AdminUserCard = {
props: {
userId: {
@ -13,7 +15,7 @@ const AdminUserCard = {
},
computed: {
user() {
return this.$store.getters.findUser(this.userId)
return useUsersStore().findUser(this.userId)
},
isAdmin() {
return this.user.rights.admin

View file

@ -1,20 +1,20 @@
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import { mapState } from 'pinia'
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useUsersStore } from 'src/stores/users.js'
const SharedComputedObject = () => ({
...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
...mapPiniaState(useMergedConfigStore, {
...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useMergedConfigStore, {
expertLevel: (store) => store.mergedConfig.expertLevel,
}),
...mapPiniaState(useAdminSettingsStore, {
...mapState(useAdminSettingsStore, {
adminConfig: (store) => store.config,
adminDraft: (store) => store.draft,
}),
...mapState({
user: (state) => state.users.currentUser,
...mapState(useUsersStore, {
user: (store) => store.currentUser,
}),
})

View file

@ -1,6 +1,6 @@
// eslint-disable-next-line no-unused
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'pinia'
import { Fragment } from 'vue'
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
@ -60,7 +60,7 @@ export default {
return this.$slots.default().findIndex(isWanted) === this.activeIndex
}
},
...mapPiniaState(useInterfaceStore, {
...mapState(useInterfaceStore, {
mobileLayout: (store) => store.layoutType === 'mobile',
}),
},

View file

@ -20,6 +20,7 @@ import VerticalTabSwitcher from './helpers/vertical_tab_switcher.jsx'
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -97,10 +98,7 @@ const SettingsModalAdminContent = {
},
computed: {
user() {
return this.$store.state.users.currentUser
},
isLoggedIn() {
return !!this.$store.state.users.currentUser
return useUsersStore().currentUser
},
open() {
return useInterfaceStore().settingsModalState !== 'hidden'

Some files were not shown because too many files have changed in this diff Show more