diff --git a/.babelrc b/.babelrc
index 4ec104161..48f99d6ca 100644
--- a/.babelrc
+++ b/.babelrc
@@ -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
}
diff --git a/changelog.d/drafts.fix b/changelog.d/drafts.fix
new file mode 100644
index 000000000..f209455c5
--- /dev/null
+++ b/changelog.d/drafts.fix
@@ -0,0 +1 @@
+Fixed drafts creating duplicates on edit instead of updating
diff --git a/docs/HACKING.md b/docs/HACKING.md
index 88760b77a..8d3c09a0c 100644
--- a/docs/HACKING.md
+++ b/docs/HACKING.md
@@ -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.
diff --git a/package.json b/package.json
index 50138e6a2..8f6a2a98f 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/App.js b/src/App.js
index ed3d43e4a..ae38204b6 100644
--- a/src/App.js
+++ b/src/App.js
@@ -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'),
),
diff --git a/src/App.vue b/src/App.vue
index bd19c5c10..cad329a78 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -75,6 +75,7 @@
+
diff --git a/src/api/helpers.js b/src/api/helpers.js
index f23960ed9..6afd20468 100644
--- a/src/api/helpers.js
+++ b/src/api/helpers.js
@@ -1,4 +1,4 @@
-import { snakeCase } from 'lodash'
+import { snakeCase } from 'lodash-es'
import { StatusCodeError } from 'src/services/errors/errors'
diff --git a/src/api/user.js b/src/api/user.js
index ec1763bb4..2f356ebbc 100644
--- a/src/api/user.js
+++ b/src/api/user.js
@@ -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,
})
}
diff --git a/src/components/chat_message_list/chat_message_list.js b/src/components/chat_message_list/chat_message_list.js
index a51e6a0e4..6aef4336b 100644
--- a/src/components/chat_message_list/chat_message_list.js
+++ b/src/components/chat_message_list/chat_message_list.js
@@ -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'
diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js
index 4f015db6a..03960a287 100644
--- a/src/components/chat_new/chat_new.js
+++ b/src/components/chat_new/chat_new.js
@@ -71,7 +71,6 @@ const chatNew = {
this.loading = true
this.userIds = []
- this.$store
useSearchStore()
.search({ q: query, resolve: true, type: 'accounts' })
.then((data) => {
diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js
index a0abcb5fe..7d80459bb 100644
--- a/src/components/chat_view/chat_view.js
+++ b/src/components/chat_view/chat_view.js
@@ -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'
diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index 53396f532..821a1be0e 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -64,7 +64,7 @@
diff --git a/src/components/follow_requests/follow_requests.js b/src/components/follow_requests/follow_requests.js
index 513298afc..181d5605e 100644
--- a/src/components/follow_requests/follow_requests.js
+++ b/src/components/follow_requests/follow_requests.js
@@ -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()
},
},
}
diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js
index c244a8cb3..f6c79ac76 100644
--- a/src/components/gallery/gallery.js
+++ b/src/components/gallery/gallery.js
@@ -1,4 +1,4 @@
-import { set, sumBy } from 'lodash'
+import { set, sumBy } from 'lodash-es'
import Attachment from 'src/components/attachment/attachment.vue'
diff --git a/src/components/list/list.js b/src/components/list/list.js
index 8abf02fd6..2283f5260 100644
--- a/src/components/list/list.js
+++ b/src/components/list/list.js
@@ -1,4 +1,4 @@
-import { isEmpty } from 'lodash'
+import { isEmpty } from 'lodash-es'
import Checkbox from 'src/components/checkbox/checkbox.vue'
diff --git a/src/components/lists_user_search/lists_user_search.js b/src/components/lists_user_search/lists_user_search.js
index 22568135a..4dde2f589 100644
--- a/src/components/lists_user_search/lists_user_search.js
+++ b/src/components/lists_user_search/lists_user_search.js
@@ -1,4 +1,4 @@
-import { debounce } from 'lodash'
+import { debounce } from 'lodash-es'
import Checkbox from 'src/components/checkbox/checkbox.vue'
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index 2cb6a96e6..d3867b1d0 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -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()
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index 97645ca73..67e8c4418 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -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'),
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 8cdb70a1a..336ad60a6 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -19,7 +19,7 @@
icon="bars"
/>
diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js
index 0ffab6f78..3fbd9b77b 100644
--- a/src/components/mobile_post_status_button/mobile_post_status_button.js
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.js
@@ -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'
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index 9b948e306..73bf6e515 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -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'
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index 7f2a16186..c3530975e 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -1,4 +1,4 @@
-import { get } from 'lodash'
+import { get } from 'lodash-es'
import { mapState } from 'pinia'
import { useInstanceStore } from 'src/stores/instance.js'
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index c3e760efb..e03a0c9d7 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -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(
diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js
index 39fa2c993..7f4208162 100644
--- a/src/components/navigation/navigation.js
+++ b/src/components/navigation/navigation.js
@@ -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',
},
}
diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js
index 31bc28eb5..1a7832afe 100644
--- a/src/components/navigation/navigation_entry.js
+++ b/src/components/navigation/navigation_entry.js
@@ -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),
diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue
index 3ab0355b1..cbebbaf47 100644
--- a/src/components/navigation/navigation_entry.vue
+++ b/src/components/navigation/navigation_entry.vue
@@ -47,17 +47,11 @@
- {{ getters[item.badgeGetter] }}
-
-
- {{ this[`${item.store}Store`][item.badgeGetter] }}
+ {{ badges[item.badgeGetter] }}