Merge pull request 'Migrate rest of modules to pinia, remove vuex, replace lodash with lodash-es' (#3571) from vuex-removal into develop

Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3571
This commit is contained in:
HJ 2026-09-03 22:00:39 +00:00
commit 7bc4fd7be7
128 changed files with 1980 additions and 1662 deletions

View file

@ -1,5 +1,5 @@
{ {
"presets": ["@babel/preset-env"], "presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"], "plugins": ["@babel/plugin-transform-runtime", "@vue/babel-plugin-jsx"],
"comments": true "comments": true
} }

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

@ -0,0 +1 @@
Fixed drafts creating duplicates on edit instead of updating

View file

@ -81,7 +81,7 @@ In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with
PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/src/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation. PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/src/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
For most part, PleromaFE tries to store all the info it can get in global vuex store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters. For most part, PleromaFE tries to store all the info it can get in global pinia store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters.
PleromaFE also tries to persist this store, however only stable data is stored, such as user authentication and preferences, user highlights. Persistence is performed by saving and loading chunk of vuex store in browser's LocalStorage/IndexedDB. PleromaFE also tries to persist this store, however only stable data is stored, such as user authentication and preferences, user highlights. Persistence is performed by saving and loading chunk of vuex store in browser's LocalStorage/IndexedDB.

View file

@ -42,6 +42,7 @@
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"localforage": "1.10.0", "localforage": "1.10.0",
"lodash-es": "4.17.21",
"parse-link-header": "2.0.0", "parse-link-header": "2.0.0",
"phoenix": "1.8.1", "phoenix": "1.8.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
@ -54,8 +55,7 @@
"vue": "3.5.22", "vue": "3.5.22",
"vue-i18n": "11", "vue-i18n": "11",
"vue-router": "4.6.4", "vue-router": "4.6.4",
"vue-virtual-scroller": "^2.0.0-beta.7", "vue-virtual-scroller": "^2.0.0-beta.7"
"vuex": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.28.5", "@babel/core": "7.28.5",
@ -78,7 +78,6 @@
"@vue/compiler-sfc": "3.5.22", "@vue/compiler-sfc": "3.5.22",
"@vue/test-utils": "2.4.6", "@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.21", "autoprefixer": "10.4.21",
"babel-plugin-lodash": "3.3.4",
"chai": "5.3.3", "chai": "5.3.3",
"chalk": "5.6.2", "chalk": "5.6.2",
"chromedriver": "135.0.4", "chromedriver": "135.0.4",
@ -97,7 +96,6 @@
"function-bind": "1.1.2", "function-bind": "1.1.2",
"http-proxy-middleware": "3.0.5", "http-proxy-middleware": "3.0.5",
"iso-639-1": "3.1.5", "iso-639-1": "3.1.5",
"lodash": "4.17.21",
"msw": "2.14.6", "msw": "2.14.6",
"nightwatch": "3.12.2", "nightwatch": "3.12.2",
"oxc": "^1.0.1", "oxc": "^1.0.1",

View file

