Merge branch 'users-statuses-pinia' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2026-08-20 22:08:57 +03:00
commit f62e323400
216 changed files with 7262 additions and 5413 deletions

View file

@ -0,0 +1 @@
fixed tapping "Mute..." and "Change visiblity" (admin action) in extra status actions closing the dropdown

View file

@ -0,0 +1 @@
Fix legacy settings migration being discarded after it ran.

View file

@ -0,0 +1 @@
Fix error dialog when posting in chat

View file

@ -0,0 +1 @@
domain mute fixedw

View file

@ -0,0 +1 @@
Fix followers list showing followed users as non-followed

View file

@ -0,0 +1 @@
Prevent service worker from caching media-proxy responses

View file

@ -0,0 +1 @@
fix moderation tools button possibly not appearing for admins

View file

@ -0,0 +1 @@
Fixed moderation actions requiring confirmation closing user popover

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 @@
Added an indicator next to instance's name showing WebSocket connection status (if enabled)

View file

@ -0,0 +1 @@
Fix theme lists failing to load when custom resource indexes are unavailable

View file

@ -0,0 +1 @@
Fix Themes 2.0 applying incorrect fonts

1
changelog.d/themes3.fix Normal file
View file

@ -0,0 +1 @@
Fixed themes 3 not loading in appearance tab

View file

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

View file

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

View file

@ -63,10 +63,11 @@ export const paramsString = (params = {}) => {
} }
export const promisedRequest = async ({ export const promisedRequest = async ({
method, method = 'GET',
url, url,
payload, payload,
formData, formData,
forceContentType,
cache, cache,
credentials, credentials,
headers = {}, headers = {},
@ -75,7 +76,7 @@ export const promisedRequest = async ({
method, method,
credentials: 'same-origin', credentials: 'same-origin',
headers: { headers: {
Accept: 'application/json', Accept: forceContentType ?? 'application/json',
...headers, ...headers,
}, },
} }
@ -110,7 +111,7 @@ export const promisedRequest = async ({
) )
if (contentLength === 0) return null if (contentLength === 0) return null
switch (contentType) { switch (forceContentType ?? contentType) {
case 'text/plain': case 'text/plain':
return await response.text() return await response.text()
case 'application/json': case 'application/json':
@ -123,7 +124,7 @@ export const promisedRequest = async ({
const { ok, status } = response const { ok, status } = response
if (ok) { if (ok) {
return { response, status, data } return { response, status, data, timestamp: Date.now() }
} else { } else {
throw new StatusCodeError(response.status, data, { url, options }, response) 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 }) => const MASTODON_PASSWORD_RESET_URL = ({ email }) =>
`/auth/password${paramsString({ email })}` `/auth/password${paramsString({ email })}`
const MASTODON_FOLLOWING_URL = ( export const MASTODON_FOLLOWING_URL = (
id, id,
{ minId, maxId, sinceId, limit, withRelationships }, { minId, maxId, sinceId, limit, withRelationships },
) => ) =>
`/api/v1/accounts/${id}/following${paramsString({ 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, id,
{ minId, maxId, sinceId, limit, withRelationships }, { minId, maxId, sinceId, limit, withRelationships },
) => ) =>
@ -26,15 +26,17 @@ const MASTODON_FOLLOWERS_URL = (
export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}` export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}`
const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context` const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context`
const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source` export const MASTODON_STATUS_SOURCE_URL = (id) =>
const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history` `/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_URL = '/api/v1/accounts'
const MASTODON_USER_LOOKUP_URL = ({ acct }) => const MASTODON_USER_LOOKUP_URL = ({ acct }) =>
`/api/v1/accounts/lookup${paramsString({ acct })}` `/api/v1/accounts/lookup${paramsString({ acct })}`
const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}` 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` `/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` `/api/v1/statuses/${id}/reblogged_by`
const MASTODON_SEARCH_2 = ({ const MASTODON_SEARCH_2 = ({
q, q,
@ -51,7 +53,7 @@ const MASTODON_SEARCH_2 = ({
const MASTODON_USER_SEARCH_URL = ({ q, resolve }) => const MASTODON_USER_SEARCH_URL = ({ q, resolve }) =>
`/api/v1/accounts/search${paramsString({ q, resolve })}` `/api/v1/accounts/search${paramsString({ q, resolve })}`
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' 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` `/api/v1/pleroma/statuses/${id}/reactions`
const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) => const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) =>
`/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}` `/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}`
@ -117,9 +119,17 @@ export const fetchUserByName = ({ name, credentials }) =>
export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) =>
promisedRequest({ promisedRequest({
url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }), url: MASTODON_FOLLOWING_URL(id, {
maxId,
sinceId,
limit,
withRelationships: true,
}),
credentials, credentials,
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) }).then(({ data, ...rest }) => ({
...rest,
data: data.map(parseUser),
}))
export const fetchFollowers = ({ export const fetchFollowers = ({
id, id,
@ -136,7 +146,10 @@ export const fetchFollowers = ({
withRelationships: true, withRelationships: true,
}), }),
credentials, credentials,
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) })) }).then(({ data, ...rest }) => ({
...rest,
data: data.map(parseUser),
}))
export const fetchConversation = ({ id, credentials }) => export const fetchConversation = ({ id, credentials }) =>
promisedRequest({ promisedRequest({
@ -163,15 +176,18 @@ export const fetchStatusSource = ({ id, credentials }) =>
credentials, credentials,
}).then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) })) }).then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) }))
export const fetchStatusHistory = ({ status, credentials }) => export const fetchStatusHistory = ({ id, credentials }) =>
promisedRequest({ promisedRequest({
url: MASTODON_STATUS_HISTORY_URL(status.id), url: MASTODON_STATUS_HISTORY_URL(id),
credentials, credentials,
}).then(({ data, ...rest }) => { }).then(({ data, ...rest }) => {
return [...data].reverse().map((item) => { return {
item.originalStatus = status ...rest,
return { ...rest, data: parseStatus(item) } data: [...data].reverse().map((item) => {
}) item.originalStatus = status
return parseStatus(item)
}),
}
}) })
export const listEmojiPacks = ({ page, pageSize, credentials }) => export const listEmojiPacks = ({ page, pageSize, credentials }) =>

View file

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

View file

