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"],
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
"plugins": ["@babel/plugin-transform-runtime", "@vue/babel-plugin-jsx"],
"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.
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.

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { snakeCase } from 'lodash'
import { snakeCase } from 'lodash-es'
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 { 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 MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
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'
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`
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`
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 }) =>
`/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}`
export const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
@ -432,10 +437,9 @@ export const exportFriends = ({ id, credentials }) => {
// #Profile settings
export const updateNotificationSettings = ({ credentials, settings }) => {
return promisedRequest({
url: NOTIFICATION_SETTINGS_URL,
url: NOTIFICATION_SETTINGS_URL(settings),
credentials,
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'

View file

@ -71,7 +71,6 @@ const chatNew = {
this.loading = true
this.userIds = []
this.$store
useSearchStore()
.search({ q: query, resolve: true, type: 'accounts' })
.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 { nextTick } from 'vue'

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { get } from 'lodash'
import { get } from 'lodash-es'
import { mapState } from 'pinia'
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 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) => {
const emojiCurry = suggestEmoji(data.emoji)
const usersCurry = data.store && suggestUsers(data.store)
const usersCurry = suggestUsers()
return (input, nameKeywordLocalizer) => {
const firstChar = input[0]
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 Checkbox from 'src/components/checkbox/checkbox.vue'

View file

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

View file

@ -31,7 +31,7 @@
class="fa-scale-110 icon"
icon="bullhorn"
/>
{{ $t('notifications.unread_announcements', { num: unreadAnnouncementCount }, unreadAnnouncementCount) }}
{{ $t('notifications.unread_announcements', { num: unreadAnnouncementsCount }, unreadAnnouncementsCount) }}
</router-link>
</div>
<div
@ -48,7 +48,7 @@
class="fa-scale-110 icon"
icon="user-plus"
/>
{{ $t('notifications.unread_follow_requests', { num: followRequestCount }, followRequestCount) }}
{{ $t('notifications.unread_follow_requests', { num: followRequestsCount }, followRequestsCount) }}
</router-link>
</div>
<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 { 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'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
const FollowRequestCard = {
props: ['user'],
components: {
BasicUserCard,
ConfirmModal: defineAsyncComponent(
() => import('src/components/confirm_modal/confirm_modal.vue'),
),
},
data() {
return {
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false,
}
},
methods: {
findFollowRequestNotificationId() {
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
},
...mapActions(useFollowRequestsStore, ['approve', 'deny']),
},
}

View file

@ -3,39 +3,17 @@
<div class="follow-request-card-content-container">
<button
class="btn button-default"
@click="approveUser"
@click="() => approve(user.id)"
>
{{ $t('user_card.approve') }}
</button>
<button
class="btn button-default"
@click="denyUser"
@click="() => deny(user.id)"
>
{{ $t('user_card.deny') }}
</button>
</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>
</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 { useFollowRequestsStore } from 'src/stores/follow_requests.js'
const FollowRequests = {
components: {
FollowRequestCard,
},
computed: {
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'

View file

@ -1,4 +1,4 @@
import { isEmpty } from 'lodash'
import { isEmpty } from 'lodash-es'
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'

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { debounce } from 'lodash'
import { debounce } from 'lodash-es'
import { mapState } from 'pinia'
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 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 { useInstanceStore } from 'src/stores/instance.js'

View file

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

View file

@ -76,7 +76,7 @@ export const ROOT_ITEMS = {
icon: 'comments',
label: 'nav.chats',
badgeStyle: 'notification',
badgeGetter: 'unreadChatsCount',
badgeGetter: 'unreadChats',
criteria: ['chats'],
},
friendRequests: {
@ -85,7 +85,7 @@ export const ROOT_ITEMS = {
label: 'nav.friend_requests',
badgeStyle: 'notification',
criteria: ['lockedUser'],
badgeGetter: 'followRequestCount',
badgeGetter: 'followRequests',
},
about: {
route: 'about',
@ -99,7 +99,7 @@ export const ROOT_ITEMS = {
label: 'nav.announcements',
store: 'announcements',
badgeStyle: 'notification',
badgeGetter: 'unreadAnnouncementCount',
badgeGetter: 'unreadAnnouncements',
criteria: ['announcements'],
},
drafts: {
@ -107,7 +107,7 @@ export const ROOT_ITEMS = {
icon: 'file-pen',
label: 'nav.drafts',
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 OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
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 { useUsersStore } from 'src/stores/users.js'
@ -40,11 +43,19 @@ const NavigationEntry = {
routeTo() {
return routeTo(this.item, this.currentUser)
},
getters() {
return this.$store.getters
badges() {
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(useChatsStore, ['unreadChatsCount']),
...mapState(useFollowRequestsStore, ['followRequestsCount']),
...mapState(useSyncConfigStore, {
pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems),

View file

@ -47,17 +47,11 @@
</component>
<slot />
<div
v-if="item.badgeGetter && getters[item.badgeGetter]"
v-if="item.badgeGetter && badges[item.badgeGetter]"
class="badge"
:class="[`-${item.badgeStyle}`]"
>
{{ getters[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] }}
{{ badges[item.badgeGetter] }}
</div>
<button
v-if="showPin && currentUser"

View file

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

View file

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

View file

@ -1,5 +1,4 @@
import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue'
import { mapActions, mapState } from 'pinia'
import Report from 'src/components/report/report.vue'
import StatusContent from 'src/components/status_content/status_content.vue'
@ -13,15 +12,13 @@ import {
highlightStyle,
} from '../../services/user_highlighter/user_highlighter.js'
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
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'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -57,8 +54,6 @@ const Notification = {
selecting: false,
statusExpanded: false,
unmuted: false,
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false,
}
},
props: ['notification'],
@ -72,9 +67,6 @@ const Notification = {
UserPopover,
UserLink,
ConfirmModal: defineAsyncComponent(
() => import('src/components/confirm_modal/confirm_modal.vue'),
),
},
mounted() {
document.addEventListener('selectionchange', this.onContentSelect)
@ -124,53 +116,7 @@ const Notification = {
toggleMute() {
this.unmuted = !this.unmuted
},
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
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()
},
...mapActions(useFollowRequestsStore, ['approve', 'deny']),
},
computed: {
status() {
@ -221,12 +167,6 @@ const Notification = {
scaleMfm() {
return this.mergedConfig.scaleMfm
},
shouldConfirmApprove() {
return this.mergedConfig.modalOnApproveFollow
},
shouldConfirmDeny() {
return this.mergedConfig.modalOnDenyFollow
},
...mapState(useUsersStore, ['currentUser']),
},
}

View file

@ -226,7 +226,7 @@
<button
class="button-unstyled"
:title="$t('tool_tip.accept_follow_request')"
@click="approveUser()"
@click="() => approve(user.id)"
>
<FAIcon
icon="check"
@ -236,7 +236,7 @@
<button
class="button-unstyled"
:title="$t('tool_tip.reject_follow_request')"
@click="denyUser()"
@click="() => deny(user.id)"
>
<FAIcon
icon="times"
@ -268,28 +268,6 @@
</template>
</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>
</template>

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { get } from 'lodash'
import { get } from 'lodash-es'
import { mapState } from 'pinia'
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 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 { 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 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 Select from 'components/select/select.vue'
import StillImage from 'components/still-image/still-image.vue'
import { clone } from 'lodash'
import { clone } from 'lodash-es'
import { defineAsyncComponent } from 'vue'
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 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 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 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 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 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 { useLocalConfigStore } from 'src/stores/local_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'
export default {
@ -236,7 +237,7 @@ export default {
configSource() {
switch (this.realSource) {
case 'profile':
return this.$store.state.profileConfig
return useProfileConfigStore().config
case 'admin':
return useAdminSettingsStore().config
default:
@ -253,7 +254,7 @@ export default {
switch (this.realSource) {
case 'profile':
return (k, v) =>
this.$store.dispatch('setProfileOption', { name: k, value: v })
useProfileConfigStore().setProfileOption({ name: k, value: v })
case 'admin':
return (k, v) =>
useAdminSettingsStore().pushAdminSetting({ path: k, value: v })
@ -412,8 +413,8 @@ export default {
hardReset() {
switch (this.realSource) {
case 'admin':
return this.$store
.dispatch('resetAdminSetting', { path: this.path })
return useAdminSettingsStore()
.resetAdminSetting({ path: this.path })
.then(() => {
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 { 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 { useMergedConfigStore } from 'src/stores/merged_config.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 { useUsersStore } from 'src/stores/users.js'
@ -109,6 +110,9 @@ const ComposingTab = {
FontControl,
},
computed: {
defaultScope() {
return useProfileConfigStore().config.defaultScope
},
postFormats() {
return useInstanceCapabilitiesStore().postFormats
},
@ -135,7 +139,7 @@ const ComposingTab = {
},
methods: {
changeDefaultScope(value) {
this.$store.dispatch('setProfileOption', { name: 'defaultScope', value })
useProfileConfigStore().setProfileOption({ name: 'defaultScope', value })
},
clearCache(key) {
clearCache(key)

View file

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

View file

@ -1,4 +1,4 @@
import { cloneDeep } from 'lodash'
import { cloneDeep } from 'lodash-es'
import { mapActions, mapState } from 'pinia'
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 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 {
computed,
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 ColorInput from 'src/components/color_input/color_input.vue'

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ import {
escape as ldEscape,
unescape as ldUnescape,
merge,
} from 'lodash'
} from 'lodash-es'
import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue'
@ -418,7 +418,6 @@ export default {
...useEmojiStore().standardEmojiList,
...useEmojiStore().customEmoji,
],
store: this.$store,
})
},
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 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 { useOAuthStore } from 'src/stores/oauth.js'

View file

@ -1742,6 +1742,7 @@
"approve_confirm_accept_button": "Approve",
"approve_confirm_cancel_button": "Do not approve",
"approve_confirm": "Do you want to approve {user}'s follow request?",
"approve_error": "Failure approving follow request: {error}",
"block": "Block",
"blocked": "Blocked!",
"block_confirm_title": "Block confirmation",
@ -1754,6 +1755,7 @@
"deny_confirm_accept_button": "Deny",
"deny_confirm_cancel_button": "Do not deny",
"deny_confirm": "Do you want to deny {user}'s follow request?",
"deny_error": "Failure denying follow request: {error}",
"edit_profile": "Edit profile",
"favorites": "Favorites",
"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
// 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 { 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 { useInterfaceStore } from 'src/stores/interface'
let loaded = false
const defaultReducer = (state, paths) =>
paths.length === 0
? state
@ -14,86 +10,10 @@ const defaultReducer = (state, paths) =>
return substate
}, {})
const saveImmedeatelyActions = [
'markNotificationsAsSeen',
'setHighlight',
'setOption',
'setClientData',
'setToken',
'clearToken',
]
const defaultStorage = (() => {
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
* if pinia persisted state does not exist.

View file

@ -1,7 +1,6 @@
/* global process */
import { createPinia } from 'pinia'
import { createStore } from 'vuex'
import 'custom-event-polyfill'
import './lib/event_target_polyfill.js'
@ -17,11 +16,8 @@ import { createI18n } from 'vue-i18n'
import afterStoreSetup from './boot/after_store.js'
import messages from './i18n/messages.js'
import createPersistedState, {
piniaPersistPlugin,
} from './lib/persisted_state.js'
import { piniaPersistPlugin } from './lib/persisted_state.js'
import { piniaPushNotificationsPlugin } from './lib/push_notifications_plugin.js'
import vuexModules from './modules/index.js'
import { piniaLanguagePlugin } from 'src/lib/language.js'
import { piniaStylePlugin } from 'src/lib/style.js'
@ -37,10 +33,6 @@ const i18n = createI18n({
messages.setLanguage(i18n.global, currentLocale)
const persistedStateOptions = {
paths: ['oauth', 'config'],
}
;(async () => {
const isFox = Math.floor(Math.random() * 2) > 0 ? '_fox' : ''
@ -69,20 +61,12 @@ const persistedStateOptions = {
try {
let storageError
const plugins = []
const pinia = createPinia()
pinia.use(piniaPersistPlugin())
pinia.use(piniaLanguagePlugin)
pinia.use(piniaStylePlugin)
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('#mascot').src =
`/static/pleromatan_apology${isFox}_small.webp`
@ -93,18 +77,8 @@ const persistedStateOptions = {
'update.art_by',
{ linkToArtist: 'pipivovott' },
)
const store = createStore({
modules: vuexModules,
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 })
// Temporarily passing pinia stores along with storageError result until migration is fully complete.
return await afterStoreSetup({ pinia, storageError, i18n })
} catch (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]

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) =>
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) => {
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) =>
isFunction(Component) ? Component.options : Component

View file

@ -1,6 +1,6 @@
import { parseLinkHeader } from '@web3-storage/parse-link-header'
import escapeHtml from 'escape-html'
import { unescape as lodashUnescape } from 'lodash'
import { unescape as lodashUnescape } from 'lodash-es'
import punycode from 'punycode.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) {
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'

View file

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

View file

@ -98,13 +98,11 @@ export const unseenNotifications = (
}
export const countExtraNotifications = (
store,
mergedConfig,
unreadChatsCount,
unreadAnnouncementCount,
unreadAnnouncementsCount,
followRequestsCount,
) => {
const rootGetters = store.rootGetters || store.getters
if (!mergedConfig.showExtraNotifications) {
return 0
}
@ -112,10 +110,10 @@ export const countExtraNotifications = (
return [
mergedConfig.showChatsInExtraNotifications ? unreadChatsCount : 0,
mergedConfig.showAnnouncementsInExtraNotifications
? unreadAnnouncementCount
? unreadAnnouncementsCount
: 0,
mergedConfig.showFollowRequestsInExtraNotifications
? rootGetters.followRequestCount
? followRequestsCount
: 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 { useStatusesStore } from 'src/stores/statuses.js'

View file

@ -1,6 +1,6 @@
import sum from 'hash-sum'
import localforage from 'localforage'
import { chunk, throttle } from 'lodash'
import { chunk, throttle } from 'lodash-es'
import { getCssRules } from '../theme_data/css_utils.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) => {
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: {...} } }}
// into an array [item2, item3] for iterating

View file

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

View file

@ -1,4 +1,4 @@
import { includes } from 'lodash'
import { includes } from 'lodash-es'
const generateProfileLink = (id, screenName, restrictedNicknames) => {
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 { useOAuthStore } from 'src/stores/oauth.js'

View file

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

View file

@ -1,4 +1,4 @@
import { find, remove } from 'lodash'
import { find, remove } from 'lodash-es'
import { defineStore } from 'pinia'
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 { 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