@ -1,4 +1,4 @@
import { throttle } from 'lodash' import { throttle } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
@ -49,6 +49,12 @@ export default {
MobilePostStatusButton, MobilePostStatusButton,
MobileNav, MobileNav,
DesktopNav, DesktopNav,
FollowRequestConfirm: defineAsyncComponent(
() =>
import(
'src/components/follow_request_confirm/follow_request_confirm.vue'
),
),
SettingsModal: defineAsyncComponent( SettingsModal: defineAsyncComponent(
() => import('src/components/settings_modal/settings_modal.vue'), () => import('src/components/settings_modal/settings_modal.vue'),
), ),

View file

@ -75,6 +75,7 @@
<UpdateNotification /> <UpdateNotification />
<GlobalError /> <GlobalError />
<GlobalNoticeList /> <GlobalNoticeList />
<FollowRequestConfirm v-if="currentUser" />
</div> </div>
</template> </template>

View file

@ -1,4 +1,4 @@
import { snakeCase } from 'lodash' import { snakeCase } from 'lodash-es'
import { StatusCodeError } from 'src/services/errors/errors' import { StatusCodeError } from 'src/services/errors/errors'

View file

@ -1,4 +1,4 @@
import { last } from 'lodash' import { last } from 'lodash-es'
import { paramsString, promisedRequest } from './helpers.js' import { paramsString, promisedRequest } from './helpers.js'
import { fetchFriends, MASTODON_STATUS_URL } from './public.js' import { fetchFriends, MASTODON_STATUS_URL } from './public.js'
@ -17,7 +17,11 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
const MOVE_ACCOUNT_URL = '/api/pleroma/move_account' const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
const ALIASES_URL = '/api/pleroma/aliases' const ALIASES_URL = '/api/pleroma/aliases'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' const NOTIFICATION_SETTINGS_URL = ({
blockFromStrangers,
hideNotificationContents,
}) =>
`/api/pleroma/notification_settings${paramsString({ blockFromStrangers, hideNotificationContents })}`
export const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read' export const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa' const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
@ -39,9 +43,10 @@ export const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow`
export const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow` export const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow`
const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests' const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
const MASTODON_APPROVE_USER_URL = (id) => export const MASTODON_APPROVE_USER_URL = (id) =>
`/api/v1/follow_requests/${id}/authorize` `/api/v1/follow_requests/${id}/authorize`
const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject` export const MASTODON_DENY_USER_URL = (id) =>
`/api/v1/follow_requests/${id}/reject`
const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) => const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) =>
`/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}` `/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}`
export const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists` export const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
@ -432,10 +437,9 @@ export const exportFriends = ({ id, credentials }) => {
// #Profile settings // #Profile settings
export const updateNotificationSettings = ({ credentials, settings }) => { export const updateNotificationSettings = ({ credentials, settings }) => {
return promisedRequest({ return promisedRequest({
url: NOTIFICATION_SETTINGS_URL, url: NOTIFICATION_SETTINGS_URL(settings),
credentials, credentials,
method: 'PUT', method: 'PUT',
payload: settings,
}) })
} }

View file

@ -1,4 +1,4 @@
import { orderBy, uniqueId } from 'lodash' import { orderBy, uniqueId } from 'lodash-es'
import ChatMessage from 'src/components/chat_message/chat_message.vue' import ChatMessage from 'src/components/chat_message/chat_message.vue'

View file

@ -71,7 +71,6 @@ const chatNew = {
this.loading = true this.loading = true
this.userIds = [] this.userIds = []
this.$store
useSearchStore() useSearchStore()
.search({ q: query, resolve: true, type: 'accounts' }) .search({ q: query, resolve: true, type: 'accounts' })
.then((data) => { .then((data) => {

View file

@ -1,4 +1,4 @@
import { get, maxBy, minBy, sortBy, throttle } from 'lodash' import { get, maxBy, minBy, sortBy, throttle } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { nextTick } from 'vue' import { nextTick } from 'vue'

View file

@ -64,7 +64,7 @@
</div> </div>
</template> </template>
<script> <script>
import { throttle } from 'lodash' import { throttle } from 'lodash-es'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js' import { hex2rgb } from '../../services/color_convert/color_convert.js'

View file

@ -1,4 +1,4 @@
import { get, reduce } from 'lodash' import { get, reduce } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue' import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue'

View file

@ -1,11 +1,10 @@
import { cloneDeep } from 'lodash'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import Gallery from 'src/components/gallery/gallery.vue' import Gallery from 'src/components/gallery/gallery.vue'
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
import StatusContent from 'src/components/status_content/status_content.vue' import StatusContent from 'src/components/status_content/status_content.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useDraftsStore } from 'src/stores/drafts.js'
import { useStatusesStore } from 'src/stores/statuses.js' import { useStatusesStore } from 'src/stores/statuses.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -33,7 +32,6 @@ const Draft = {
}, },
data() { data() {
return { return {
referenceDraft: cloneDeep(this.draft),
editing: false, editing: false,
showingConfirmDialog: false, showingConfirmDialog: false,
} }
@ -50,14 +48,6 @@ const Draft = {
return {} return {}
} }
}, },
safeToSave() {
return (
this.draft.status ||
this.draft.files?.length ||
this.draft.hasPoll ||
this.draft.hasQuote
)
},
postStatusFormProps() { postStatusFormProps() {
return { return {
draftId: this.draft.id, draftId: this.draft.id,
@ -69,18 +59,12 @@ const Draft = {
? useStatusesStore().allStatuses.get(this.draft.refId) ? useStatusesStore().allStatuses.get(this.draft.refId)
: undefined : undefined
}, },
localCollapseSubjectDefault() {
return useMergedConfigStore().mergedConfig.collapseMessageWithSubject
},
}, },
watch: { watch: {
editing(newVal) { editing(newVal) {
if (newVal) return if (newVal) return
if (this.safeToSave) { // (Post|Edit)StatusForm handles draft saving
this.$store.dispatch('addOrSaveDraft', { draft: this.draft }) this.$refs.form.saveDraft()
} else {
this.$store.dispatch('addOrSaveDraft', { draft: this.referenceDraft })
}
}, },
}, },
methods: { methods: {
@ -91,9 +75,11 @@ const Draft = {
this.showingConfirmDialog = true this.showingConfirmDialog = true
}, },
doAbandon() { doAbandon() {
this.$store.dispatch('abandonDraft', { id: this.draft.id }).then(() => { useDraftsStore()
this.hideConfirmDialog() .abandonDraft(this.draft.id)
}) .then(() => {
this.hideConfirmDialog()
})
}, },
hideConfirmDialog() { hideConfirmDialog() {
this.showingConfirmDialog = false this.showingConfirmDialog = false

View file

@ -67,11 +67,13 @@
<div v-if="editing"> <div v-if="editing">
<PostStatusForm <PostStatusForm
v-if="draft.type !== 'edit'" v-if="draft.type !== 'edit'"
ref="form"
:hide-draft="true" :hide-draft="true"
v-bind="postStatusFormProps" v-bind="postStatusFormProps"
/> />
<EditStatusForm <EditStatusForm
v-else v-else
ref="form"
:hide-draft="true" :hide-draft="true"
:params="postStatusFormProps" :params="postStatusFormProps"
/> />

View file

@ -3,6 +3,8 @@ import { defineAsyncComponent } from 'vue'
import Draft from 'src/components/draft/draft.vue' import Draft from 'src/components/draft/draft.vue'
import List from 'src/components/list/list.vue' import List from 'src/components/list/list.vue'
import { useDraftsStore } from 'src/stores/drafts.js'
const Drafts = { const Drafts = {
components: { components: {
Draft, Draft,
@ -18,7 +20,7 @@ const Drafts = {
}, },
computed: { computed: {
drafts() { drafts() {
return this.$store.getters.draftsArray return useDraftsStore().draftsArray
}, },
}, },
methods: { methods: {
@ -26,8 +28,8 @@ const Drafts = {
this.showingConfirmDialog = true this.showingConfirmDialog = true
}, },
doAbandonAll() { doAbandonAll() {
this.$store useDraftsStore()
.dispatch('abandonAllDrafts') .abandonAllDrafts()
.then(() => this.hideConfirmDialog()) .then(() => this.hideConfirmDialog())
}, },
hideConfirmDialog() { hideConfirmDialog() {

View file

@ -15,9 +15,11 @@ const EditStatusForm = {
requestClose() { requestClose() {
this.$refs.postStatusForm.requestClose() this.$refs.postStatusForm.requestClose()
}, },
saveDraft() {
this.$refs.postStatusForm.saveDraft()
},
doEditStatus({ status, spoilerText, sensitive, media, contentType, poll }) { doEditStatus({ status, spoilerText, sensitive, media, contentType, poll }) {
const params = { const params = {
store: this.$store,
statusId: this.params.statusId, statusId: this.params.statusId,
status, status,
spoilerText, spoilerText,

View file

@ -1,4 +1,4 @@
import { get } from 'lodash' import { get } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'

View file

@ -1,4 +1,4 @@
import { take } from 'lodash' import { take } from 'lodash-es'
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'
import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue' import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue'

View file

@ -15,7 +15,7 @@ import { useUsersStore } from 'src/stores/users.js'
export default (data) => { export default (data) => {
const emojiCurry = suggestEmoji(data.emoji) const emojiCurry = suggestEmoji(data.emoji)
const usersCurry = data.store && suggestUsers(data.store) const usersCurry = suggestUsers()
return (input, nameKeywordLocalizer) => { return (input, nameKeywordLocalizer) => {
const firstChar = input[0] const firstChar = input[0]
if (firstChar === ':' && data.emoji) { if (firstChar === ':' && data.emoji) {

View file

@ -1,4 +1,4 @@
import { chunk, debounce, trim } from 'lodash' import { chunk, debounce, trim } from 'lodash-es'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'

View file

@ -1,8 +1,8 @@
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { mapGetters } from 'vuex'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js' import { useChatsStore } from 'src/stores/chats.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
@ -30,14 +30,14 @@ const ExtraNotifications = {
return ( return (
this.mergedConfig.showExtraNotifications && this.mergedConfig.showExtraNotifications &&
this.mergedConfig.showAnnouncementsInExtraNotifications && this.mergedConfig.showAnnouncementsInExtraNotifications &&
this.unreadAnnouncementCount this.unreadAnnouncementsCount
) )
}, },
shouldShowFollowRequests() { shouldShowFollowRequests() {
return ( return (
this.mergedConfig.showExtraNotifications && this.mergedConfig.showExtraNotifications &&
this.mergedConfig.showFollowRequestsInExtraNotifications && this.mergedConfig.showFollowRequestsInExtraNotifications &&
this.followRequestCount this.followRequestsCount
) )
}, },
hasAnythingToShow() { hasAnythingToShow() {
@ -55,12 +55,12 @@ const ExtraNotifications = {
currentUser() { currentUser() {
return useUsersStore().currentUser return useUsersStore().currentUser
}, },
...mapGetters(['followRequestCount']),
...mapState(useAnnouncementsStore, { ...mapState(useAnnouncementsStore, {
unreadAnnouncementCount: 'unreadAnnouncementCount', unreadAnnouncementsCount: 'unreadAnnouncementsCount',
}), }),
...mapState(useMergedConfigStore, ['mergedConfig']), ...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useChatsStore, ['unreadChatsCount']), ...mapState(useChatsStore, ['unreadChatsCount']),
...mapState(useFollowRequestsStore, ['followRequestsCount']),
}, },
methods: { methods: {
openNotificationSettings() { openNotificationSettings() {

View file

@ -31,7 +31,7 @@
class="fa-scale-110 icon" class="fa-scale-110 icon"
icon="bullhorn" icon="bullhorn"
/> />
{{ $t('notifications.unread_announcements', { num: unreadAnnouncementCount }, unreadAnnouncementCount) }} {{ $t('notifications.unread_announcements', { num: unreadAnnouncementsCount }, unreadAnnouncementsCount) }}
</router-link> </router-link>
</div> </div>
<div <div
@ -48,7 +48,7 @@
class="fa-scale-110 icon" class="fa-scale-110 icon"
icon="user-plus" icon="user-plus"
/> />
{{ $t('notifications.unread_follow_requests', { num: followRequestCount }, followRequestCount) }} {{ $t('notifications.unread_follow_requests', { num: followRequestsCount }, followRequestsCount) }}
</router-link> </router-link>
</div> </div>
<i18n-t <i18n-t

View file

@ -1,98 +1,16 @@
import { defineAsyncComponent } from 'vue' import { mapActions } from 'pinia'
import BasicUserCard from '../basic_user_card/basic_user_card.vue' import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { approveUser, denyUser } from 'src/api/user.js'
const FollowRequestCard = { const FollowRequestCard = {
props: ['user'], props: ['user'],
components: { components: {
BasicUserCard, BasicUserCard,
ConfirmModal: defineAsyncComponent(
() => import('src/components/confirm_modal/confirm_modal.vue'),
),
},
data() {
return {
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false,
}
}, },
methods: { methods: {
findFollowRequestNotificationId() { ...mapActions(useFollowRequestsStore, ['approve', 'deny']),
const notif = useNotificationsStore().data.find(
(notif) =>
notif.from_profile.id === this.user.id &&
notif.type === 'follow_request',
)
return notif?.id
},
showApproveConfirmDialog() {
this.showingApproveConfirmDialog = true
},
hideApproveConfirmDialog() {
this.showingApproveConfirmDialog = false
},
showDenyConfirmDialog() {
this.showingDenyConfirmDialog = true
},
hideDenyConfirmDialog() {
this.showingDenyConfirmDialog = false
},
approveUser() {
if (this.shouldConfirmApprove) {
this.showApproveConfirmDialog()
} else {
this.doApprove()
}
},
doApprove() {
approveUser({
id: this.user.id,
credentials: useOAuthStore().token,
})
// TODO fix
this.$store.dispatch('removeFollowRequest', this.user)
const notifId = this.findFollowRequestNotificationId()
useNotificationsStore().markSingleNotificationAsSeen(notifId)
this.hideApproveConfirmDialog()
},
denyUser() {
if (this.shouldConfirmDeny) {
this.showDenyConfirmDialog()
} else {
this.doDeny()
}
},
doDeny() {
const notifId = this.findFollowRequestNotificationId()
denyUser({
id: this.user.id,
credentials: useOAuthStore().token,
}).then(() => {
useNotificationsStore().dismissNotificationLocal(notifId)
// TODO fix
this.$store.dispatch('removeFollowRequest', this.user)
})
this.hideDenyConfirmDialog()
},
},
computed: {
mergedConfig() {
return useMergedConfigStore().mergedConfig
},
shouldConfirmApprove() {
return this.mergedConfig.modalOnApproveFollow
},
shouldConfirmDeny() {
return this.mergedConfig.modalOnDenyFollow
},
}, },
} }

View file

@ -3,39 +3,17 @@
<div class="follow-request-card-content-container"> <div class="follow-request-card-content-container">
<button <button
class="btn button-default" class="btn button-default"
@click="approveUser" @click="() => approve(user.id)"
> >
{{ $t('user_card.approve') }} {{ $t('user_card.approve') }}
</button> </button>
<button <button
class="btn button-default" class="btn button-default"
@click="denyUser" @click="() => deny(user.id)"
> >
{{ $t('user_card.deny') }} {{ $t('user_card.deny') }}
</button> </button>
</div> </div>
<teleport to="#modal">
<ConfirmModal
v-if="showingApproveConfirmDialog"
:title="$t('user_card.approve_confirm_title')"
:confirm-text="$t('user_card.approve_confirm_accept_button')"
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
@accepted="doApprove"
@cancelled="hideApproveConfirmDialog"
>
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
</ConfirmModal>
<ConfirmModal
v-if="showingDenyConfirmDialog"
:title="$t('user_card.deny_confirm_title')"
:confirm-text="$t('user_card.deny_confirm_accept_button')"
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
@accepted="doDeny"
@cancelled="hideDenyConfirmDialog"
>
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
</ConfirmModal>
</teleport>
</basic-user-card> </basic-user-card>
</template> </template>

View file

@ -0,0 +1,38 @@
<template>
<teleport to="#modal">
<div>
<ConfirmModal
v-if="store.showingApproveConfirmDialog"
:title="$t('user_card.approve_confirm_title')"
:confirm-text="$t('user_card.approve_confirm_accept_button')"
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
@accepted="store.doApprove"
@cancelled="store.hideApproveConfirmDialog"
>
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
</ConfirmModal>
<ConfirmModal
v-if="store.showingDenyConfirmDialog"
:title="$t('user_card.deny_confirm_title')"
:confirm-text="$t('user_card.deny_confirm_accept_button')"
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
@accepted="store.doDeny"
@cancelled="store.hideDenyConfirmDialog"
>
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
</ConfirmModal>
</div>
</teleport>
</template>
<script setup>
import { computed } from 'vue'
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useUsersStore } from 'src/stores/users.js'
const store = useFollowRequestsStore()
const user = computed(() => useUsersStore().findUser(store.tempId))
</script>

View file

@ -1,12 +1,14 @@
import FollowRequestCard from 'src/components/follow_request_card/follow_request_card.vue' import FollowRequestCard from 'src/components/follow_request_card/follow_request_card.vue'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
const FollowRequests = { const FollowRequests = {
components: { components: {
FollowRequestCard, FollowRequestCard,
}, },
computed: { computed: {
requests() { requests() {
return this.$store.state.api.followRequests return useFollowRequestsStore().requests.values()
}, },
}, },
} }

View file

@ -1,4 +1,4 @@
import { set, sumBy } from 'lodash' import { set, sumBy } from 'lodash-es'
import Attachment from 'src/components/attachment/attachment.vue' import Attachment from 'src/components/attachment/attachment.vue'

View file

@ -1,4 +1,4 @@
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash-es'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'

View file

@ -1,4 +1,4 @@
import { debounce } from 'lodash' import { debounce } from 'lodash-es'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'

View file

@ -122,7 +122,6 @@ const mediaUpload = {
}, },
async uploadFile(file) { async uploadFile(file) {
const self = this const self = this
const store = this.$store
if (file.size > useInstanceStore().uploadlimit) { if (file.size > useInstanceStore().uploadlimit) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size) const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat( const allowedsize = fileSizeFormatService.fileSizeFormat(
@ -145,7 +144,7 @@ const mediaUpload = {
self.$emit('uploading') self.$emit('uploading')
self.uploadCount++ self.uploadCount++
statusPosterService.uploadMedia({ store, formData }).then( statusPosterService.uploadMedia({ formData }).then(
(fileData) => { (fileData) => {
self.$emit('uploaded', fileData) self.$emit('uploaded', fileData)
self.decreaseUploadCount() self.decreaseUploadCount()

View file

@ -10,6 +10,7 @@ import {
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js' import { useChatsStore } from 'src/stores/chats.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js' import { useNotificationsStore } from 'src/stores/notifications.js'
@ -67,10 +68,10 @@ const MobileNav = {
return ( return (
this.unseenNotifications.length + this.unseenNotifications.length +
countExtraNotifications( countExtraNotifications(
this.$store,
useMergedConfigStore().mergedConfig, useMergedConfigStore().mergedConfig,
useChatsStore().unreadChatsCount, useChatsStore().unreadChatsCount,
useAnnouncementsStore().unreadAnnouncementCount, useAnnouncementsStore().unreadAnnouncementsCount,
useFollowRequestsStore().followRequestsCount,
) )
) )
}, },
@ -95,7 +96,7 @@ const MobileNav = {
closingDrawerMarksAsSeen() { closingDrawerMarksAsSeen() {
return useMergedConfigStore().mergedConfig.closingDrawerMarksAsSeen return useMergedConfigStore().mergedConfig.closingDrawerMarksAsSeen
}, },
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), ...mapState(useAnnouncementsStore, ['unreadAnnouncementsCount']),
...mapState(useMergedConfigStore, { ...mapState(useMergedConfigStore, {
pinnedItems: (store) => pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'), new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'),

View file

@ -19,7 +19,7 @@
icon="bars" icon="bars"
/> />
<div <div
v-if="(unreadChatsCount && !chatsPinned) || unreadAnnouncementCount" v-if="(unreadChatsCount && !chatsPinned) || unreadAnnouncementsCount"
class="badge -dot -notification" class="badge -dot -notification"
/> />
</button> </button>

View file

@ -1,4 +1,4 @@
import { debounce } from 'lodash' import { debounce } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'

View file

@ -1,4 +1,4 @@
import { last } from 'lodash' import { last } from 'lodash-es'
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue' import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'

View file

@ -1,4 +1,4 @@
import { get } from 'lodash' import { get } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'

View file

@ -1,5 +1,4 @@
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { mapState as mapVuexState } from 'vuex'
import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue' import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
@ -11,6 +10,7 @@ import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useChatsStore } from 'src/stores/chats.js' import { useChatsStore } from 'src/stores/chats.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
@ -112,7 +112,7 @@ const NavPanel = {
}, },
computed: { computed: {
...mapState(useAnnouncementsStore, { ...mapState(useAnnouncementsStore, {
unreadAnnouncementCount: 'unreadAnnouncementCount', unreadAnnouncementsCount: 'unreadAnnouncementsCount',
supportsAnnouncements: (store) => store.supportsAnnouncements, supportsAnnouncements: (store) => store.supportsAnnouncements,
}), }),
...mapState(useInstanceCapabilitiesStore, [ ...mapState(useInstanceCapabilitiesStore, [
@ -130,9 +130,7 @@ const NavPanel = {
new Set(store.prefsStorage.collections.pinnedNavItems), new Set(store.prefsStorage.collections.pinnedNavItems),
}), }),
...mapState(useUsersStore, ['currentUser']), ...mapState(useUsersStore, ['currentUser']),
...mapVuexState({ ...mapState(useFollowRequestsStore, ['followRequestsCount']),
followRequestCount: (state) => state.api.followRequests.length,
}),
...mapState(useChatsStore, ['unreadChatsCount']), ...mapState(useChatsStore, ['unreadChatsCount']),
timelinesItems() { timelinesItems() {
return filterNavigation( return filterNavigation(

View file

@ -76,7 +76,7 @@ export const ROOT_ITEMS = {
icon: 'comments', icon: 'comments',
label: 'nav.chats', label: 'nav.chats',
badgeStyle: 'notification', badgeStyle: 'notification',
badgeGetter: 'unreadChatsCount', badgeGetter: 'unreadChats',
criteria: ['chats'], criteria: ['chats'],
}, },
friendRequests: { friendRequests: {
@ -85,7 +85,7 @@ export const ROOT_ITEMS = {
label: 'nav.friend_requests', label: 'nav.friend_requests',
badgeStyle: 'notification', badgeStyle: 'notification',
criteria: ['lockedUser'], criteria: ['lockedUser'],
badgeGetter: 'followRequestCount', badgeGetter: 'followRequests',
}, },
about: { about: {
route: 'about', route: 'about',
@ -99,7 +99,7 @@ export const ROOT_ITEMS = {
label: 'nav.announcements', label: 'nav.announcements',
store: 'announcements', store: 'announcements',
badgeStyle: 'notification', badgeStyle: 'notification',
badgeGetter: 'unreadAnnouncementCount', badgeGetter: 'unreadAnnouncements',
criteria: ['announcements'], criteria: ['announcements'],
}, },
drafts: { drafts: {
@ -107,7 +107,7 @@ export const ROOT_ITEMS = {
icon: 'file-pen', icon: 'file-pen',
label: 'nav.drafts', label: 'nav.drafts',
badgeStyle: 'neutral', badgeStyle: 'neutral',
badgeGetter: 'draftCount', badgeGetter: 'drafts',
}, },
} }

View file

@ -1,9 +1,12 @@
import { mapState, mapStores } from 'pinia' import { mapState } from 'pinia'
import { routeTo } from 'src/components/navigation/navigation.js' import { routeTo } from 'src/components/navigation/navigation.js'
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue' import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useDraftsStore } from 'src/stores/drafts.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js' import { useUsersStore } from 'src/stores/users.js'
@ -40,11 +43,19 @@ const NavigationEntry = {
routeTo() { routeTo() {
return routeTo(this.item, this.currentUser) return routeTo(this.item, this.currentUser)
}, },
getters() { badges() {
return this.$store.getters return {
drafts: this.draftsCount,
unreadAnnouncements: this.unreadAnnouncementsCount,
followRequests: this.followRequestsCount,
unreadChats: this.unreadChatsCount,
}
}, },
...mapStores(useAnnouncementsStore), ...mapState(useAnnouncementsStore, ['unreadAnnouncementsCount']),
...mapState(useDraftsStore, ['draftsCount']),
...mapState(useUsersStore, ['currentUser']), ...mapState(useUsersStore, ['currentUser']),
...mapState(useChatsStore, ['unreadChatsCount']),
...mapState(useFollowRequestsStore, ['followRequestsCount']),
...mapState(useSyncConfigStore, { ...mapState(useSyncConfigStore, {
pinnedItems: (store) => pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems), new Set(store.prefsStorage.collections.pinnedNavItems),

View file

@ -47,17 +47,11 @@
</component> </component>
<slot /> <slot />
<div <div
v-if="item.badgeGetter && getters[item.badgeGetter]" v-if="item.badgeGetter && badges[item.badgeGetter]"
class="badge" class="badge"
:class="[`-${item.badgeStyle}`]" :class="[`-${item.badgeStyle}`]"
> >
{{ getters[item.badgeGetter] }} {{ badges[item.badgeGetter] }}
</div>
<div
v-else-if="item.badgeGetter && item.store && this[`${item.store}Store`][item.badgeGetter]"
class="badge badge-notification"
>
{{ this[`${item.store}Store`][item.badgeGetter] }}
</div> </div>
<button <button
v-if="showPin && currentUser" v-if="showPin && currentUser"

View file

@ -1,5 +1,4 @@
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { mapState as mapVuexState } from 'vuex'
import { import {
filterNavigation, filterNavigation,
@ -14,6 +13,9 @@ import {
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
import { useChatsStore } from 'src/stores/chats.js'
import { useDraftsStore } from 'src/stores/drafts.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useListsStore } from 'src/stores/lists' import { useListsStore } from 'src/stores/lists'
@ -56,18 +58,27 @@ const NavPanel = {
}, },
components: {}, components: {},
computed: { computed: {
getters() { badges() {
return this.$store.getters return {
drafts: this.draftsCount,
unreadAnnouncements: this.unreadAnnouncementsCount,
followRequests: this.followRequestsCount,
unreadChats: this.unreadChatsCount,
}
}, },
...mapState(useListsStore, { ...mapState(useListsStore, {
lists: getListEntries, lists: getListEntries,
}), }),
...mapState(useAnnouncementsStore, { ...mapState(useAnnouncementsStore, {
supportsAnnouncements: (store) => store.supportsAnnouncements, supportsAnnouncements: (store) => store.supportsAnnouncements,
unreadAnnouncementsCount: 'unreadAnnouncementsCount',
}), }),
...mapState(useDraftsStore, ['draftsCount']),
...mapState(useFollowRequestsStore, ['followRequestsCount']),
...mapState(useBookmarkFoldersStore, { ...mapState(useBookmarkFoldersStore, {
bookmarks: getBookmarkFolderEntries, bookmarks: getBookmarkFolderEntries,
}), }),
...mapState(useChatsStore, ['unreadChatsCount']),
...mapState(useSyncConfigStore, { ...mapState(useSyncConfigStore, {
pinnedItems: (store) => pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems), new Set(store.prefsStorage.collections.pinnedNavItems),
@ -78,9 +89,6 @@ const NavPanel = {
'localBubble', 'localBubble',
]), ]),
...mapState(useUsersStore, ['currentUser']), ...mapState(useUsersStore, ['currentUser']),
...mapVuexState({
followRequestCount: (state) => state.api.followRequests.length,
}),
pinnedList() { pinnedList() {
if (!this.currentUser) { if (!this.currentUser) {
return filterNavigation( return filterNavigation(

View file

@ -23,7 +23,7 @@
:src="item.iconEmojiUrl" :src="item.iconEmojiUrl"
/> />
<div <div
v-if="item.badgeGetter && getters[item.badgeGetter]" v-if="item.badgeGetter && badges[item.badgeGetter]"
class="badge -dot" class="badge -dot"
:class="[`-${item.badgeStyle}`]" :class="[`-${item.badgeStyle}`]"
/> />

View file

@ -1,5 +1,4 @@
import { mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import { defineAsyncComponent } from 'vue'
import Report from 'src/components/report/report.vue' import Report from 'src/components/report/report.vue'
import StatusContent from 'src/components/status_content/status_content.vue' import StatusContent from 'src/components/status_content/status_content.vue'
@ -13,15 +12,13 @@ import {
highlightStyle, highlightStyle,
} from '../../services/user_highlighter/user_highlighter.js' } from '../../services/user_highlighter/user_highlighter.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js' import { useStatusesStore } from 'src/stores/statuses.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import { useUsersStore } from 'src/stores/users.js' import { 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' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -57,8 +54,6 @@ const Notification = {
selecting: false, selecting: false,
statusExpanded: false, statusExpanded: false,
unmuted: false, unmuted: false,
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false,
} }
}, },
props: ['notification'], props: ['notification'],
@ -72,9 +67,6 @@ const Notification = {
UserPopover, UserPopover,
UserLink, UserLink,
ConfirmModal: defineAsyncComponent(
() => import('src/components/confirm_modal/confirm_modal.vue'),
),
}, },
mounted() { mounted() {
document.addEventListener('selectionchange', this.onContentSelect) document.addEventListener('selectionchange', this.onContentSelect)
@ -124,53 +116,7 @@ const Notification = {
toggleMute() { toggleMute() {
this.unmuted = !this.unmuted this.unmuted = !this.unmuted
}, },
showApproveConfirmDialog() { ...mapActions(useFollowRequestsStore, ['approve', 'deny']),
this.showingApproveConfirmDialog = true
},
hideApproveConfirmDialog() {
this.showingApproveConfirmDialog = false
},
showDenyConfirmDialog() {
this.showingDenyConfirmDialog = true
},
hideDenyConfirmDialog() {
this.showingDenyConfirmDialog = false
},
approveUser() {
if (this.shouldConfirmApprove) {
this.showApproveConfirmDialog()
} else {
this.doApprove()
}
},
doApprove() {
approveUser({
id: this.user.id,
credentials: useOAuthStore().token,
})
// TODO Fix this
this.$store.dispatch('removeFollowRequest', this.user)
useNotificationsStore().markSingleNotificationAsSeen(this.notification.id)
this.hideApproveConfirmDialog()
},
denyUser() {
if (this.shouldConfirmDeny) {
this.showDenyConfirmDialog()
} else {
this.doDeny()
}
},
doDeny() {
denyUser({
id: this.user.id,
credentials: useOAuthStore().token,
}).then(() => {
useNotificationsStore().dismissNotificationLocal(this.notification.id)
// TODO Fix this
this.$store.dispatch('removeFollowRequest', this.user)
})
this.hideDenyConfirmDialog()
},
}, },
computed: { computed: {
status() { status() {
@ -221,12 +167,6 @@ const Notification = {
scaleMfm() { scaleMfm() {
return this.mergedConfig.scaleMfm return this.mergedConfig.scaleMfm
}, },
shouldConfirmApprove() {
return this.mergedConfig.modalOnApproveFollow
},
shouldConfirmDeny() {
return this.mergedConfig.modalOnDenyFollow
},
...mapState(useUsersStore, ['currentUser']), ...mapState(useUsersStore, ['currentUser']),
}, },
} }

View file

@ -226,7 +226,7 @@
<button <button
class="button-unstyled" class="button-unstyled"
:title="$t('tool_tip.accept_follow_request')" :title="$t('tool_tip.accept_follow_request')"
@click="approveUser()" @click="() => approve(user.id)"
> >
<FAIcon <FAIcon
icon="check" icon="check"
@ -236,7 +236,7 @@
<button <button
class="button-unstyled" class="button-unstyled"
:title="$t('tool_tip.reject_follow_request')" :title="$t('tool_tip.reject_follow_request')"
@click="denyUser()" @click="() => deny(user.id)"
> >
<FAIcon <FAIcon
icon="times" icon="times"
@ -268,28 +268,6 @@
</template> </template>
</div> </div>
</div> </div>
<teleport to="#modal">
<ConfirmModal
v-if="showingApproveConfirmDialog"
:title="$t('user_card.approve_confirm_title')"
:confirm-text="$t('user_card.approve_confirm_accept_button')"
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
@accepted="doApprove"
@cancelled="hideApproveConfirmDialog"
>
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
</ConfirmModal>
<ConfirmModal
v-if="showingDenyConfirmDialog"
:title="$t('user_card.deny_confirm_title')"
:confirm-text="$t('user_card.deny_confirm_accept_button')"
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
@accepted="doDeny"
@cancelled="hideDenyConfirmDialog"
>
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
</ConfirmModal>
</teleport>
</article> </article>
</template> </template>

View file

@ -14,6 +14,7 @@ import NotificationFilters from './notification_filters.vue'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js' import { useChatsStore } from 'src/stores/chats.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js' import { useNotificationsStore } from 'src/stores/notifications.js'
@ -107,17 +108,17 @@ const Notifications = {
}, },
extraNotificationsCount() { extraNotificationsCount() {
return countExtraNotifications( return countExtraNotifications(
this.$store,
useMergedConfigStore().mergedConfig, useMergedConfigStore().mergedConfig,
useChatsStore().unreadChatsCount, useChatsStore().unreadChatsCount,
useAnnouncementsStore().unreadAnnouncementCount, useAnnouncementsStore().unreadAnnouncementsCount,
useFollowRequestsStore().followRequestsCount,
) )
}, },
unseenCountTitle() { unseenCountTitle() {
return ( return (
this.unseenNotifications.length + this.unseenNotifications.length +
this.unreadChatsCount + this.unreadChatsCount +
this.unreadAnnouncementCount this.unreadAnnouncementsCount
) )
}, },
loading() { loading() {
@ -156,7 +157,7 @@ const Notifications = {
showExtraNotifications() { showExtraNotifications() {
return !this.noExtra return !this.noExtra
}, },
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), ...mapState(useAnnouncementsStore, ['unreadAnnouncementsCount']),
...mapState(useChatsStore, ['unreadChatsCount']), ...mapState(useChatsStore, ['unreadChatsCount']),
...mapState(useInterfaceStore, ['layoutType']), ...mapState(useInterfaceStore, ['layoutType']),
}, },

View file

@ -4,7 +4,7 @@ import {
unescape as ldUnescape, unescape as ldUnescape,
reject, reject,
uniqBy, uniqBy,
} from 'lodash' } from 'lodash-es'
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
@ -24,6 +24,7 @@ import { findOffset } from '../../services/offset_finder/offset_finder.service.j
import genRandomSeed from '../../services/random_seed/random_seed.service.js' import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import statusPoster from '../../services/status_poster/status_poster.service.js' import statusPoster from '../../services/status_poster/status_poster.service.js'
import { useDraftsStore } from 'src/stores/drafts.js'
import { useEmojiStore } from 'src/stores/emoji.js' import { useEmojiStore } from 'src/stores/emoji.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
@ -400,8 +401,6 @@ const PostStatusForm = {
contentType: this.newStatus.contentType, contentType: this.newStatus.contentType,
poll, poll,
idempotencyKey: this.idempotencyKey, idempotencyKey: this.idempotencyKey,
store: this.$store,
} }
}, },
@ -412,7 +411,6 @@ const PostStatusForm = {
...useEmojiStore().standardEmojiList, ...useEmojiStore().standardEmojiList,
...useEmojiStore().customEmoji, ...useEmojiStore().customEmoji,
], ],
store: this.$store,
}) })
}, },
emojiSuggestor() { emojiSuggestor() {
@ -574,7 +572,7 @@ const PostStatusForm = {
...mapState(useUsersStore, ['currentUser']), ...mapState(useUsersStore, ['currentUser']),
...mapState(useMergedConfigStore, ['mergedConfig']), ...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useInterfaceStore, { ...mapState(useInterfaceStore, {
mobileLayout: (store) => store.mobileLayout, mobileLayout: (state) => state.mobileLayout,
}), }),
}, },
watch: { watch: {
@ -751,7 +749,6 @@ const PostStatusForm = {
const description = this.newStatus.mediaDescriptions[id] const description = this.newStatus.mediaDescriptions[id]
if (!description || description.trim() === '') return if (!description || description.trim() === '') return
return statusPoster.setMediaDescription({ return statusPoster.setMediaDescription({
store: this.$store,
id, id,
description, description,
}) })
@ -985,13 +982,13 @@ const PostStatusForm = {
saveDraft() { saveDraft() {
if (!this.disableDraft && !this.saveInhibited) { if (!this.disableDraft && !this.saveInhibited) {
if (this.safeToSaveDraft) { if (this.safeToSaveDraft) {
return this.$store return useDraftsStore()
.dispatch('addOrSaveDraft', { .addOrSaveDraft({
draft: { type: this.statusType,
type: this.statusType, refId: this.refId,
refId: this.refId, ...this.newStatus,
...this.newStatus, // Draft ID overwrites status ID (which is undefined for fresh statuses)
}, id: this.draftId,
}) })
.then((id) => { .then((id) => {
if (this.newStatus.id !== id) { if (this.newStatus.id !== id) {
@ -1024,14 +1021,14 @@ const PostStatusForm = {
} }
}, },
abandonDraft() { abandonDraft() {
return this.$store.dispatch('abandonDraft', { id: this.draftId }) return useDraftsStore().abandonDraft(this.draftId)
}, },
getDraft() { getDraft() {
const maybeDraft = this.$store.state.drafts.drafts[this.draftId] const maybeDraft = useDraftsStore().drafts.get(this.draftId)
if (this.draftId && maybeDraft) { if (this.draftId && maybeDraft) {
return maybeDraft return maybeDraft
} else { } else {
const existingDrafts = this.$store.getters.draftsByTypeAndRefId( const existingDrafts = useDraftsStore().draftsByTypeAndRefId(
this.statusType, this.statusType,
this.refId, this.refId,
) )

View file

@ -1,4 +1,4 @@
import { get } from 'lodash' import { get } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import Modal from 'src/components/modal/modal.vue' import Modal from 'src/components/modal/modal.vue'

View file

@ -1,4 +1,4 @@
import { debounce } from 'lodash' import { debounce } from 'lodash-es'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import Quote from './quote.vue' import Quote from './quote.vue'

View file

@ -1,4 +1,4 @@
import { flattenDeep, unescape as ldUnescape } from 'lodash' import { flattenDeep, unescape as ldUnescape } from 'lodash-es'
import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue' import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js' import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js'

View file

@ -1,4 +1,4 @@
import { map, uniqBy } from 'lodash' import { map, uniqBy } from 'lodash-es'
import Conversation from 'src/components/conversation/conversation.vue' import Conversation from 'src/components/conversation/conversation.vue'
import FollowCard from 'src/components/follow_card/follow_card.vue' import FollowCard from 'src/components/follow_card/follow_card.vue'

View file

@ -2,7 +2,7 @@ import Checkbox from 'components/checkbox/checkbox.vue'
import Popover from 'components/popover/popover.vue' import Popover from 'components/popover/popover.vue'
import Select from 'components/select/select.vue' import Select from 'components/select/select.vue'
import StillImage from 'components/still-image/still-image.vue' import StillImage from 'components/still-image/still-image.vue'
import { clone } from 'lodash' import { clone } from 'lodash-es'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'

View file

@ -1,4 +1,4 @@
import { get } from 'lodash' import { get } from 'lodash-es'
import AttachmentSetting from '../helpers/attachment_setting.vue' import AttachmentSetting from '../helpers/attachment_setting.vue'
import BooleanSetting from '../helpers/boolean_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue'

View file

@ -1,4 +1,4 @@
import { get } from 'lodash' import { get } from 'lodash-es'
import AttachmentSetting from '../helpers/attachment_setting.vue' import AttachmentSetting from '../helpers/attachment_setting.vue'
import BooleanSetting from '../helpers/boolean_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue'

View file

@ -1,4 +1,4 @@
import { get } from 'lodash' import { get } from 'lodash-es'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import AttachmentSetting from '../helpers/attachment_setting.vue' import AttachmentSetting from '../helpers/attachment_setting.vue'

View file

@ -1,4 +1,4 @@
import { clone } from 'lodash' import { clone } from 'lodash-es'
import Attachment from 'src/components/attachment/attachment.vue' import Attachment from 'src/components/attachment/attachment.vue'
import MediaUpload from 'src/components/media_upload/media_upload.vue' import MediaUpload from 'src/components/media_upload/media_upload.vue'

View file

@ -1,4 +1,4 @@
import { cloneDeep, get, isEqual, set } from 'lodash' import { cloneDeep, get, isEqual, set } from 'lodash-es'
import DraftButtons from './draft_buttons.vue' import DraftButtons from './draft_buttons.vue'
import LocalSettingIndicator from './local_setting_indicator.vue' import LocalSettingIndicator from './local_setting_indicator.vue'
@ -8,6 +8,7 @@ import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useLocalConfigStore } from 'src/stores/local_config.js' import { useLocalConfigStore } from 'src/stores/local_config.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useProfileConfigStore } from 'src/stores/profile_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
export default { export default {
@ -236,7 +237,7 @@ export default {
configSource() { configSource() {
switch (this.realSource) { switch (this.realSource) {
case 'profile': case 'profile':
return this.$store.state.profileConfig return useProfileConfigStore().config
case 'admin': case 'admin':
return useAdminSettingsStore().config return useAdminSettingsStore().config
default: default:
@ -253,7 +254,7 @@ export default {
switch (this.realSource) { switch (this.realSource) {
case 'profile': case 'profile':
return (k, v) => return (k, v) =>
this.$store.dispatch('setProfileOption', { name: k, value: v }) useProfileConfigStore().setProfileOption({ name: k, value: v })
case 'admin': case 'admin':
return (k, v) => return (k, v) =>
useAdminSettingsStore().pushAdminSetting({ path: k, value: v }) useAdminSettingsStore().pushAdminSetting({ path: k, value: v })
@ -412,8 +413,8 @@ export default {
hardReset() { hardReset() {
switch (this.realSource) { switch (this.realSource) {
case 'admin': case 'admin':
return this.$store return useAdminSettingsStore()
.dispatch('resetAdminSetting', { path: this.path }) .resetAdminSetting({ path: this.path })
.then(() => { .then(() => {
this.draft = this.state this.draft = this.state
}) })

View file

@ -1,4 +1,4 @@
import { cloneDeep, isEqual } from 'lodash' import { cloneDeep, isEqual } from 'lodash-es'
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'

View file

@ -16,6 +16,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useProfileConfigStore } from 'src/stores/profile_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js' import { useUsersStore } from 'src/stores/users.js'
@ -109,6 +110,9 @@ const ComposingTab = {
FontControl, FontControl,
}, },
computed: { computed: {
defaultScope() {
return useProfileConfigStore().config.defaultScope
},
postFormats() { postFormats() {
return useInstanceCapabilitiesStore().postFormats return useInstanceCapabilitiesStore().postFormats
}, },
@ -135,7 +139,7 @@ const ComposingTab = {
}, },
methods: { methods: {
changeDefaultScope(value) { changeDefaultScope(value) {
this.$store.dispatch('setProfileOption', { name: 'defaultScope', value }) useProfileConfigStore().setProfileOption({ name: 'defaultScope', value })
}, },
clearCache(key) { clearCache(key) {
clearCache(key) clearCache(key)

View file

@ -11,10 +11,10 @@
<ScopeSelector <ScopeSelector
class="scope-selector setting-control" class="scope-selector setting-control"
:show-all="true" :show-all="true"
:user-default="$store.state.profileConfig.defaultScope" :user-default="defaultScope"
:initial-scope="$store.state.profileConfig.defaultScope" :initial-scope="defaultScope"
:on-scope-change="changeDefaultScope"
:unstyled="false" :unstyled="false"
@change="changeDefaultScope"
/> />
</label> </label>
</li> </li>

View file

@ -1,4 +1,4 @@
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash-es'
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'

View file

@ -1,4 +1,4 @@
import { get, map, reject } from 'lodash' import { get, map, reject } from 'lodash-es'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import BlockCard from 'src/components/block_card/block_card.vue' import BlockCard from 'src/components/block_card/block_card.vue'

View file

@ -1,4 +1,4 @@
import { get, set, throttle, unset } from 'lodash' import { get, set, throttle, unset } from 'lodash-es'
import { import {
computed, computed,
getCurrentInstance, getCurrentInstance,

View file

@ -1,4 +1,4 @@
import { flattenDeep, throttle } from 'lodash' import { flattenDeep, throttle } from 'lodash-es'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import ColorInput from 'src/components/color_input/color_input.vue' import ColorInput from 'src/components/color_input/color_input.vue'

View file

@ -1,5 +1,4 @@
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import { mapGetters } from 'vuex'
import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js' import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
import UserCard from 'src/components/user_card/user_card.vue' import UserCard from 'src/components/user_card/user_card.vue'
@ -8,6 +7,8 @@ import { unseenNotifications } from '../../services/notification_utils/notificat
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useChatsStore } from 'src/stores/chats.js' import { useChatsStore } from 'src/stores/chats.js'
import { useDraftsStore } from 'src/stores/drafts.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
@ -61,10 +62,6 @@ const SideDrawer = {
GestureService.DIRECTION_LEFT, GestureService.DIRECTION_LEFT,
this.toggleDrawer, this.toggleDrawer,
) )
if (this.currentUser?.locked) {
this.$store.dispatch('startFetchingFollowRequests')
}
}, },
components: { components: {
UserCard, UserCard,
@ -85,9 +82,6 @@ const SideDrawer = {
unseenNotificationsCount() { unseenNotificationsCount() {
return this.unseenNotifications.length return this.unseenNotifications.length
}, },
followRequestCount() {
return this.$store.state.api.followRequests.length
},
timelinesRoute() { timelinesRoute() {
let name let name
if (useInterfaceStore().lastTimeline) { if (useInterfaceStore().lastTimeline) {
@ -100,9 +94,10 @@ const SideDrawer = {
return { name } return { name }
} }
}, },
...mapState(useFollowRequestsStore, ['followRequestsCount']),
...mapState(useAnnouncementsStore, [ ...mapState(useAnnouncementsStore, [
'supportsAnnouncements', 'supportsAnnouncements',
'unreadAnnouncementCount', 'unreadAnnouncementsCount',
]), ]),
...mapState(useInstanceCapabilitiesStore, [ ...mapState(useInstanceCapabilitiesStore, [
'pleromaChatMessagesAvailable', 'pleromaChatMessagesAvailable',
@ -115,7 +110,7 @@ const SideDrawer = {
hideSitename: (store) => store.instanceIdentity.hideSitename, hideSitename: (store) => store.instanceIdentity.hideSitename,
}), }),
...mapState(useChatsStore, ['unreadChatsCount']), ...mapState(useChatsStore, ['unreadChatsCount']),
...mapGetters(['draftCount']), ...mapState(useDraftsStore, ['draftsCount']),
}, },
methods: { methods: {
toggleDrawer() { toggleDrawer() {

View file

@ -141,10 +141,10 @@
icon="user-plus" icon="user-plus"
/> {{ $t("nav.friend_requests") }} /> {{ $t("nav.friend_requests") }}
<span <span
v-if="followRequestCount > 0" v-if="followRequestsCount > 0"
class="badge -notification" class="badge -notification"
> >
{{ followRequestCount }} {{ followRequestsCount }}
</span> </span>
</router-link> </router-link>
</li> </li>
@ -248,10 +248,10 @@
icon="bullhorn" icon="bullhorn"
/> {{ $t("nav.announcements") }} /> {{ $t("nav.announcements") }}
<span <span
v-if="unreadAnnouncementCount" v-if="unreadAnnouncementsCount"
class="badge -notification" class="badge -notification"
> >
{{ unreadAnnouncementCount }} {{ unreadAnnouncementsCount }}
</span> </span>
</router-link> </router-link>
</li> </li>
@ -269,10 +269,10 @@
icon="file-pen" icon="file-pen"
/> {{ $t('nav.drafts') }} /> {{ $t('nav.drafts') }}
<span <span
v-if="draftCount" v-if="draftsCount"
class="badge -neutral" class="badge -neutral"
> >
{{ draftCount }} {{ draftsCount }}
</span> </span>
</router-link> </router-link>
</li> </li>

View file

@ -1,4 +1,4 @@
import { groupBy, map } from 'lodash' import { groupBy, map } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue' import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'

View file

@ -97,9 +97,6 @@ const StatusActionButtons = {
replying: this.replying, replying: this.replying,
emojiPickerShown: this.emojiPickerShown, emojiPickerShown: this.emojiPickerShown,
emit: this.$emit, emit: this.$emit,
dispatch: this.$store.dispatch,
state: this.$store.state,
getters: this.$store.getters,
router: this.$router, router: this.$router,
currentUser: this.currentUser, currentUser: this.currentUser,
loggedIn: !!this.currentUser, loggedIn: !!this.currentUser,

View file

@ -29,14 +29,13 @@ const StickerPicker = {
} }
}, },
pick(sticker, name) { pick(sticker, name) {
const store = this.$store
// TODO remove this workaround by finding a way to bypass reuploads // TODO remove this workaround by finding a way to bypass reuploads
fetch(sticker).then((res) => { fetch(sticker).then((res) => {
res.blob().then((blob) => { res.blob().then((blob) => {
const file = new File([blob], name, { mimetype: 'image/png' }) const file = new File([blob], name, { mimetype: 'image/png' })
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
statusPosterService.uploadMedia({ store, formData }).then( statusPosterService.uploadMedia({ formData }).then(
(fileData) => { (fileData) => {
this.$emit('uploaded', fileData) this.$emit('uploaded', fileData)
this.clear() this.clear()

View file

@ -1,4 +1,4 @@
import { debounce, throttle } from 'lodash' import { debounce, throttle } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import Conversation from 'src/components/conversation/conversation.vue' import Conversation from 'src/components/conversation/conversation.vue'

View file

@ -3,7 +3,7 @@ import {
escape as ldEscape, escape as ldEscape,
unescape as ldUnescape, unescape as ldUnescape,
merge, merge,
} from 'lodash' } from 'lodash-es'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
@ -418,7 +418,6 @@ export default {
...useEmojiStore().standardEmojiList, ...useEmojiStore().standardEmojiList,
...useEmojiStore().customEmoji, ...useEmojiStore().customEmoji,
], ],
store: this.$store,
}) })
}, },
emojiSuggestor() { emojiSuggestor() {

View file

@ -1,4 +1,4 @@
import { get } from 'lodash' import { get } from 'lodash-es'
import FollowCard from 'src/components/follow_card/follow_card.vue' import FollowCard from 'src/components/follow_card/follow_card.vue'
import List from 'src/components/list/list.vue' import List from 'src/components/list/list.vue'

View file

@ -1,4 +1,4 @@
import { shuffle } from 'lodash' import { shuffle } from 'lodash-es'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'

View file

@ -1742,6 +1742,7 @@
"approve_confirm_accept_button": "Approve", "approve_confirm_accept_button": "Approve",
"approve_confirm_cancel_button": "Do not approve", "approve_confirm_cancel_button": "Do not approve",
"approve_confirm": "Do you want to approve {user}'s follow request?", "approve_confirm": "Do you want to approve {user}'s follow request?",
"approve_error": "Failure approving follow request: {error}",
"block": "Block", "block": "Block",
"blocked": "Blocked!", "blocked": "Blocked!",
"block_confirm_title": "Block confirmation", "block_confirm_title": "Block confirmation",
@ -1754,6 +1755,7 @@
"deny_confirm_accept_button": "Deny", "deny_confirm_accept_button": "Deny",
"deny_confirm_cancel_button": "Do not deny", "deny_confirm_cancel_button": "Do not deny",
"deny_confirm": "Do you want to deny {user}'s follow request?", "deny_confirm": "Do you want to deny {user}'s follow request?",
"deny_error": "Failure denying follow request: {error}",
"edit_profile": "Edit profile", "edit_profile": "Edit profile",
"favorites": "Favorites", "favorites": "Favorites",
"follow": "Follow", "follow": "Follow",

View file

@ -7,7 +7,7 @@
// sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json // sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry. // There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
import { isEqual } from 'lodash' import { isEqual } from 'lodash-es'
import enMessages from './en.json' import enMessages from './en.json'
import { langCodeToJsonName, languages } from './languages.js' import { langCodeToJsonName, languages } from './languages.js'

View file

@ -1,11 +1,7 @@
import { cloneDeep, each, get, merge, set } from 'lodash' import { cloneDeep, get, set } from 'lodash-es'
import { storage } from './storage.js' import { storage } from './storage.js'
import { useInterfaceStore } from 'src/stores/interface'
let loaded = false
const defaultReducer = (state, paths) => const defaultReducer = (state, paths) =>
paths.length === 0 paths.length === 0
? state ? state
@ -14,86 +10,10 @@ const defaultReducer = (state, paths) =>
return substate return substate
}, {}) }, {})
const saveImmedeatelyActions = [
'markNotificationsAsSeen',
'setHighlight',
'setOption',
'setClientData',
'setToken',
'clearToken',
]
const defaultStorage = (() => { const defaultStorage = (() => {
return storage return storage
})() })()
export default function createPersistedState({
key = 'vuex-lz',
paths = [],
getState = (key, storage) => {
const value = storage.getItem(key)
return value
},
setState = (key, state, storage) => {
if (!loaded) {
console.info('waiting for old state to be loaded...')
return Promise.resolve()
} else {
return storage.setItem(key, state)
}
},
reducer = defaultReducer,
storage = defaultStorage,
subscriber = (store) => (handler) => store.subscribe(handler),
} = {}) {
return getState(key, storage).then((savedState) => {
return (store) => {
try {
if (savedState !== null && typeof savedState === 'object') {
// build user cache
const usersState = savedState.users || {}
usersState.usersObject = {}
const users = usersState.users || []
each(users, (user) => {
usersState.usersObject[user.id] = user
})
savedState.users = usersState
store.replaceState(merge({}, store.state, savedState))
}
loaded = true
} catch (e) {
console.error("Couldn't load state")
console.error(e)
loaded = true
}
subscriber(store)((mutation, state) => {
try {
if (saveImmedeatelyActions.includes(mutation.type)) {
setState(key, reducer(cloneDeep(state), paths), storage).then(
(success) => {
if (success !== undefined) {
if (mutation.type === 'setOption') {
useInterfaceStore().settingsSaved({ success })
}
}
},
(error) => {
if (mutation.type === 'setOption') {
useInterfaceStore().settingsSaved({ error })
}
},
)
}
} catch (e) {
console.error("Couldn't persist state:")
console.error(e)
}
})
}
})
}
/** /**
* This persists state for pinia, which falls back to read from the vuex state * This persists state for pinia, which falls back to read from the vuex state
* if pinia persisted state does not exist. * if pinia persisted state does not exist.

View file

@ -1,7 +1,6 @@
/* global process */ /* global process */
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import { createStore } from 'vuex'
import 'custom-event-polyfill' import 'custom-event-polyfill'
import './lib/event_target_polyfill.js' import './lib/event_target_polyfill.js'
@ -17,11 +16,8 @@ import { createI18n } from 'vue-i18n'
import afterStoreSetup from './boot/after_store.js' import afterStoreSetup from './boot/after_store.js'
import messages from './i18n/messages.js' import messages from './i18n/messages.js'
import createPersistedState, { import { piniaPersistPlugin } from './lib/persisted_state.js'
piniaPersistPlugin,
} from './lib/persisted_state.js'
import { piniaPushNotificationsPlugin } from './lib/push_notifications_plugin.js' import { piniaPushNotificationsPlugin } from './lib/push_notifications_plugin.js'
import vuexModules from './modules/index.js'
import { piniaLanguagePlugin } from 'src/lib/language.js' import { piniaLanguagePlugin } from 'src/lib/language.js'
import { piniaStylePlugin } from 'src/lib/style.js' import { piniaStylePlugin } from 'src/lib/style.js'
@ -37,10 +33,6 @@ const i18n = createI18n({
messages.setLanguage(i18n.global, currentLocale) messages.setLanguage(i18n.global, currentLocale)
const persistedStateOptions = {
paths: ['oauth', 'config'],
}
;(async () => { ;(async () => {
const isFox = Math.floor(Math.random() * 2) > 0 ? '_fox' : '' const isFox = Math.floor(Math.random() * 2) > 0 ? '_fox' : ''
@ -69,20 +61,12 @@ const persistedStateOptions = {
try { try {
let storageError let storageError
const plugins = []
const pinia = createPinia() const pinia = createPinia()
pinia.use(piniaPersistPlugin()) pinia.use(piniaPersistPlugin())
pinia.use(piniaLanguagePlugin) pinia.use(piniaLanguagePlugin)
pinia.use(piniaStylePlugin) pinia.use(piniaStylePlugin)
pinia.use(piniaPushNotificationsPlugin) pinia.use(piniaPushNotificationsPlugin)
try {
const persistedState = await createPersistedState(persistedStateOptions)
plugins.push(persistedState)
} catch (e) {
console.error('Storage error', e)
storageError = e
}
document.querySelector('#splash').classList.remove('initial-hidden') document.querySelector('#splash').classList.remove('initial-hidden')
document.querySelector('#mascot').src = document.querySelector('#mascot').src =
`/static/pleromatan_apology${isFox}_small.webp` `/static/pleromatan_apology${isFox}_small.webp`
@ -93,18 +77,8 @@ const persistedStateOptions = {
'update.art_by', 'update.art_by',
{ linkToArtist: 'pipivovott' }, { linkToArtist: 'pipivovott' },
) )
const store = createStore({ // Temporarily passing pinia stores along with storageError result until migration is fully complete.
modules: vuexModules, return await afterStoreSetup({ pinia, storageError, i18n })
plugins,
options: {
devtools: process.env.NODE_ENV !== 'production',
},
strict: false, // Socket modifies itself, let's ignore this for now.
// strict: process.env.NODE_ENV !== 'production'
})
window.vuex = store
// Temporarily passing pinia and vuex stores along with storageError result until migration is fully complete.
return await afterStoreSetup({ pinia, store, storageError, i18n })
} catch (e) { } catch (e) {
splashError(i18n, e) splashError(i18n, e)
} }

View file

@ -1,79 +0,0 @@
import { Socket } from 'phoenix'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useShoutStore } from 'src/stores/shout.js'
import followRequestFetcher from 'src/services/follow_request_fetcher/follow_request_fetcher.service'
const api = {
state: {
fetchers: {},
socket: null,
followRequests: [],
},
getters: {
followRequestCount: (state) => state.followRequests.length,
},
mutations: {
addFetcher(state, { fetcherName, fetcher }) {
state.fetchers[fetcherName] = fetcher
},
removeFetcher(state, { fetcherName }) {
state.fetchers[fetcherName].stop()
delete state.fetchers[fetcherName]
},
setWsToken(state, token) {
state.wsToken = token
},
setSocket(state, socket) {
state.socket = socket
},
setFollowRequests(state, value) {
state.followRequests = value
},
},
actions: {
// Follow requests
startFetchingFollowRequests(store) {
if (store.state.fetchers.followRequests) return
const fetcher = followRequestFetcher.startFetching({
store,
credentials: useOAuthStore().token,
})
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
},
stopFetchingFollowRequests(store) {
const fetcher = store.state.fetchers.followRequests
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
},
// Pleroma websocket
setWsToken(store, token) {
store.commit('setWsToken', token)
},
initializeSocket({ commit, state, rootState }) {
// Set up websocket connection
const token = state.wsToken
if (
useInstanceCapabilitiesStore().shoutAvailable &&
token !== undefined &&
state.socket === null
) {
const socket = new Socket('/socket', { params: { token } })
socket.connect()
commit('setSocket', socket)
useShoutStore().initializeShout(socket)
}
},
disconnectFromSocket({ commit, state }) {
state.socket?.disconnect()
commit('setSocket', null)
},
},
}
export default api

View file

@ -1,4 +1,4 @@
import { get } from 'lodash' import { get } from 'lodash-es'
const browserLocale = (navigator.language || 'en').split('-')[0] const browserLocale = (navigator.language || 'en').split('-')[0]

View file

@ -1,99 +0,0 @@
import { storage } from 'src/lib/storage.js'
export const defaultState = {
drafts: {},
}
export const mutations = {
addOrSaveDraft(state, { draft }) {
state.drafts[draft.id] = draft
},
abandonDraft(state, { id }) {
delete state.drafts[id]
},
loadDrafts(state, data) {
state.drafts = data
},
}
const storageKey = 'pleroma-fe-drafts'
/*
* Note: we do not use the persist state plugin because
* it is not impossible for a user to have two windows at
* the same time. The persist state plugin is just overriding
* everything with the current state. This isn't good because
* if a draft is created in one window and another draft is
* created in another, the draft in the first window will just
* be overriden.
* Here, we can't guarantee 100% atomicity unless one uses
* different keys, which will just pollute the whole storage.
* It is indeed best to have backend support for this.
*/
const getStorageData = async () =>
(await storage.getItem(storageKey)) ||
{
/* no-op */
}
const saveDraftToStorage = async (draft) => {
const currentData = await getStorageData()
currentData[draft.id] = JSON.parse(JSON.stringify(draft))
await storage.setItem(storageKey, currentData)
}
const deleteDraftFromStorage = async (ids) => {
const currentData = await getStorageData()
ids.forEach((id) => {
delete currentData[id]
})
await storage.setItem(storageKey, currentData)
}
export const actions = {
async addOrSaveDraft(store, { draft }) {
const id = draft.id || new Date().getTime().toString()
const draftWithId = { ...draft, id }
store.commit('addOrSaveDraft', { draft: draftWithId })
await saveDraftToStorage(draftWithId)
return id
},
async abandonDraft(store, { id }) {
store.commit('abandonDraft', { id })
await deleteDraftFromStorage([id])
},
async abandonAllDrafts(store) {
const ids = Object.keys(store.state.drafts)
ids.forEach((id) => store.commit('abandonDraft', { id }))
await deleteDraftFromStorage(ids)
},
async loadDrafts(store) {
const currentData = await getStorageData()
store.commit('loadDrafts', currentData)
},
}
export const getters = {
draftsByTypeAndRefId(state) {
return (type, refId) => {
return Object.values(state.drafts).filter(
(draft) => draft.type === type && draft.refId === refId,
)
}
},
draftsArray(state) {
return Object.values(state.drafts)
},
draftCount(state) {
return Object.values(state.drafts).length
},
}
const drafts = {
state: defaultState,
mutations,
getters,
actions,
}
export default drafts

View file

@ -1,9 +0,0 @@
import api from './api.js'
import drafts from './drafts.js'
import profileConfig from './profileConfig.js'
export default {
api,
profileConfig,
drafts,
}

View file

@ -1,4 +1,4 @@
import { kebabCase } from 'lodash' import { kebabCase } from 'lodash-es'
const propsToNative = (props) => const propsToNative = (props) =>
Object.keys(props).reduce((acc, cur) => { Object.keys(props).reduce((acc, cur) => {

View file

@ -1,4 +1,4 @@
import { find, reduce } from 'lodash' import { find, reduce } from 'lodash-es'
export const replaceWord = (str, toReplace, replacement) => { export const replaceWord = (str, toReplace, replacement) => {
return str.slice(0, toReplace.start) + replacement + str.slice(toReplace.end) return str.slice(0, toReplace.start) + replacement + str.slice(toReplace.end)

View file

@ -1,4 +1,4 @@
import { isFunction } from 'lodash' import { isFunction } from 'lodash-es'
const getComponentOptions = (Component) => const getComponentOptions = (Component) =>
isFunction(Component) ? Component.options : Component isFunction(Component) ? Component.options : Component

View file

@ -1,6 +1,6 @@
import { parseLinkHeader } from '@web3-storage/parse-link-header' import { parseLinkHeader } from '@web3-storage/parse-link-header'
import escapeHtml from 'escape-html' import escapeHtml from 'escape-html'
import { unescape as lodashUnescape } from 'lodash' import { unescape as lodashUnescape } from 'lodash-es'
import punycode from 'punycode.js' import punycode from 'punycode.js'
import { fileType } from '../file_type/file_type.service.js' import { fileType } from '../file_type/file_type.service.js'

View file

@ -1,4 +1,4 @@
import { capitalize } from 'lodash' import { capitalize } from 'lodash-es'
function humanizeErrors(errors) { function humanizeErrors(errors) {
return Object.entries(errors).reduce((errs, [k, val]) => { return Object.entries(errors).reduce((errs, [k, val]) => {

View file

@ -1,33 +0,0 @@
import { useUsersStore } from 'src/stores/users.js'
import { fetchFollowRequests } from 'src/api/user.js'
import { promiseInterval } from 'src/services/promise_interval/promise_interval.js'
const fetchAndUpdate = ({ store, credentials }) => {
return fetchFollowRequests({ credentials })
.then(
(result) => {
const { data: requests } = result
store.commit('setFollowRequests', requests)
useUsersStore().addNewUsers(result)
},
(rej) => {
console.error(rej)
},
)
.catch((e) => {
console.error(e)
})
}
const startFetching = ({ credentials, store }) => {
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
boundFetchAndUpdate()
return promiseInterval(boundFetchAndUpdate, 10000)
}
const followRequestFetcher = {
startFetching,
}
export default followRequestFetcher

View file

@ -1,4 +1,4 @@
import { unescape as ldUnescape } from 'lodash' import { unescape as ldUnescape } from 'lodash-es'
import { getTagName } from './utility.service.js' import { getTagName } from './utility.service.js'

View file

@ -1,5 +1,5 @@
import ISO6391 from 'iso-639-1' import ISO6391 from 'iso-639-1'
import { map } from 'lodash' import { map } from 'lodash-es'
import languagesObject from '../../i18n/messages' import languagesObject from '../../i18n/messages'

View file

@ -98,13 +98,11 @@ export const unseenNotifications = (
} }
export const countExtraNotifications = ( export const countExtraNotifications = (
store,
mergedConfig, mergedConfig,
unreadChatsCount, unreadChatsCount,
unreadAnnouncementCount, unreadAnnouncementsCount,
followRequestsCount,
) => { ) => {
const rootGetters = store.rootGetters || store.getters
if (!mergedConfig.showExtraNotifications) { if (!mergedConfig.showExtraNotifications) {
return 0 return 0
} }
@ -112,10 +110,10 @@ export const countExtraNotifications = (
return [ return [
mergedConfig.showChatsInExtraNotifications ? unreadChatsCount : 0, mergedConfig.showChatsInExtraNotifications ? unreadChatsCount : 0,
mergedConfig.showAnnouncementsInExtraNotifications mergedConfig.showAnnouncementsInExtraNotifications
? unreadAnnouncementCount ? unreadAnnouncementsCount
: 0, : 0,
mergedConfig.showFollowRequestsInExtraNotifications mergedConfig.showFollowRequestsInExtraNotifications
? rootGetters.followRequestCount ? followRequestsCount
: 0, : 0,
].reduce((a, c) => a + c, 0) ].reduce((a, c) => a + c, 0)
} }

View file

@ -1,4 +1,4 @@
import { map } from 'lodash' import { map } from 'lodash-es'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js' import { useStatusesStore } from 'src/stores/statuses.js'

View file

@ -1,6 +1,6 @@
import sum from 'hash-sum' import sum from 'hash-sum'
import localforage from 'localforage' import localforage from 'localforage'
import { chunk, throttle } from 'lodash' import { chunk, throttle } from 'lodash-es'
import { getCssRules } from '../theme_data/css_utils.js' import { getCssRules } from '../theme_data/css_utils.js'
import { getEngineChecksum, init } from '../theme_data/theme_data_3.service.js' import { getEngineChecksum, init } from '../theme_data/theme_data_3.service.js'

View file

@ -1,4 +1,4 @@
import { flattenDeep } from 'lodash' import { flattenDeep } from 'lodash-es'
export const deserializeShadow = (string) => { export const deserializeShadow = (string) => {
const modes = [ const modes = [

View file

@ -1,4 +1,4 @@
import { sortBy } from 'lodash' import { sortBy } from 'lodash-es'
// "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }} // "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }}
// into an array [item2, item3] for iterating // into an array [item2, item3] for iterating

View file

@ -1,6 +1,6 @@
import { brightness, convert } from 'chromatism' import { brightness, convert } from 'chromatism'
import sum from 'hash-sum' import sum from 'hash-sum'
import { flattenDeep, sortBy } from 'lodash' import { flattenDeep, sortBy } from 'lodash-es'
import { import {
alphaBlend, alphaBlend,

View file

@ -1,4 +1,4 @@
import { includes } from 'lodash' import { includes } from 'lodash-es'
const generateProfileLink = (id, screenName, restrictedNicknames) => { const generateProfileLink = (id, screenName, restrictedNicknames) => {
const complicated = const complicated =

View file

@ -1,4 +1,4 @@
import { cloneDeep, differenceWith, get, isEqual, set } from 'lodash' import { cloneDeep, differenceWith, get, isEqual, set } from 'lodash-es'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'

View file

@ -16,7 +16,7 @@ export const useAnnouncementsStore = defineStore('announcements', {
userActions: {}, userActions: {},
}), }),
getters: { getters: {
unreadAnnouncementCount() { unreadAnnouncementsCount() {
if (!useUsersStore().currentUser) { if (!useUsersStore().currentUser) {
return 0 return 0
} }

View file

@ -1,4 +1,4 @@
import { find, remove } from 'lodash' import { find, remove } from 'lodash-es'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'

View file

@ -1,4 +1,4 @@
import { orderBy, sumBy } from 'lodash' import { orderBy, sumBy } from 'lodash-es'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'

76
src/stores/drafts.js Normal file
View file

@ -0,0 +1,76 @@
import { defineStore } from 'pinia'
import { storage } from 'src/lib/storage.js'
const storageKey = 'pleroma-fe-drafts'
/*
* Note: we do not use the persist state plugin because
* it is not impossible for a user to have two windows at
* the same time. The persist state plugin is just overriding
* everything with the current state. This isn't good because
* if a draft is created in one window and another draft is
* created in another, the draft in the first window will just
* be overriden.
* Here, we can't guarantee 100% atomicity unless one uses
* different keys, which will just pollute the whole storage.
* It is indeed best to have backend support for this.
*/
const getStorageData = async () => await storage.getItem(storageKey)
const saveDraftToStorage = async (draft) => {
const currentData = (await getStorageData()) ?? {}
currentData[draft.id] = JSON.parse(JSON.stringify(draft))
await storage.setItem(storageKey, currentData)
}
const deleteDraftFromStorage = async (ids) => {
const currentData = (await getStorageData()) ?? {}
ids.forEach((id) => {
delete currentData[id]
})
await storage.setItem(storageKey, currentData)
}
export const useDraftsStore = defineStore('drafts', {
state: () => ({
drafts: new Map(),
}),
getters: {
draftsByTypeAndRefId(state) {
return (type, refId) => {
return [...state.drafts.values()].filter(
(draft) => draft.type === type && draft.refId === refId,
)
}
},
draftsArray(state) {
return [...state.drafts.values()]
},
draftsCount(state) {
return state.drafts.size
},
},
actions: {
async loadDrafts() {
const currentData = (await getStorageData()) ?? {}
this.drafts = new Map(Object.entries(currentData))
},
async addOrSaveDraft(draft) {
const id = draft.id ?? new Date().getTime().toString()
const draftWithId = { ...draft, id }
this.drafts.set(id, draftWithId)
await saveDraftToStorage(draftWithId)
return id
},
async abandonDraft(id) {
this.drafts.delete(id)
await deleteDraftFromStorage([id])
},
async abandonAllDrafts() {
const ids = [...this.drafts.keys()]
ids.forEach((id) => this.drafts.delete(id))
await deleteDraftFromStorage(ids)
},
},
})

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