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

View file

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

View file

@ -63,10 +63,11 @@ export const paramsString = (params = {}) => {
}
export const promisedRequest = async ({
method,
method = 'GET',
url,
payload,
formData,
forceContentType,
cache,
credentials,
headers = {},
@ -75,7 +76,7 @@ export const promisedRequest = async ({
method,
credentials: 'same-origin',
headers: {
Accept: 'application/json',
Accept: forceContentType ?? 'application/json',
...headers,
},
}
@ -110,7 +111,7 @@ export const promisedRequest = async ({
)
if (contentLength === 0) return null
switch (contentType) {
switch (forceContentType ?? contentType) {
case 'text/plain':
return await response.text()
case 'application/json':
@ -123,7 +124,7 @@ export const promisedRequest = async ({
const { ok, status } = response
if (ok) {
return { response, status, data }
return { response, status, data, timestamp: Date.now() }
} else {
throw new StatusCodeError(response.status, data, { url, options }, response)
}

View file

@ -13,12 +13,12 @@ const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
const MASTODON_PASSWORD_RESET_URL = ({ email }) =>
`/auth/password${paramsString({ email })}`
const MASTODON_FOLLOWING_URL = (
export const MASTODON_FOLLOWING_URL = (
id,
{ minId, maxId, sinceId, limit, withRelationships },
) =>
`/api/v1/accounts/${id}/following${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
const MASTODON_FOLLOWERS_URL = (
export const MASTODON_FOLLOWERS_URL = (
id,
{ minId, maxId, sinceId, limit, withRelationships },
) =>
@ -26,15 +26,17 @@ const MASTODON_FOLLOWERS_URL = (
export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}`
const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context`
const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source`
const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history`
export const MASTODON_STATUS_SOURCE_URL = (id) =>
`/api/v1/statuses/${id}/source`
export const MASTODON_STATUS_HISTORY_URL = (id) =>
`/api/v1/statuses/${id}/history`
const MASTODON_USER_URL = '/api/v1/accounts'
const MASTODON_USER_LOOKUP_URL = ({ acct }) =>
`/api/v1/accounts/lookup${paramsString({ acct })}`
const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}`
const MASTODON_STATUS_FAVORITEDBY_URL = (id) =>
export const MASTODON_STATUS_FAVORITEDBY_URL = (id) =>
`/api/v1/statuses/${id}/favourited_by`
const MASTODON_STATUS_REBLOGGEDBY_URL = (id) =>
export const MASTODON_STATUS_REBLOGGEDBY_URL = (id) =>
`/api/v1/statuses/${id}/reblogged_by`
const MASTODON_SEARCH_2 = ({
q,
@ -51,7 +53,7 @@ const MASTODON_SEARCH_2 = ({
const MASTODON_USER_SEARCH_URL = ({ q, resolve }) =>
`/api/v1/accounts/search${paramsString({ q, resolve })}`
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
const PLEROMA_EMOJI_REACTIONS_URL = (id) =>
export const PLEROMA_EMOJI_REACTIONS_URL = (id) =>
`/api/v1/pleroma/statuses/${id}/reactions`
const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) =>
`/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}`
@ -117,9 +119,17 @@ export const fetchUserByName = ({ name, credentials }) =>
export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) =>
promisedRequest({
url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }),
url: MASTODON_FOLLOWING_URL(id, {
maxId,
sinceId,
limit,
withRelationships: true,
}),
credentials,
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
}).then(({ data, ...rest }) => ({
...rest,
data: data.map(parseUser),
}))
export const fetchFollowers = ({
id,
@ -136,7 +146,10 @@ export const fetchFollowers = ({
withRelationships: true,
}),
credentials,
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
}).then(({ data, ...rest }) => ({
...rest,
data: data.map(parseUser),
}))
export const fetchConversation = ({ id, credentials }) =>
promisedRequest({
@ -163,15 +176,18 @@ export const fetchStatusSource = ({ id, credentials }) =>
credentials,
}).then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) }))
export const fetchStatusHistory = ({ status, credentials }) =>
export const fetchStatusHistory = ({ id, credentials }) =>
promisedRequest({
url: MASTODON_STATUS_HISTORY_URL(status.id),
url: MASTODON_STATUS_HISTORY_URL(id),
credentials,
}).then(({ data, ...rest }) => {
return [...data].reverse().map((item) => {
item.originalStatus = status
return { ...rest, data: parseStatus(item) }
})
return {
...rest,
data: [...data].reverse().map((item) => {
item.originalStatus = status
return parseStatus(item)
}),
}
})
export const listEmojiPacks = ({ page, pageSize, credentials }) =>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,7 @@ import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useReportsStore } from 'src/stores/reports'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
@ -47,10 +48,10 @@ const AccountActions = {
this.showingConfirmBlock = false
},
showRepeats() {
this.$store.dispatch('showReblogs', this.user.id)
useUsersStore().showReblogs(this.user.id)
},
hideRepeats() {
this.$store.dispatch('hideReblogs', this.user.id)
useUsersStore().hideReblogs(this.user.id)
},
blockUser() {
if (this.$refs.timedBlockDialog) {
@ -64,11 +65,11 @@ const AccountActions = {
}
},
doBlockUser() {
this.$store.dispatch('blockUser', { id: this.user.id })
useUsersStore().blockUser(this.user.id)
this.hideConfirmBlock()
},
unblockUser() {
this.$store.dispatch('unblockUser', this.user.id)
useUsersStore().unblockUser(this.user.id)
},
removeUserFromFollowers() {
if (!this.shouldConfirmRemoveUserFromFollowers) {
@ -78,7 +79,7 @@ const AccountActions = {
}
},
doRemoveUserFromFollowers() {
this.$store.dispatch('removeUserFromFollowers', this.user.id)
useUsersStore().removeUserFromFollowers(this.user.id)
this.hideConfirmRemoveUserFromFollowers()
},
reportUser() {
@ -88,7 +89,7 @@ const AccountActions = {
this.$router.push({
name: 'chat',
params: {
username: this.$store.state.users.currentUser.screen_name,
username: useUsersStore().currentUser.screen_name,
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 localeService from '../../services/locale/locale.service.js'
import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useUsersStore } from 'src/stores/users.js'
const Announcement = {
components: {
@ -25,9 +26,7 @@ const Announcement = {
announcement: Object,
},
computed: {
...mapState({
currentUser: (state) => state.users.currentUser,
}),
...mapState(useUsersStore, ['currentUser']),
canEditAnnouncement() {
return this.currentUser?.privileges.has(
'announcements_manage_announcements',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@ import { defineAsyncComponent } from 'vue'
import Modal from 'src/components/modal/modal.vue'
import { useEditStatusStore } from 'src/stores/editStatus.js'
import { useUsersStore } from 'src/stores/users.js'
const EditStatusModal = {
components: {
@ -19,7 +20,7 @@ const EditStatusModal = {
},
computed: {
isLoggedIn() {
return !!this.$store.state.users.currentUser
return !!useUsersStore().currentUser
},
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
* data: object providing source information for specific types of suggestions:
@ -77,7 +79,7 @@ export const suggestUsers = ({ dispatch, state }) => {
let timeout = null
let cancelUserSearch = null
const userSearch = (query) => dispatch('searchUsers', { query })
const userSearch = (query) => useSearchStore().searchUsers({ query })
const debounceUserSearch = (query) => {
cancelUserSearch?.()
return new Promise((resolve, reject) => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@ import { debounce } from 'lodash'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { usePostStatusStore } from 'src/stores/post_status.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faPen } from '@fortawesome/free-solid-svg-icons'
@ -34,7 +35,7 @@ const MobilePostStatusButton = {
},
computed: {
isLoggedIn() {
return !!this.$store.state.users.currentUser
return useUsersStore().loggedIn
},
isHidden() {
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 { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronDown } from '@fortawesome/free-solid-svg-icons'
@ -405,7 +406,7 @@ const ModerationTools = {
)
},
isAdmin() {
return this.$store.state.users.currentUser.role === 'admin'
return useUsersStore().currentUser.role === 'admin'
},
},
methods: {
@ -452,7 +453,7 @@ const ModerationTools = {
},
privileged(privilege) {
if (this.isAdmin) return true
return this.$store.state.users.currentUser.privileges.has(privilege)
return useUsersStore().currentUser.privileges.has(privilege)
},
setTag(tag, value) {
useAdminSettingsStore().setUsersTags({
@ -516,8 +517,7 @@ const ModerationTools = {
setOpen(value) {
this.open = value
},
maybeShowConfirm(close, { group, name, action, value }) {
close()
maybeShowConfirm({ group, name, action, value }) {
this.confirmDialogName = name
this.confirmDialogGroup = group
this.confirmDialogAction = () => action()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -78,8 +78,8 @@
v-model="expiryUnit"
unstyled="true"
class="expiry-unit"
@change="expiryAmountChange"
:aria-label="$t('polls.expiry_unit')"
@change="expiryAmountChange"
>
<option
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 { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
import { pollFormToMasto } from 'src/services/poll/poll.service.js'
@ -570,17 +571,18 @@ const PostStatusForm = {
},
// Global stuff
currentUser() {
return this.$store.state.users.currentUser
},
...mapState(useUsersStore, ['currentUser']),
...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useInterfaceStore, {
mobileLayout: (store) => store.mobileLayout,
}),
},
watch: {
isDirty(newVal, oldVal) {
this.statusChanged()
newStatus: {
deep: true,
handler() {
this.statusChanged()
},
},
saveable(val) {
// 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 { usePostStatusStore } from 'src/stores/post_status.js'
import { useUsersStore } from 'src/stores/users.js'
const PostStatusModal = {
components: {
@ -17,7 +18,7 @@ const PostStatusModal = {
},
computed: {
isLoggedIn() {
return !!this.$store.state.users.currentUser
return !!useUsersStore().currentUser
},
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 { useLocalConfigStore } from 'src/stores/local_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 { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
@ -26,7 +28,7 @@ const QuickFilterSettings = {
path: 'replyVisibility',
value: visibility,
})
this.$store.dispatch('queueFlushAll')
useStatusesStore().queueFlushAll()
},
openTab(tab) {
useInterfaceStore().openSettingsModalTab(tab)
@ -54,7 +56,7 @@ const QuickFilterSettings = {
}
},
loggedIn() {
return !!this.$store.state.users.currentUser
return !!useUsersStore().currentUser
},
replyVisibilitySelf: {
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 { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -36,7 +37,7 @@ const QuickViewSettings = {
mobileLayout: (state) => state.layoutType === 'mobile',
}),
loggedIn() {
return !!this.$store.state.users.currentUser
return !!useUsersStore().currentUser
},
conversationDisplay: {
get() {

View file

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

View file

@ -93,8 +93,8 @@ export default {
this.$emit('update:id', notice[3])
} else if (value) {
this.loading = true
this.$store
.dispatch('search', {
this.useSearchStore()
.search({
q: value,
resolve: true,
offset: 0,

View file

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

View file

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

View file

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

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