From 148ce1eb990c55f4da64b335e0695b10d20bf2c0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 2 Sep 2026 16:58:31 +0300 Subject: [PATCH] migrate drafts store to pinia as-is --- src/components/draft/draft.js | 7 +- src/components/drafts/drafts.js | 6 +- .../post_status_form/post_status_form.js | 19 ++-- src/components/side_drawer/side_drawer.js | 4 +- src/modules/drafts.js | 99 ------------------- src/modules/index.js | 6 +- src/stores/drafts.js | 75 ++++++++++++++ src/stores/users.js | 3 +- 8 files changed, 96 insertions(+), 123 deletions(-) delete mode 100644 src/modules/drafts.js create mode 100644 src/stores/drafts.js diff --git a/src/components/draft/draft.js b/src/components/draft/draft.js index 1c7f419c6..582381ec6 100644 --- a/src/components/draft/draft.js +++ b/src/components/draft/draft.js @@ -7,6 +7,7 @@ import StatusContent from 'src/components/status_content/status_content.vue' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useStatusesStore } from 'src/stores/statuses.js' +import { useDraftsStore } from 'src/stores/drafts.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faPollH } from '@fortawesome/free-solid-svg-icons' @@ -77,9 +78,9 @@ const Draft = { editing(newVal) { if (newVal) return if (this.safeToSave) { - this.$store.dispatch('addOrSaveDraft', { draft: this.draft }) + useDraftsStore().addOrSaveDraft(this.draft) } else { - this.$store.dispatch('addOrSaveDraft', { draft: this.referenceDraft }) + useDraftsStore().addOrSaveDraft(this.referenceDraft) } }, }, @@ -91,7 +92,7 @@ const Draft = { this.showingConfirmDialog = true }, doAbandon() { - this.$store.dispatch('abandonDraft', { id: this.draft.id }).then(() => { + useDraftsStore().abandonDraft(this.draft.id).then(() => { this.hideConfirmDialog() }) }, diff --git a/src/components/drafts/drafts.js b/src/components/drafts/drafts.js index 3d93edb14..c2cac4f13 100644 --- a/src/components/drafts/drafts.js +++ b/src/components/drafts/drafts.js @@ -1,4 +1,5 @@ import { defineAsyncComponent } from 'vue' +import { useDraftsStore } from 'src/stores/drafts.js' import Draft from 'src/components/draft/draft.vue' import List from 'src/components/list/list.vue' @@ -18,7 +19,7 @@ const Drafts = { }, computed: { drafts() { - return this.$store.getters.draftsArray + return useDraftsStore().draftsArray }, }, methods: { @@ -26,8 +27,7 @@ const Drafts = { this.showingConfirmDialog = true }, doAbandonAll() { - this.$store - .dispatch('abandonAllDrafts') + useDraftsStore().abandonAllDrafts() .then(() => this.hideConfirmDialog()) }, hideConfirmDialog() { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 4fb6f4746..2e303a338 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -32,6 +32,7 @@ import { useMediaViewerStore } from 'src/stores/media_viewer.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUsersStore } from 'src/stores/users.js' +import { useDraftsStore } from 'src/stores/drafts.js' import { pollFormToMasto } from 'src/services/poll/poll.service.js' @@ -985,13 +986,11 @@ 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, }) .then((id) => { if (this.newStatus.id !== id) { @@ -1024,14 +1023,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, ) diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index f2702f430..5949649a0 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -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' @@ -15,6 +14,7 @@ import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useShoutStore } from 'src/stores/shout' import { useUsersStore } from 'src/stores/users.js' +import { useDraftsStore } from 'src/stores/drafts.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -110,7 +110,7 @@ const SideDrawer = { hideSitename: (store) => store.instanceIdentity.hideSitename, }), ...mapState(useChatsStore, ['unreadChatsCount']), - ...mapGetters(['draftCount']), + ...mapState(useDraftsStore, ['draftCount']), }, methods: { toggleDrawer() { diff --git a/src/modules/drafts.js b/src/modules/drafts.js deleted file mode 100644 index 3cde4f574..000000000 --- a/src/modules/drafts.js +++ /dev/null @@ -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 diff --git a/src/modules/index.js b/src/modules/index.js index 436d7e844..b1c6ea436 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -1,5 +1 @@ -import drafts from './drafts.js' - -export default { - drafts, -} +export default {} diff --git a/src/stores/drafts.js b/src/stores/drafts.js new file mode 100644 index 000000000..bca8ccac0 --- /dev/null +++ b/src/stores/drafts.js @@ -0,0 +1,75 @@ +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 abandonDraft(id) { + this.drafts.delete(id) + await deleteDraftFromStorage([id]) + }, + 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(draft.id, draftWithId) + await saveDraftToStorage(draftWithId) + return id + }, + async abandonAllDrafts(store) { + const ids = this.drafts.keys() + ids.forEach((id) => this.abandonDraft(id)) + await deleteDraftFromStorage(ids) + }, + } +}) diff --git a/src/stores/users.js b/src/stores/users.js index 5548a14df..7471b9ab5 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -21,6 +21,7 @@ import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useProfileConfigStore } from 'src/stores/profile_config.js' import { useTimelinesStore } from 'src/stores/timelines.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' +import { useDraftsStore } from 'src/stores/drafts.js' import { revokeToken } from 'src/api/oauth.js' import { @@ -700,7 +701,7 @@ export const useUsersStore = defineStore('users', { useAnnouncementsStore().startFetching() this.fetchMutes() - dispatch('loadDrafts') + useDraftsStore().loadDrafts() } catch (error) { console.error(error)