@ -18,7 +18,7 @@ const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
const MOVE_ACCOUNT_URL = '/api/pleroma/move_account' const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
const ALIASES_URL = '/api/pleroma/aliases' const ALIASES_URL = '/api/pleroma/aliases'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' 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_SETTINGS_URL = '/api/pleroma/accounts/mfa'
const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes' 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_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/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` `/api/v1/notifications/${id}/dismiss`
const MASTODON_FAVORITE_URL = (id) => `/api/v1/statuses/${id}/favourite` export const MASTODON_FAVORITE_URL = (id) => `/api/v1/statuses/${id}/favourite`
const MASTODON_UNFAVORITE_URL = (id) => `/api/v1/statuses/${id}/unfavourite` export const MASTODON_UNFAVORITE_URL = (id) =>
const MASTODON_RETWEET_URL = (id) => `/api/v1/statuses/${id}/reblog` `/api/v1/statuses/${id}/unfavourite`
const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog` export const MASTODON_RETWEET_URL = (id) => `/api/v1/statuses/${id}/reblog`
const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}` export const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog`
const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow` export const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}`
const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow` 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_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
const MASTODON_APPROVE_USER_URL = (id) => 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_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject`
const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) => const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) =>
`/api/v1/accounts/relationships/${paramsString({ 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_URL = (id = '') => `/api/v1/lists/${id}`
export const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts` export const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts`
const MASTODON_USER_BLOCKS_URL = ({ export const MASTODON_USER_BLOCKS_URL = ({
maxId, maxId,
sinceId, sinceId,
limit, limit,
withRelationships, withRelationships,
}) => }) =>
`/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}` `/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}`
const MASTODON_USER_MUTES_URL = ({ export const MASTODON_USER_MUTES_URL = ({
maxId, maxId,
sinceId, sinceId,
limit, limit,
withRelationships, withRelationships,
}) => }) =>
`/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}` `/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}`
const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block` export const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block`
const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock` export const MASTODON_UNBLOCK_USER_URL = (id) =>
const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute` `/api/v1/accounts/${id}/unblock`
const MASTODON_UNMUTE_USER_URL = (id) => `/api/v1/accounts/${id}/unmute` export const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute`
const MASTODON_REMOVE_USER_FROM_FOLLOWERS = (id) => 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` `/api/v1/accounts/${id}/remove_from_followers`
const MASTODON_USER_NOTE_URL = (id) => `/api/v1/accounts/${id}/note` export const MASTODON_USER_NOTE_URL = (id) => `/api/v1/accounts/${id}/note`
const MASTODON_BOOKMARK_STATUS_URL = (id) => `/api/v1/statuses/${id}/bookmark` export const MASTODON_BOOKMARK_STATUS_URL = (id) =>
const MASTODON_UNBOOKMARK_STATUS_URL = (id) => `/api/v1/statuses/${id}/bookmark`
export const MASTODON_UNBOOKMARK_STATUS_URL = (id) =>
`/api/v1/statuses/${id}/unbookmark` `/api/v1/statuses/${id}/unbookmark`
const MASTODON_POST_STATUS_URL = '/api/v1/statuses' const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media' const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
const MASTODON_VOTE_URL = (id) => `/api/v1/polls/${id}/votes` const MASTODON_VOTE_URL = (id) => `/api/v1/polls/${id}/votes`
const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials' const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
const MASTODON_REPORT_USER_URL = '/api/v1/reports' const MASTODON_REPORT_USER_URL = '/api/v1/reports'
const MASTODON_PIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/pin` export const MASTODON_PIN_OWN_STATUS_URL = (id) => `/api/v1/statuses/${id}/pin`
const MASTODON_UNPIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/unpin` export const MASTODON_UNPIN_OWN_STATUS_URL = (id) =>
const MASTODON_MUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/mute` `/api/v1/statuses/${id}/unpin`
const MASTODON_UNMUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/unmute` export const MASTODON_MUTE_CONVERSATION_URL = (id) =>
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' `/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_URL = '/api/v1/announcements'
const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) => const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) =>
`/api/v1/announcements/${id}/dismiss` `/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}` `/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}` `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
@ -143,39 +149,39 @@ export const bookmarkStatus = ({ id, credentials, ...options }) =>
payload: { payload: {
folder_id: options.folder_id, folder_id: options.folder_id,
}, },
}) }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const unbookmarkStatus = ({ id, credentials }) => export const unbookmarkStatus = ({ id, credentials }) =>
promisedRequest({ promisedRequest({
url: MASTODON_UNBOOKMARK_STATUS_URL(id), url: MASTODON_UNBOOKMARK_STATUS_URL(id),
credentials, credentials,
method: 'POST', method: 'POST',
}) }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const pinOwnStatus = ({ id, credentials }) => export const pinOwnStatus = ({ id, credentials }) =>
promisedRequest({ promisedRequest({
url: MASTODON_PIN_OWN_STATUS(id), url: MASTODON_PIN_OWN_STATUS_URL(id),
credentials, credentials,
method: 'POST', method: 'POST',
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const unpinOwnStatus = ({ id, credentials }) => export const unpinOwnStatus = ({ id, credentials }) =>
promisedRequest({ promisedRequest({
url: MASTODON_UNPIN_OWN_STATUS(id), url: MASTODON_UNPIN_OWN_STATUS_URL(id),
credentials, credentials,
method: 'POST', method: 'POST',
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const muteConversation = ({ id, credentials }) => export const muteConversation = ({ id, credentials }) =>
promisedRequest({ promisedRequest({
url: MASTODON_MUTE_CONVERSATION(id), url: MASTODON_MUTE_CONVERSATION_URL(id),
credentials, credentials,
method: 'POST', method: 'POST',
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
export const unmuteConversation = ({ id, credentials }) => export const unmuteConversation = ({ id, credentials }) =>
promisedRequest({ promisedRequest({
url: MASTODON_UNMUTE_CONVERSATION(id), url: MASTODON_UNMUTE_CONVERSATION_URL(id),
credentials, credentials,
method: 'POST', method: 'POST',
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
@ -410,7 +416,6 @@ export const exportFriends = ({ id, credentials }) => {
id, id,
maxId, maxId,
credentials, credentials,
withRelationships: true,
}) })
friends = [...friends, ...users] friends = [...friends, ...users]
if (users.length === 0) { if (users.length === 0) {
@ -657,7 +662,7 @@ export const fetchUserInLists = ({ id, credentials }) =>
export const removeUserFromFollowers = ({ id, credentials }) => export const removeUserFromFollowers = ({ id, credentials }) =>
promisedRequest({ promisedRequest({
url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), url: MASTODON_REMOVE_USER_FROM_FOLLOWERS_URL(id),
credentials, credentials,
method: 'POST', method: 'POST',
}) })

View file

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

View file

@ -36,9 +36,12 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useLocalConfigStore } from 'src/stores/local_config.js' import { useLocalConfigStore } from 'src/stores/local_config.js'
import { useMergedConfigStore } from 'src/stores/merged_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 { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.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 VBodyScrollLock from 'src/directives/body_scroll_lock'
import { import {
@ -454,7 +457,7 @@ const setConfig = async ({ store }) => {
const checkOAuthToken = async ({ store }) => { const checkOAuthToken = async ({ store }) => {
const oauth = useOAuthStore() const oauth = useOAuthStore()
if (oauth.userToken) { if (oauth.userToken) {
return store.dispatch('loginUser', oauth.userToken) return useUsersStore().loginUser(oauth.userToken)
} }
return return
} }
@ -541,7 +544,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
window.highlightConfig = useUserHighlightStore() window.highlightConfig = useUserHighlightStore()
FaviconService.initFaviconService() FaviconService.initFaviconService()
initServiceWorker(store) initServiceWorker(useNotificationsStore())
window.addEventListener('focus', () => updateFocus()) window.addEventListener('focus', () => updateFocus())
@ -589,6 +592,10 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
useI18nStore().setI18n(i18n) useI18nStore().setI18n(i18n)
// Global WS handlers
useInterfaceStore().attachSocket()
useStatusesStore().attachSocket()
app.use(router) app.use(router)
app.use(store) app.use(store)
app.use(i18n) app.use(i18n)

View file

@ -1,22 +1,16 @@
import AuthForm from 'src/components/auth_form/auth_form.js' 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 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 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 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 { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.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) => { const validateAuthenticatedRoute = (to, from, next) => {
if (store.state.users.currentUser) { if (useUsersStore().currentUser) {
next() next()
} else { } else {
next( next(
@ -31,7 +25,7 @@ export default (store) => {
path: '/', path: '/',
redirect: () => { redirect: () => {
return ( return (
(store.state.users.currentUser (useUsersStore().currentUser
? useInstanceStore().instanceIdentity.redirectRootLogin ? useInstanceStore().instanceIdentity.redirectRootLogin
: useInstanceStore().instanceIdentity.redirectRootNoLogin) || : useInstanceStore().instanceIdentity.redirectRootNoLogin) ||
'/main/all' '/main/all'
@ -41,22 +35,52 @@ export default (store) => {
{ {
name: 'public-external-timeline', name: 'public-external-timeline',
path: '/main/all', path: '/main/all',
component: PublicAndExternalTimeline, component: Timeline,
props: () => ({
timelineRef: { name: 'publicAndExternal' },
}),
}, },
{ {
name: 'public-timeline', name: 'public-timeline',
path: '/main/public', path: '/main/public',
component: PublicTimeline, component: Timeline,
props: () => ({
timelineRef: { name: 'public' },
}),
}, },
{ {
name: 'friends', name: 'friends',
path: '/main/friends', path: '/main/friends',
component: FriendsTimeline, component: Timeline,
beforeEnter: validateAuthenticatedRoute, 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', name: 'conversation',
path: '/notice/:id', path: '/notice/:id',
@ -71,7 +95,14 @@ export default (store) => {
meta: { dontScroll: true }, meta: { dontScroll: true },
beforeEnter: validateAuthenticatedRoute, 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', name: 'remote-user-profile-acct',
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)', path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
@ -104,8 +135,11 @@ export default (store) => {
{ {
name: 'dms', name: 'dms',
path: '/users/:username/dms', path: '/users/:username/dms',
component: DMs, component: Timeline,
beforeEnter: validateAuthenticatedRoute, beforeEnter: validateAuthenticatedRoute,
props: () => ({
timelineRef: { name: 'dms' },
}),
}, },
{ {
name: 'registration', name: 'registration',
@ -202,8 +236,10 @@ export default (store) => {
{ {
name: 'lists-timeline', name: 'lists-timeline',
path: '/lists/:id', path: '/lists/:id',
component: () => component: Timeline,
import('src/components/lists_timeline/lists_timeline.vue'), props: (route) => ({
timelineRef: { name: 'list', argument: route.params.id },
}),
}, },
{ {
name: 'lists-edit', name: 'lists-edit',
@ -237,7 +273,10 @@ export default (store) => {
{ {
name: 'bookmark-folder', name: 'bookmark-folder',
path: '/bookmarks/:id', path: '/bookmarks/:id',
component: BookmarkTimeline, component: Timeline,
props: (route) => ({
timelineRef: { name: 'bookmarks', argument: route.params.id },
}),
}, },
{ {
name: 'bookmark-folder-edit', 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 { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useReportsStore } from 'src/stores/reports' import { useReportsStore } from 'src/stores/reports'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons' import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
@ -47,10 +48,10 @@ const AccountActions = {
this.showingConfirmBlock = false this.showingConfirmBlock = false
}, },
showRepeats() { showRepeats() {
this.$store.dispatch('showReblogs', this.user.id) useUsersStore().showReblogs(this.user.id)
}, },
hideRepeats() { hideRepeats() {
this.$store.dispatch('hideReblogs', this.user.id) useUsersStore().hideReblogs(this.user.id)
}, },
blockUser() { blockUser() {
if (this.$refs.timedBlockDialog) { if (this.$refs.timedBlockDialog) {
@ -64,11 +65,11 @@ const AccountActions = {
} }
}, },
doBlockUser() { doBlockUser() {
this.$store.dispatch('blockUser', { id: this.user.id }) useUsersStore().blockUser(this.user.id)
this.hideConfirmBlock() this.hideConfirmBlock()
}, },
unblockUser() { unblockUser() {
this.$store.dispatch('unblockUser', this.user.id) useUsersStore().unblockUser(this.user.id)
}, },
removeUserFromFollowers() { removeUserFromFollowers() {
if (!this.shouldConfirmRemoveUserFromFollowers) { if (!this.shouldConfirmRemoveUserFromFollowers) {
@ -78,7 +79,7 @@ const AccountActions = {
} }
}, },
doRemoveUserFromFollowers() { doRemoveUserFromFollowers() {
this.$store.dispatch('removeUserFromFollowers', this.user.id) useUsersStore().removeUserFromFollowers(this.user.id)
this.hideConfirmRemoveUserFromFollowers() this.hideConfirmRemoveUserFromFollowers()
}, },
reportUser() { reportUser() {
@ -88,7 +89,7 @@ const AccountActions = {
this.$router.push({ this.$router.push({
name: 'chat', name: 'chat',
params: { params: {
username: this.$store.state.users.currentUser.screen_name, username: useUsersStore().currentUser.screen_name,
recipient_id: this.user.id, recipient_id: 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 AnnouncementEditor from 'src/components/announcement_editor/announcement_editor.vue'
import localeService from '../../services/locale/locale.service.js' import localeService from '../../services/locale/locale.service.js'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useUsersStore } from 'src/stores/users.js'
const Announcement = { const Announcement = {
components: { components: {
@ -25,9 +26,7 @@ const Announcement = {
announcement: Object, announcement: Object,
}, },
computed: { computed: {
...mapState({ ...mapState(useUsersStore, ['currentUser']),
currentUser: (state) => state.users.currentUser,
}),
canEditAnnouncement() { canEditAnnouncement() {
return this.currentUser?.privileges.has( return this.currentUser?.privileges.has(
'announcements_manage_announcements', '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 Announcement from 'src/components/announcement/announcement.vue'
import AnnouncementEditor from 'src/components/announcement_editor/announcement_editor.vue' import AnnouncementEditor from 'src/components/announcement_editor/announcement_editor.vue'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useUsersStore } from 'src/stores/users.js'
const AnnouncementsPage = { const AnnouncementsPage = {
components: { components: {
@ -26,9 +27,7 @@ const AnnouncementsPage = {
useAnnouncementsStore().fetchAnnouncements() useAnnouncementsStore().fetchAnnouncements()
}, },
computed: { computed: {
...mapState({ ...mapState(useUsersStore, ['currentUser']),
currentUser: (state) => state.users.currentUser,
}),
announcements() { announcements() {
return useAnnouncementsStore().announcements return useAnnouncementsStore().announcements
}, },

View file

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

View file

@ -11,7 +11,7 @@
> >
<UserAvatar <UserAvatar
class="user-avatar avatar" class="user-avatar avatar"
:user="user" :user-id="user.id"
@click.prevent @click.prevent
/> />
</UserPopover> </UserPopover>

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 UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useUsersStore } from 'src/stores/users.js'
const BlockCard = { const BlockCard = {
props: ['userId'], props: ['userId'],
computed: { computed: {
user() { user() {
return this.$store.getters.findUser(this.userId) return useUsersStore().findUser(this.userId)
}, },
relationship() { relationship() {
return this.$store.getters.relationship(this.userId) return useUsersStore().relationship(this.userId)
}, },
blocked() { blocked() {
return this.relationship.blocking return this.relationship.blocking
@ -35,13 +36,13 @@ const BlockCard = {
}, },
methods: { methods: {
unblockUser() { unblockUser() {
this.$store.dispatch('unblockUser', this.user.id) useUsersStore().unblockUser(this.user.id)
}, },
blockUser() { blockUser() {
if (this.blockExpiration) { if (this.blockExpiration) {
this.$refs.timedBlockDialog.optionallyPrompt() this.$refs.timedBlockDialog.optionallyPrompt()
} else { } 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 'pinia'
import { mapState } from 'vuex'
import ChatListItem from 'src/components/chat_list_item/chat_list_item.vue' import ChatListItem from 'src/components/chat_list_item/chat_list_item.vue'
import ChatNew from 'src/components/chat_new/chat_new.vue' import ChatNew from 'src/components/chat_new/chat_new.vue'
import List from 'src/components/list/list.vue' import List from 'src/components/list/list.vue'
import { useChatsStore } from 'src/stores/chats.js' import { useChatsStore } from 'src/stores/chats.js'
import { useUsersStore } from 'src/stores/users.js'
const ChatList = { const ChatList = {
components: { components: {
@ -14,10 +14,8 @@ const ChatList = {
ChatNew, ChatNew,
}, },
computed: { computed: {
...mapState({ ...mapState(useUsersStore, ['currentUser']),
currentUser: (state) => state.users.currentUser, ...mapState(useChatsStore, ['sortedChatList']),
}),
...mapPiniaState(useChatsStore, ['sortedChatList']),
}, },
data() { data() {
return { 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 AvatarList from 'src/components/avatar_list/avatar_list.vue'
import ChatTitle from 'src/components/chat_title/chat_title.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 Timeago from 'src/components/timeago/timeago.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import { useUsersStore } from 'src/stores/users.js'
const ChatListItem = { const ChatListItem = {
name: 'ChatListItem', name: 'ChatListItem',
props: ['chat'], props: ['chat'],
@ -17,9 +19,7 @@ const ChatListItem = {
StatusBody, StatusBody,
}, },
computed: { computed: {
...mapState({ ...mapState(useUsersStore, ['currentUser']),
currentUser: (state) => state.users.currentUser,
}),
attachmentInfo() { attachmentInfo() {
if (this.chat.lastMessage.attachments.length === 0) { if (this.chat.lastMessage.attachments.length === 0) {
return return

View file

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

View file

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

View file

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

View file

@ -1,9 +1,10 @@
import { mapGetters, mapState } from 'vuex' import { mapState } from 'pinia'
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue' import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useUsersStore } from 'src/stores/users.js'
import { chats } from 'src/api/chats.js' import { chats } from 'src/api/chats.js'
@ -42,10 +43,7 @@ const chatNew = {
return this.suggestions return this.suggestions
} }
}, },
...mapState({ ...mapState(useUsersStore, ['currentUser', 'findUser']),
currentUser: (state) => state.users.currentUser,
}),
...mapGetters(['findUser']),
}, },
methods: { methods: {
goBack() { goBack() {
@ -73,7 +71,8 @@ const chatNew = {
this.loading = true this.loading = true
this.userIds = [] this.userIds = []
this.$store this.$store
.dispatch('search', { q: query, resolve: true, type: 'accounts' }) this.useSearchStore()
.search({ q: query, resolve: true, type: 'accounts' })
.then((data) => { .then((data) => {
this.loading = false this.loading = false
this.userIds = data.accounts.map((a) => a.id) this.userIds = data.accounts.map((a) => a.id)

View file

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

View file

@ -1,7 +1,6 @@
import { get, maxBy, minBy, sortBy, throttle } from 'lodash' import { get, maxBy, minBy, sortBy, throttle } from 'lodash'
import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'pinia'
import { nextTick } from 'vue' import { nextTick } from 'vue'
import { mapState } from 'vuex'
import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue' import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue'
import ChatTitle from 'src/components/chat_title/chat_title.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 { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.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 { import {
chatMessages, chatMessages,
@ -85,6 +87,8 @@ const Chat = {
// Internal network stuff // Internal network stuff
fetcher: null, fetcher: null,
socket: null,
streaming: false,
errorLoadingChat: false, errorLoadingChat: false,
messageRetriers: {}, messageRetriers: {},
idempotencyKeyIndex: {}, idempotencyKeyIndex: {},
@ -92,7 +96,8 @@ const Chat = {
}, },
created() { created() {
if (this.testMode) return if (this.testMode) return
this.startFetching() this.activate()
this.attachSocket()
}, },
mounted() { mounted() {
window.addEventListener('resize', this.handleResize) window.addEventListener('resize', this.handleResize)
@ -118,10 +123,13 @@ const Chat = {
this.handleVisibilityChange, this.handleVisibilityChange,
false, false,
) )
if (this.testMode) return
this.deactivate()
}, },
computed: { computed: {
conversationId() { conversationId() {
const status = this.$store.state.statuses.allStatusesObject[this.statusId] const status = useStatusesStore().allStatuses.get(this.statusId)
return get( return get(
status, status,
'retweeted_status.statusnet_conversation_id', 'retweeted_status.statusnet_conversation_id',
@ -157,17 +165,14 @@ const Chat = {
if (this.isConversation) return false // Unsupported if (this.isConversation) return false // Unsupported
return ( return (
this.mergedConfig.useStreamingApi && this.mergedConfig.useStreamingApi &&
this.mastoUserSocketStatus === WSConnectionStatus.JOINED useStreamingStore().state === WSConnectionStatus.JOINED
) )
}, },
...mapPiniaState(useInterfaceStore, { ...mapState(useInterfaceStore, {
mobileLayout: (store) => store.layoutType === 'mobile', mobileLayout: (store) => store.layoutType === 'mobile',
}), }),
...mapPiniaState(useMergedConfigStore, ['mergedConfig']), ...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState({ ...mapState(useUsersStore, ['currentUser']),
mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
currentUser: (state) => state.users.currentUser,
}),
}, },
watch: { watch: {
messages(old, neu) { messages(old, neu) {
@ -226,16 +231,85 @@ const Chat = {
return return
} }
this.clear() this.deactivate()
this.startFetching() this.activate()
},
mastoUserSocketStatus(newValue) {
if (newValue === WSConnectionStatus.JOINED) {
this.fetchChat({ isFirstFetch: true })
}
}, },
}, },
methods: { methods: {
async activate() {
if (!this.isConversation) {
try {
const result = await getOrCreateChat({
accountId: this.chatUserId,
credentials: useOAuthStore().token,
})
useUsersStore().addNewUsers(result)
const { data } = result
data.account = useUsersStore().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.startFetching('Chat activated', true)
}
},
deactivate() {
this.clear()
if (!this.streaming) {
this.stopFetching()
}
},
attachSocket() {
const et = new EventTarget()
const socket = { et }
et.addEventListener('update', this.onStreamMessage)
et.addEventListener('open', this.onStreamConnect)
et.addEventListener('close', this.onStreamDisconnect)
useStreamingStore().addSubscriber(socket)
this.socket = socket
},
detachSocket() {
const { et } = this.socket
et.removeEventListener('update', this.onStreamMessage)
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 })
},
stopFetching(reason) {
console.debug('[Chat View] Stopped fetching', 'Reason:', reason)
this.fetcher.stop()
this.fetcher = null
},
// Actions // Actions
async readChat() { async readChat() {
if (this.conversationId) return // Unsupported if (this.conversationId) return // Unsupported
@ -259,18 +333,8 @@ const Chat = {
this.lastReadMessageId = this.maxId this.lastReadMessageId = this.maxId
this.newMessageCount = 0 this.newMessageCount = 0
}, },
scrollDown(options = {}) {
const { behavior = 'auto', forceRead = false } = options // Clears
this.$nextTick(() => {
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior,
})
})
if (forceRead) {
this.readChat()
}
},
cullOlder() { cullOlder() {
const maxIndex = this.messages.length const maxIndex = this.messages.length
const minIndex = maxIndex - 50 const minIndex = maxIndex - 50
@ -366,35 +430,12 @@ const Chat = {
}) })
} }
}, },
async startFetching() { onStreamMessage({ data }) {
if (!this.isConversation) { const messages = data.filter(
try { ({ statusnet_conversation_id }) =>
const { data } = await getOrCreateChat({ statusnet_conversation_id === this.conversationId,
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,
) )
this.fetchChat({ isFirstFetch: true }) this.addMessages({ messages })
}, },
addMessages({ messages: newMessages }) { addMessages({ messages: newMessages }) {
for (let i = 0; i < newMessages.length; i++) { for (let i = 0; i < newMessages.length; i++) {
@ -438,9 +479,6 @@ const Chat = {
} }
} }
}, },
goBack() {
this.$router.back()
},
// Optimistic posting (chats only) // Optimistic posting (chats only)
async sendMessage({ status, media, idempotencyKey }) { async sendMessage({ status, media, idempotencyKey }) {
@ -538,11 +576,14 @@ const Chat = {
// Event handlers // Event handlers
onPosted(data) { onPosted(data) {
this.explicitReplyStatus = null // only conversation poster has the returned data
this.$router.push({ if (this.isConversation) {
name: 'conversation2', this.explicitReplyStatus = null
params: { statusId: data.id }, this.$router.push({
}) name: 'conversation2',
params: { statusId: data.id },
})
}
}, },
handleVisibilityChange() { handleVisibilityChange() {
this.$nextTick(() => { this.$nextTick(() => {
@ -618,6 +659,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 // Ugly
// TODO move to ChatMessage // TODO move to ChatMessage
async deleteChatMessage({ chatId, messageId }) { async deleteChatMessage({ chatId, messageId }) {

View file

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

View file

@ -1,4 +1,4 @@
import { clone, filter, findIndex, get, reduce } from 'lodash' import { get, reduce } from 'lodash'
import { mapState as mapPiniaState } from 'pinia' import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex' import { mapState } from 'vuex'
@ -9,9 +9,10 @@ import QuickViewSettings from 'src/components/quick_view_settings/quick_view_set
import RichContent from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
import ThreadTree from 'src/components/thread_tree/thread_tree.vue' 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 { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { fetchConversation, fetchStatus } from 'src/api/public.js' import { fetchConversation, fetchStatus } from 'src/api/public.js'
import { WSConnectionStatus } from 'src/api/websocket.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 = { const conversation = {
props: { props: {
statusId: { statusId: {
@ -122,6 +109,9 @@ const conversation = {
} }
}, },
computed: { computed: {
status() {
return useStatusesStore().allStatuses.get(this.statusId)
},
maxDepthToShowByDefault() { maxDepthToShowByDefault() {
// maxDepthInThread = max number of depths that is *visible* // maxDepthInThread = max number of depths that is *visible*
// since our depth starts with 0 and "showing" means "showing children" // since our depth starts with 0 and "showing" means "showing children"
@ -165,9 +155,6 @@ const conversation = {
hideStatus() { hideStatus() {
return this.virtualHidden && this.suspendable return this.virtualHidden && this.suspendable
}, },
status() {
return this.$store.state.statuses.allStatusesObject[this.statusId]
},
originalStatusId() { originalStatusId() {
if (this.status.retweeted_status) { if (this.status.retweeted_status) {
return this.status.retweeted_status.id return this.status.retweeted_status.id
@ -187,15 +174,13 @@ const conversation = {
return [this.status] return [this.status]
} }
const conversation = clone( const conversation = useStatusesStore().conversations.get(
this.$store.state.statuses.conversationsObject[this.conversationId], 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))
.toSorted(sortById)
}, },
statusMap() { statusMap() {
return this.conversation.reduce((res, s) => { return this.conversation.reduce((res, s) => {
@ -441,7 +426,7 @@ const conversation = {
} }
}, },
virtualHidden() { virtualHidden() {
this.$store.dispatch('setVirtualHeight', { useStatusesStore().setVirtualHeight({
statusId: this.statusId, statusId: this.statusId,
height: `${this.$el.clientHeight}px`, height: `${this.$el.clientHeight}px`,
}) })
@ -453,9 +438,12 @@ const conversation = {
fetchConversation({ fetchConversation({
id: this.statusId, id: this.statusId,
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}).then(({ data: { ancestors, descendants } }) => { }).then(({ data: { ancestors, descendants }, timestamp }) => {
this.$store.dispatch('addNewStatuses', { statuses: ancestors }) useStatusesStore().addNewStatuses({ statuses: ancestors, timestamp })
this.$store.dispatch('addNewStatuses', { statuses: descendants }) useStatusesStore().addNewStatuses({
statuses: descendants,
timestamp,
})
this.setFocused(this.originalStatusId) this.setFocused(this.originalStatusId)
}) })
} else { } else {
@ -465,7 +453,7 @@ const conversation = {
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}) })
.then(({ data: status }) => { .then(({ data: status }) => {
this.$store.dispatch('addNewStatuses', { statuses: [status] }) useStatusesStore().addNewStatuses({ statuses: [status] })
this.fetchConversation() this.fetchConversation()
}) })
.catch((error) => { .catch((error) => {
@ -482,17 +470,17 @@ const conversation = {
this.focused = id this.focused = id
if (!this.streamingEnabled) { if (!this.streamingEnabled) {
this.$store.dispatch('fetchStatus', id) useStatusesStore().fetchStatus(id)
} }
this.$store.dispatch('fetchFavsAndRepeats', id) useStatusesStore().fetchFavsAndRepeats(id)
this.$store.dispatch('fetchEmojiReactionsBy', id) useStatusesStore().fetchEmojiReactions(id)
}, },
toggleExpanded() { toggleExpanded() {
this.expanded = !this.expanded this.expanded = !this.expanded
}, },
getConversationId(statusId) { getConversationId(statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId] const status = useStatusesStore().allStatuses.get(statusId)
return get( return get(
status, status,
'retweeted_status.statusnet_conversation_id', 'retweeted_status.statusnet_conversation_id',

View file

@ -99,7 +99,7 @@
ref="statusComponent" ref="statusComponent"
class="conversation-status status-fadein panel-body" class="conversation-status status-fadein panel-body"
:statusoid="status" :status-id="status.id"
:replies="getReplies(status.id)" :replies="getReplies(status.id)"
:expandable="!isExpanded" :expandable="!isExpanded"
@ -152,7 +152,7 @@
ref="statusComponent" ref="statusComponent"
:depth="0" :depth="0"
:status="status" :status-id="status.id"
:in-profile="inProfile" :in-profile="inProfile"
:conversation="conversation" :conversation="conversation"
:collapsable="collapsable" :collapsable="collapsable"
@ -186,7 +186,7 @@
:key="status.id" :key="status.id"
ref="statusComponent" ref="statusComponent"
class="conversation-status status-fadein panel-body" class="conversation-status status-fadein panel-body"
:statusoid="status" :status-id="status.id"
:replies="getReplies(status.id)" :replies="getReplies(status.id)"
:expandable="!isExpanded" :expandable="!isExpanded"

View file

@ -5,6 +5,10 @@ import { defineAsyncComponent } from 'vue'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js' 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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -14,6 +18,8 @@ import {
faComments, faComments,
faHome, faHome,
faInfoCircle, faInfoCircle,
faPlug,
faPlugCircleXmark,
faSearch, faSearch,
faSignInAlt, faSignInAlt,
faSignOutAlt, faSignOutAlt,
@ -33,6 +39,8 @@ library.add(
faTachometerAlt, faTachometerAlt,
faCog, faCog,
faInfoCircle, faInfoCircle,
faPlug,
faPlugCircleXmark,
) )
export default { export default {
@ -91,11 +99,23 @@ export default {
sitename: (store) => store.instanceIdentity.name, sitename: (store) => store.instanceIdentity.name,
hideSitename: (store) => store.instanceIdentity.hideSitename, hideSitename: (store) => store.instanceIdentity.hideSitename,
}), }),
currentUser() { ...mapState(useUsersStore, ['currentUser']),
return this.$store.state.users.currentUser ...mapState(useStreamingStore, {
}, streamingConnected: (store) => store.state === WSConnectionStatus.JOINED,
}),
...mapState(useMergedConfigStore, ['mergedConfig']),
shouldConfirmLogout() { 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: { methods: {
@ -117,7 +137,7 @@ export default {
}, },
doLogout() { doLogout() {
this.$router.replace('/main/public') this.$router.replace('/main/public')
this.$store.dispatch('logout') useUsersStore().logout()
this.hideConfirmLogout() this.hideConfirmLogout()
}, },
onSearchBarToggled(hidden) { onSearchBarToggled(hidden) {

View file

@ -15,6 +15,24 @@
> >
{{ sitename }} {{ sitename }}
</router-link> </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> </div>
<router-link <router-link
class="logo" 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 ProgressButton from 'src/components/progress_button/progress_button.vue'
import { useUsersStore } from 'src/stores/users.js'
const DomainMuteCard = { const DomainMuteCard = {
props: ['domain'], props: ['domain'],
components: { components: {
@ -7,7 +9,7 @@ const DomainMuteCard = {
}, },
computed: { computed: {
user() { user() {
return this.$store.state.users.currentUser return useUsersStore().currentUser
}, },
muted() { muted() {
return this.user.domainMutes.includes(this.domain) return this.user.domainMutes.includes(this.domain)
@ -15,10 +17,10 @@ const DomainMuteCard = {
}, },
methods: { methods: {
unmuteDomain() { unmuteDomain() {
return this.$store.dispatch('unmuteDomain', this.domain) return useUsersStore().unmuteDomain(this.domain)
}, },
muteDomain() { 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 StatusContent from 'src/components/status_content/status_content.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faPollH } from '@fortawesome/free-solid-svg-icons' import { faPollH } from '@fortawesome/free-solid-svg-icons'
@ -65,7 +66,7 @@ const Draft = {
}, },
refStatus() { refStatus() {
return this.draft.refId return this.draft.refId
? this.$store.state.statuses.allStatusesObject[this.draft.refId] ? useStatusesStore().allStatuses.get(this.draft.refId)
: undefined : undefined
}, },
localCollapseSubjectDefault() { localCollapseSubjectDefault() {

View file

@ -4,6 +4,7 @@ import { defineAsyncComponent } from 'vue'
import Modal from 'src/components/modal/modal.vue' import Modal from 'src/components/modal/modal.vue'
import { useEditStatusStore } from 'src/stores/editStatus.js' import { useEditStatusStore } from 'src/stores/editStatus.js'
import { useUsersStore } from 'src/stores/users.js'
const EditStatusModal = { const EditStatusModal = {
components: { components: {
@ -19,7 +20,7 @@ const EditStatusModal = {
}, },
computed: { computed: {
isLoggedIn() { isLoggedIn() {
return !!this.$store.state.users.currentUser return !!useUsersStore().currentUser
}, },
modalActivated() { modalActivated() {
return useEditStatusStore().modalActivated return useEditStatusStore().modalActivated

View file

@ -1,3 +1,5 @@
import { useSearchStore } from 'src/stores/search.js'
/** /**
* suggest - generates a suggestor function to be used by emoji-input * suggest - generates a suggestor function to be used by emoji-input
* data: object providing source information for specific types of suggestions: * data: object providing source information for specific types of suggestions:
@ -77,7 +79,7 @@ export const suggestUsers = ({ dispatch, state }) => {
let timeout = null let timeout = null
let cancelUserSearch = null let cancelUserSearch = null
const userSearch = (query) => dispatch('searchUsers', { query }) const userSearch = (query) => useSearchStore().searchUsers({ query })
const debounceUserSearch = (query) => { const debounceUserSearch = (query) => {
cancelUserSearch?.() cancelUserSearch?.()
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

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 { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.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 { library } from '@fortawesome/fontawesome-svg-core'
import { faCheck, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons' import { faCheck, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
@ -40,7 +42,7 @@ const EmojiReactions = {
}, {}) }, {})
}, },
loggedIn() { loggedIn() {
return !!this.$store.state.users.currentUser return !!useUsersStore().currentUser
}, },
remoteInteractionLink() { remoteInteractionLink() {
return useInstanceStore().getRemoteInteractionLink({ return useInstanceStore().getRemoteInteractionLink({
@ -58,25 +60,22 @@ const EmojiReactions = {
reactedWith(emoji) { reactedWith(emoji) {
return this.status.emoji_reactions.find((r) => r.name === emoji).me 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) const hasNoAccounts = this.status.emoji_reactions.find((r) => !r.accounts)
if (hasNoAccounts) { if (!hasNoAccounts) {
return await this.$store.dispatch( return await useStatusesStore().fetchEmojiReactions(this.status.id)
'fetchEmojiReactionsBy',
this.status.id,
)
} }
}, },
reactWith(emoji) { reactWith(emoji) {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) useStatusesStore().reactWithEmoji(this.status.id, emoji)
}, },
unreact(emoji) { unreact(emoji) {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) useStatusesStore().unreactWithEmoji(this.status.id, emoji)
}, },
async emojiOnClick(emoji) { async emojiOnClick(emoji) {
if (!this.loggedIn) return if (!this.loggedIn) return
await this.fetchEmojiReactionsByIfMissing() await this.fetchEmojiReactionsIfMissing()
if (this.reactedWith(emoji)) { if (this.reactedWith(emoji)) {
this.unreact(emoji) this.unreact(emoji)
} else { } else {

View file

@ -56,7 +56,7 @@
class="emoji-reaction-popover" class="emoji-reaction-popover"
:normal-button="true" :normal-button="true"
:trigger-attrs="counterTriggerAttrs(reaction)" :trigger-attrs="counterTriggerAttrs(reaction)"
@show="fetchEmojiReactionsByIfMissing()" @show="fetchEmojiReactionsIfMissing()"
> >
<span class="emoji-reaction-counts">{{ reaction.count }}</span> <span class="emoji-reaction-counts">{{ reaction.count }}</span>
</UserListPopover> </UserListPopover>

View file

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

View file

@ -1,11 +1,8 @@
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import {
requestFollow,
requestUnfollow,
} from '../../services/follow_manipulate/follow_manipulate'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useUsersStore } from 'src/stores/users.js'
export default { export default {
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'], props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
components: { components: {
@ -64,9 +61,11 @@ export default {
}, },
follow() { follow() {
this.inProgress = true this.inProgress = true
requestFollow(this.relationship.id, this.$store).then(() => { useUsersStore()
this.inProgress = false .followUser(this.relationship.id)
}) .finally(() => {
this.inProgress = false
})
}, },
unfollow() { unfollow() {
if (this.shouldConfirmUnfollow) { if (this.shouldConfirmUnfollow) {
@ -76,15 +75,12 @@ export default {
} }
}, },
doUnfollow() { doUnfollow() {
const store = this.$store
this.inProgress = true this.inProgress = true
requestUnfollow(this.relationship.id, store).then(() => { useUsersStore()
this.inProgress = false .unfollowUser(this.relationship.id)
store.commit('removeStatus', { .finally(() => {
timeline: 'friends', this.inProgress = false
userId: this.relationship.id,
}) })
})
this.hideConfirmUnfollow() 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 RemoteFollow from 'src/components/remote_follow/remote_follow.vue'
import RemoveFollowerButton from 'src/components/remove_follower_button/remove_follower_button.vue' import RemoveFollowerButton from 'src/components/remove_follower_button/remove_follower_button.vue'
import { useUsersStore } from 'src/stores/users.js'
const FollowCard = { const FollowCard = {
props: ['user', 'noFollowsYou'], props: ['user', 'noFollowsYou'],
components: { components: {
@ -13,13 +15,13 @@ const FollowCard = {
}, },
computed: { computed: {
isMe() { isMe() {
return this.$store.state.users.currentUser.id === this.user.id return useUsersStore().currentUser.id === this.user.id
}, },
loggedIn() { loggedIn() {
return this.$store.state.users.currentUser return useUsersStore().currentUser
}, },
relationship() { relationship() {
return this.$store.getters.relationship(this.user.id) return useUsersStore().relationships.get(this.user.id)
}, },
}, },
} }

View file

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

View file

@ -1,9 +1,9 @@
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
import BasicUserCard from '../basic_user_card/basic_user_card.vue' import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import { useMergedConfigStore } from 'src/stores/merged_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 { useOAuthStore } from 'src/stores/oauth.js'
import { approveUser, denyUser } from 'src/api/user.js' import { approveUser, denyUser } from 'src/api/user.js'
@ -24,7 +24,7 @@ const FollowRequestCard = {
}, },
methods: { methods: {
findFollowRequestNotificationId() { findFollowRequestNotificationId() {
const notif = notificationsFromStore(this.$store).find( const notif = useNotificationsStore().data.find(
(notif) => (notif) =>
notif.from_profile.id === this.user.id && notif.from_profile.id === this.user.id &&
notif.type === 'follow_request', notif.type === 'follow_request',
@ -55,16 +55,11 @@ const FollowRequestCard = {
id: this.user.id, id: this.user.id,
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}) })
// TODO fix
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
const notifId = this.findFollowRequestNotificationId() const notifId = this.findFollowRequestNotificationId()
this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId }) useNotificationsStore().markSingleNotificationAsSeen(notifId)
this.$store.dispatch('updateNotification', {
id: notifId,
updater: (notification) => {
notification.type = 'follow'
},
})
this.hideApproveConfirmDialog() this.hideApproveConfirmDialog()
}, },
denyUser() { denyUser() {
@ -81,7 +76,8 @@ const FollowRequestCard = {
id: this.user.id, id: this.user.id,
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}).then(() => { }).then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId }) useNotificationsStore().dismissNotificationLocal(notifId)
// TODO fix
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
}) })
this.hideDenyConfirmDialog() 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 Notifications from 'src/components/notifications/notifications.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import { useUsersStore } from 'src/stores/users.js'
const tabModeDict = { const tabModeDict = {
mentions: ['mention'], mentions: ['mention'],
statuses: ['status'], statuses: ['status'],
@ -14,10 +16,9 @@ const tabModeDict = {
const Interactions = { const Interactions = {
data() { data() {
return { return {
allowFollowingMove: allowFollowingMove: useUsersStore().currentUser.allow_following_move,
this.$store.state.users.currentUser.allow_following_move,
filterMode: tabModeDict.mentions, filterMode: tabModeDict.mentions,
canSeeReports: this.$store.state.users.currentUser.privileges.has( canSeeReports: useUsersStore().currentUser.privileges.has(
'reports_manage_reports', 'reports_manage_reports',
), ),
} }

View file

@ -98,12 +98,13 @@ const List = {
this.fetchFunction(this.page) this.fetchFunction(this.page)
.then((result) => { .then((result) => {
console.log(result)
this.loading = false this.loading = false
this.bottomedOut = isEmpty(result.items) this.bottomedOut = isEmpty(result)
if (this.externalItems) return if (this.externalItems) return
this.page += 1 this.page += 1
this.total = result.count this.total = result.length
this.items.push(...result.items) this.items.push(...result)
}) })
.catch((error) => { .catch((error) => {
this.loading = false this.loading = false

View file

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

View file

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

@ -32,8 +32,8 @@ const ListsUserSearch = {
this.loading = true this.loading = true
this.$emit('loading') this.$emit('loading')
this.userIds = [] this.userIds = []
this.$store this.useSearchStore()
.dispatch('search', { .search({
q: query, q: query,
resolve: true, resolve: true,
type: 'accounts', type: 'accounts',

View file

@ -1,5 +1,4 @@
import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'pinia'
import { mapState } from 'vuex'
import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue' import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
@ -12,6 +11,7 @@ import {
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.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' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -75,7 +75,7 @@ const MentionLink = {
}, },
computed: { computed: {
user() { user() {
return this.url && this.$store?.getters.findUserByUrl(this.url) return this.url && useUsersStore().findUserByUrl(this.url)
}, },
isYou() { isYou() {
// FIXME why user !== currentUser??? // FIXME why user !== currentUser???
@ -156,11 +156,9 @@ const MentionLink = {
shouldFadeDomain() { shouldFadeDomain() {
return this.mergedConfig.mentionLinkFadeDomain return this.mergedConfig.mentionLinkFadeDomain
}, },
...mapPiniaState(useMergedConfigStore, ['mergedConfig']), ...mapState(useMergedConfigStore, ['mergedConfig']),
...mapPiniaState(useUserHighlightStore, ['highlight']), ...mapState(useUserHighlightStore, ['highlight']),
...mapState({ ...mapState(useUsersStore, ['currentUser']),
currentUser: (state) => state.users.currentUser,
}),
}, },
} }

View file

@ -31,7 +31,7 @@
<UserAvatar <UserAvatar
v-if="shouldShowAvatar" v-if="shouldShowAvatar"
class="mention-avatar" class="mention-avatar"
:user="user" :user-id="user.id"
/><span /><span
class="shortName" class="shortName"
>@<span >@<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 GestureService from '../../services/gesture_service/gesture_service'
import { import {
countExtraNotifications, countExtraNotifications,
unseenNotificationsFromStore, unseenNotifications,
} from '../../services/notification_utils/notification_utils' } from '../../services/notification_utils/notification_utils'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js' import { useChatsStore } from 'src/stores/chats.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -53,11 +55,10 @@ const MobileNav = {
}, },
computed: { computed: {
currentUser() { currentUser() {
return this.$store.state.users.currentUser return useUsersStore().currentUser
}, },
unseenNotifications() { unseenNotifications() {
return unseenNotificationsFromStore( return unseenNotifications(
this.$store,
useMergedConfigStore().mergedConfig.notificationVisibility, useMergedConfigStore().mergedConfig.notificationVisibility,
useMergedConfigStore().mergedConfig.ignoreInactionableSeen, useMergedConfigStore().mergedConfig.ignoreInactionableSeen,
) )
@ -145,11 +146,11 @@ const MobileNav = {
}, },
doLogout() { doLogout() {
this.$router.replace('/main/public') this.$router.replace('/main/public')
this.$store.dispatch('logout') useUsersStore().logout()
this.hideConfirmLogout() this.hideConfirmLogout()
}, },
markNotificationsAsSeen() { markNotificationsAsSeen() {
this.$store.dispatch('markNotificationsAsSeen') useNotificationsStore().markNotificationsAsSeen()
}, },
onScroll({ target: { scrollTop, clientHeight, scrollHeight } }) { onScroll({ target: { scrollTop, clientHeight, scrollHeight } }) {
this.notificationsAtTop = scrollTop > 0 this.notificationsAtTop = scrollTop > 0

View file

@ -2,6 +2,7 @@ import { debounce } from 'lodash'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { usePostStatusStore } from 'src/stores/post_status.js' import { usePostStatusStore } from 'src/stores/post_status.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faPen } from '@fortawesome/free-solid-svg-icons' import { faPen } from '@fortawesome/free-solid-svg-icons'
@ -34,7 +35,7 @@ const MobilePostStatusButton = {
}, },
computed: { computed: {
isLoggedIn() { isLoggedIn() {
return !!this.$store.state.users.currentUser return useUsersStore().loggedIn
}, },
isHidden() { isHidden() {
if (HIDDEN_FOR_PAGES.has(this.$route.name)) { if (HIDDEN_FOR_PAGES.has(this.$route.name)) {

View file

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

View file

@ -9,7 +9,7 @@
@show="setOpen(true)" @show="setOpen(true)"
@close="setOpen(false)" @close="setOpen(false)"
> >
<template #content="{close}"> <template #content>
<div class="dropdown-menu"> <div class="dropdown-menu">
<template v-for="(entry, index) in entries"> <template v-for="(entry, index) in entries">
<div <div
@ -26,7 +26,7 @@
> >
<button <button
class="main-button" class="main-button"
@click="() => maybeShowConfirm(close, entry)" @click="() => maybeShowConfirm(entry)"
> >
<span <span
v-if="entry.checkbox" v-if="entry.checkbox"

View file

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

View file

@ -14,6 +14,7 @@ import { useChatsStore } from 'src/stores/chats.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useSyncConfigStore } from 'src/stores/sync_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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -128,8 +129,8 @@ const NavPanel = {
pinnedItems: (store) => pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems), new Set(store.prefsStorage.collections.pinnedNavItems),
}), }),
...mapPiniaState(useUsersStore, ['currentUser']),
...mapState({ ...mapState({
currentUser: (state) => state.users.currentUser,
followRequestCount: (state) => state.api.followRequests.length, followRequestCount: (state) => state.api.followRequests.length,
}), }),
...mapPiniaState(useChatsStore, ['unreadChatsCount']), ...mapPiniaState(useChatsStore, ['unreadChatsCount']),

View file

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

View file

@ -18,6 +18,7 @@ import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useListsStore } from 'src/stores/lists' import { useListsStore } from 'src/stores/lists'
import { useSyncConfigStore } from 'src/stores/sync_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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -76,8 +77,8 @@ const NavPanel = {
'pleromaChatMessagesAvailable', 'pleromaChatMessagesAvailable',
'localBubble', 'localBubble',
]), ]),
...mapPiniaState(useUsersStore, ['currentUser']),
...mapState({ ...mapState({
currentUser: (state) => state.users.currentUser,
followRequestCount: (state) => state.api.followRequests.length, followRequestCount: (state) => state.api.followRequests.length,
}), }),
pinnedList() { pinnedList() {

View file

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

View file

@ -6,7 +6,7 @@
<Status <Status
class="Notification panel-body" class="Notification panel-body"
:compact="true" :compact="true"
:statusoid="notification.status" :status-id="notification.status?.id"
@click="interacted" @click="interacted"
/> />
</article> </article>
@ -57,7 +57,7 @@
<UserAvatar <UserAvatar
class="post-avatar" class="post-avatar"
:compact="true" :compact="true"
:user="notification.from_profile" :user-id="notification.from_profile.id"
/> />
</UserPopover> </UserPopover>
</a> </a>
@ -261,7 +261,7 @@
<StatusContent <StatusContent
class="status-content" class="status-content"
:compact="!statusExpanded" :compact="!statusExpanded"
:status="notification.status" :status="status"
:collapse="!statusExpanded" :collapse="!statusExpanded"
@click="onContentClick" @click="onContentClick"
/> />

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'pinia'
import { mapState } from 'vuex'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useUsersStore } from 'src/stores/users.js'
import { resetPassword } from 'src/api/public.js' import { resetPassword } from 'src/api/public.js'
@ -21,13 +21,11 @@ const passwordReset = {
error: null, error: null,
}), }),
computed: { computed: {
...mapState({ ...mapState(useUsersStore, ['loggedIn']),
signedIn: (state) => !!state.users.currentUser, ...mapState(useInstanceStore, ['mailerEnabled']),
}),
...mapPiniaState(useInstanceStore, ['mailerEnabled']),
}, },
created() { created() {
if (this.signedIn) { if (this.loggedIn) {
this.$router.push({ name: 'root' }) 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 { useMergedConfigStore } from 'src/stores/merged_config.js'
import { usePollsStore } from 'src/stores/polls.js' import { usePollsStore } from 'src/stores/polls.js'
import { useUsersStore } from 'src/stores/users.js'
export default { export default {
name: 'Poll', name: 'Poll',
@ -64,7 +65,7 @@ export default {
return useMergedConfigStore().mergedConfig.scaleMfm return useMergedConfigStore().mergedConfig.scaleMfm
}, },
loggedIn() { loggedIn() {
return this.$store.state.users.currentUser return useUsersStore().currentUser
}, },
showResults() { showResults() {
return this.poll.voted || this.expired || !this.loggedIn return this.poll.voted || this.expired || !this.loggedIn

View file

@ -78,8 +78,8 @@
v-model="expiryUnit" v-model="expiryUnit"
unstyled="true" unstyled="true"
class="expiry-unit" class="expiry-unit"
@change="expiryAmountChange"
:aria-label="$t('polls.expiry_unit')" :aria-label="$t('polls.expiry_unit')"
@change="expiryAmountChange"
> >
<option <option
v-for="unit in expiryUnits" v-for="unit in expiryUnits"

View file

@ -31,6 +31,7 @@ import { useInterfaceStore } from 'src/stores/interface.js'
import { useMediaViewerStore } from 'src/stores/media_viewer.js' import { useMediaViewerStore } from 'src/stores/media_viewer.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_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' import { pollFormToMasto } from 'src/services/poll/poll.service.js'
@ -570,17 +571,18 @@ const PostStatusForm = {
}, },
// Global stuff // Global stuff
currentUser() { ...mapState(useUsersStore, ['currentUser']),
return this.$store.state.users.currentUser
},
...mapState(useMergedConfigStore, ['mergedConfig']), ...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useInterfaceStore, { ...mapState(useInterfaceStore, {
mobileLayout: (store) => store.mobileLayout, mobileLayout: (store) => store.mobileLayout,
}), }),
}, },
watch: { watch: {
isDirty(newVal, oldVal) { newStatus: {
this.statusChanged() deep: true,
handler() {
this.statusChanged()
},
}, },
saveable(val) { saveable(val) {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#usage_notes // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#usage_notes

View file

@ -4,6 +4,7 @@ import Modal from 'src/components/modal/modal.vue'
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
import { usePostStatusStore } from 'src/stores/post_status.js' import { usePostStatusStore } from 'src/stores/post_status.js'
import { useUsersStore } from 'src/stores/users.js'
const PostStatusModal = { const PostStatusModal = {
components: { components: {
@ -17,7 +18,7 @@ const PostStatusModal = {
}, },
computed: { computed: {
isLoggedIn() { isLoggedIn() {
return !!this.$store.state.users.currentUser return !!useUsersStore().currentUser
}, },
modalActivated() { modalActivated() {
return usePostStatusStore().modalActivated return usePostStatusStore().modalActivated

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

@ -5,7 +5,9 @@ import Popover from 'src/components/popover/popover.vue'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useLocalConfigStore } from 'src/stores/local_config.js' import { useLocalConfigStore } from 'src/stores/local_config.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useSyncConfigStore } from 'src/stores/sync_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 { library } from '@fortawesome/fontawesome-svg-core'
import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons' import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
@ -26,7 +28,7 @@ const QuickFilterSettings = {
path: 'replyVisibility', path: 'replyVisibility',
value: visibility, value: visibility,
}) })
this.$store.dispatch('queueFlushAll') useStatusesStore().queueFlushAll()
}, },
openTab(tab) { openTab(tab) {
useInterfaceStore().openSettingsModalTab(tab) useInterfaceStore().openSettingsModalTab(tab)
@ -54,7 +56,7 @@ const QuickFilterSettings = {
} }
}, },
loggedIn() { loggedIn() {
return !!this.$store.state.users.currentUser return !!useUsersStore().currentUser
}, },
replyVisibilitySelf: { replyVisibilitySelf: {
get() { get() {

View file

@ -6,6 +6,7 @@ import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filt
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -36,7 +37,7 @@ const QuickViewSettings = {
mobileLayout: (state) => state.layoutType === 'mobile', mobileLayout: (state) => state.layoutType === 'mobile',
}), }),
loggedIn() { loggedIn() {
return !!this.$store.state.users.currentUser return !!useUsersStore().currentUser
}, },
conversationDisplay: { conversationDisplay: {
get() { get() {

View file

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

View file

@ -93,8 +93,8 @@ export default {
this.$emit('update:id', notice[3]) this.$emit('update:id', notice[3])
} else if (value) { } else if (value) {
this.loading = true this.loading = true
this.$store this.useSearchStore()
.dispatch('search', { .search({
q: value, q: value,
resolve: true, resolve: true,
offset: 0, 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 useVuelidate from '@vuelidate/core'
import { required, requiredIf, sameAs } from '@vuelidate/validators' import { required, requiredIf, sameAs } from '@vuelidate/validators'
import { mapState as mapPiniaState } from 'pinia' import { mapActions, mapState } from 'pinia'
import { mapActions, mapState } from 'vuex'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' 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 TermsOfServicePanel from 'src/components/terms_of_service_panel/terms_of_service_panel.vue'
import localeService from '../../services/locale/locale.service.js' import localeService from '../../services/locale/locale.service.js'
import { useInstanceStore } from 'src/stores/instance.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' import { DAY } from 'src/services/date_utils/date_utils.js'
const registration = { const registration = {
@ -26,6 +28,9 @@ const registration = {
reason: '', reason: '',
language: [''], language: [''],
}, },
signUpPending: false,
signUpErrors: [],
signUpNotice: {},
captcha: {}, captcha: {},
}), }),
components: { components: {
@ -58,7 +63,7 @@ const registration = {
} }
}, },
created() { created() {
if ((!this.registrationOpen && !this.token) || this.signedIn) { if ((!this.registrationOpen && !this.token) || this.loggedIn) {
this.$router.push({ name: 'root' }) 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, registrationOpen: (store) => store.registrationOpen,
embeddedToS: (store) => store.embeddedToS, embeddedToS: (store) => store.embeddedToS,
termsOfService: (store) => store.tos, termsOfService: (store) => store.tos,
@ -109,16 +117,49 @@ const registration = {
birthdayRequired: (store) => store.birthdayRequired, birthdayRequired: (store) => store.birthdayRequired,
birthdayMinAge: (store) => store.birthdayMinAge, birthdayMinAge: (store) => store.birthdayMinAge,
}), }),
...mapState({ ...mapState(useUsersStore, ['loggedIn']),
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,
}),
}, },
methods: { 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() { async submit() {
this.user.nickname = this.user.username this.user.nickname = this.user.username
this.user.token = this.token this.user.token = this.token

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