migrate drafts store to pinia as-is

This commit is contained in:
Henry Jameson 2026-09-02 16:58:31 +03:00
commit 148ce1eb99
8 changed files with 96 additions and 123 deletions

View file

@ -7,6 +7,7 @@ import StatusContent from 'src/components/status_content/status_content.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js' import { useStatusesStore } from 'src/stores/statuses.js'
import { useDraftsStore } from 'src/stores/drafts.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faPollH } from '@fortawesome/free-solid-svg-icons' import { faPollH } from '@fortawesome/free-solid-svg-icons'
@ -77,9 +78,9 @@ const Draft = {
editing(newVal) { editing(newVal) {
if (newVal) return if (newVal) return
if (this.safeToSave) { if (this.safeToSave) {
this.$store.dispatch('addOrSaveDraft', { draft: this.draft }) useDraftsStore().addOrSaveDraft(this.draft)
} else { } else {
this.$store.dispatch('addOrSaveDraft', { draft: this.referenceDraft }) useDraftsStore().addOrSaveDraft(this.referenceDraft)
} }
}, },
}, },
@ -91,7 +92,7 @@ const Draft = {
this.showingConfirmDialog = true this.showingConfirmDialog = true
}, },
doAbandon() { doAbandon() {
this.$store.dispatch('abandonDraft', { id: this.draft.id }).then(() => { useDraftsStore().abandonDraft(this.draft.id).then(() => {
this.hideConfirmDialog() this.hideConfirmDialog()
}) })
}, },

View file

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

View file

@ -32,6 +32,7 @@ import { useMediaViewerStore } from 'src/stores/media_viewer.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js' import { useUsersStore } from 'src/stores/users.js'
import { useDraftsStore } from 'src/stores/drafts.js'
import { pollFormToMasto } from 'src/services/poll/poll.service.js' import { pollFormToMasto } from 'src/services/poll/poll.service.js'
@ -985,13 +986,11 @@ const PostStatusForm = {
saveDraft() { saveDraft() {
if (!this.disableDraft && !this.saveInhibited) { if (!this.disableDraft && !this.saveInhibited) {
if (this.safeToSaveDraft) { if (this.safeToSaveDraft) {
return this.$store return useDraftsStore()
.dispatch('addOrSaveDraft', { .addOrSaveDraft({
draft: { type: this.statusType,
type: this.statusType, refId: this.refId,
refId: this.refId, ...this.newStatus,
...this.newStatus,
},
}) })
.then((id) => { .then((id) => {
if (this.newStatus.id !== id) { if (this.newStatus.id !== id) {
@ -1024,14 +1023,14 @@ const PostStatusForm = {
} }
}, },
abandonDraft() { abandonDraft() {
return this.$store.dispatch('abandonDraft', { id: this.draftId }) return useDraftsStore().abandonDraft(this.draftId)
}, },
getDraft() { getDraft() {
const maybeDraft = this.$store.state.drafts.drafts[this.draftId] const maybeDraft = useDraftsStore().drafts.get(this.draftId)
if (this.draftId && maybeDraft) { if (this.draftId && maybeDraft) {
return maybeDraft return maybeDraft
} else { } else {
const existingDrafts = this.$store.getters.draftsByTypeAndRefId( const existingDrafts = useDraftsStore().draftsByTypeAndRefId(
this.statusType, this.statusType,
this.refId, this.refId,
) )

View file

@ -1,5 +1,4 @@
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import { mapGetters } from 'vuex'
import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js' import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
import UserCard from 'src/components/user_card/user_card.vue' import UserCard from 'src/components/user_card/user_card.vue'
@ -15,6 +14,7 @@ import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useShoutStore } from 'src/stores/shout' import { useShoutStore } from 'src/stores/shout'
import { useUsersStore } from 'src/stores/users.js' import { useUsersStore } from 'src/stores/users.js'
import { useDraftsStore } from 'src/stores/drafts.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -110,7 +110,7 @@ const SideDrawer = {
hideSitename: (store) => store.instanceIdentity.hideSitename, hideSitename: (store) => store.instanceIdentity.hideSitename,
}), }),
...mapState(useChatsStore, ['unreadChatsCount']), ...mapState(useChatsStore, ['unreadChatsCount']),
...mapGetters(['draftCount']), ...mapState(useDraftsStore, ['draftCount']),
}, },
methods: { methods: {
toggleDrawer() { toggleDrawer() {

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,5 +1 @@
import drafts from './drafts.js' export default {}
export default {
drafts,
}

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

@ -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)
},
}
})

View file

@ -21,6 +21,7 @@ import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useProfileConfigStore } from 'src/stores/profile_config.js' import { useProfileConfigStore } from 'src/stores/profile_config.js'
import { useTimelinesStore } from 'src/stores/timelines.js' import { useTimelinesStore } from 'src/stores/timelines.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import { useDraftsStore } from 'src/stores/drafts.js'
import { revokeToken } from 'src/api/oauth.js' import { revokeToken } from 'src/api/oauth.js'
import { import {
@ -700,7 +701,7 @@ export const useUsersStore = defineStore('users', {
useAnnouncementsStore().startFetching() useAnnouncementsStore().startFetching()
this.fetchMutes() this.fetchMutes()
dispatch('loadDrafts') useDraftsStore().loadDrafts()
} catch (error) { } catch (error) {
console.error(error) console.error(error)