commit: 1f56401a8ef04a1dfdc8e6405aa43a022588cbb2
parent a5c4853987fe1382cc6ceeca2abde19a94280258
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date: Mon, 3 Feb 2025 15:52:22 +0000
Merge branch 'migrate/vuex-to-pinia' into 'develop'
Migrate from vuex to pinia
Closes #1202
See merge request pleroma/pleroma-fe!1807
Diffstat:
101 files changed, 1781 insertions(+), 1812 deletions(-)
diff --git a/changelog.d/pinia.change b/changelog.d/pinia.change
@@ -0,0 +1 @@
+Partially migrated from vuex to pinia
diff --git a/package.json b/package.json
@@ -38,6 +38,7 @@
"pako": "^2.1.0",
"parse-link-header": "2.0.0",
"phoenix": "1.7.19",
+ "pinia": "^2.0.33",
"punycode.js": "2.3.1",
"qrcode": "1.5.4",
"querystring-es3": "0.2.1",
diff --git a/src/App.js b/src/App.js
@@ -17,6 +17,8 @@ import GlobalNoticeList from './components/global_notice_list/global_notice_list
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
import { defineAsyncComponent } from 'vue'
+import { useShoutStore } from './stores/shout'
+import { useInterfaceStore } from './stores/interface'
export default {
name: 'app',
@@ -60,7 +62,7 @@ export default {
document.getElementById('modal').classList = ['-' + this.layoutType]
},
mounted () {
- if (this.$store.state.interface.themeApplied) {
+ if (useInterfaceStore().themeApplied) {
this.removeSplash()
}
},
@@ -69,7 +71,7 @@ export default {
},
computed: {
themeApplied () {
- return this.$store.state.interface.themeApplied
+ return useInterfaceStore().themeApplied
},
layoutModalClass () {
return '-' + this.layoutType
@@ -106,7 +108,7 @@ export default {
}
}
},
- shout () { return this.$store.state.shout.joined },
+ shout () { return useShoutStore().joined },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
@@ -132,7 +134,7 @@ export default {
hideShoutbox () {
return this.$store.getters.mergedConfig.hideShoutbox
},
- layoutType () { return this.$store.state.interface.layoutType },
+ layoutType () { return useInterfaceStore().layoutType },
privateMode () { return this.$store.state.instance.private },
reverseLayout () {
const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
@@ -148,8 +150,8 @@ export default {
},
methods: {
updateMobileState () {
- this.$store.dispatch('setLayoutWidth', windowWidth())
- this.$store.dispatch('setLayoutHeight', windowHeight())
+ useInterfaceStore().setLayoutWidth(windowWidth())
+ useInterfaceStore().setLayoutHeight(windowHeight())
},
removeSplash () {
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
diff --git a/src/App.vue b/src/App.vue
@@ -1,6 +1,6 @@
<template>
<div
- v-show="$store.state.interface.themeApplied"
+ v-show="themeApplied"
id="app-loaded"
:style="bgStyle"
>
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
@@ -17,6 +17,10 @@ import { applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
+import { useI18nStore } from 'src/stores/i18n'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useAnnouncementsStore } from 'src/stores/announcements'
+
let staticInitialResults = null
const parsedInitialResults = () => {
@@ -333,9 +337,16 @@ const checkOAuthToken = async ({ store }) => {
return Promise.resolve()
}
-const afterStoreSetup = async ({ store, i18n }) => {
- store.dispatch('setLayoutWidth', windowWidth())
- store.dispatch('setLayoutHeight', windowHeight())
+const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
+ const app = createApp(App)
+ app.use(pinia)
+
+ if (storageError) {
+ useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error' })
+ }
+
+ useInterfaceStore().setLayoutWidth(windowWidth())
+ useInterfaceStore().setLayoutHeight(windowHeight())
FaviconService.initFaviconService()
initServiceWorker(store)
@@ -348,7 +359,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
await setConfig({ store })
try {
- await store.dispatch('applyTheme').catch((e) => { console.error('Error setting theme', e) })
+ await useInterfaceStore().applyTheme().catch((e) => { console.error('Error setting theme', e) })
} catch (e) {
window.splashError(e)
return Promise.reject(e)
@@ -369,7 +380,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
- store.dispatch('startFetchingAnnouncements')
+ useAnnouncementsStore().startFetchingAnnouncements()
getTOS({ store })
getStickers({ store })
@@ -384,7 +395,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
}
})
- const app = createApp(App)
+ useI18nStore().setI18n(i18n)
app.use(router)
app.use(store)
@@ -392,9 +403,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
// Little thing to get out of invalid theme state
window.resetThemes = () => {
- store.dispatch('resetThemeV3')
- store.dispatch('resetThemeV3Palette')
- store.dispatch('resetThemeV2')
+ useInterfaceStore().resetThemeV3()
+ useInterfaceStore().resetThemeV3Palette()
+ useInterfaceStore().resetThemeV2()
}
app.use(vClickOutside)
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
@@ -7,6 +7,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisV
} from '@fortawesome/free-solid-svg-icons'
+import { useReportsStore } from 'src/stores/reports'
library.add(
faEllipsisV
@@ -73,7 +74,7 @@ const AccountActions = {
this.hideConfirmRemoveUserFromFollowers()
},
reportUser () {
- this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
+ useReportsStore().openUserReportingModal({ userId: this.user.id })
},
openChat () {
this.$router.push({
diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js
@@ -2,6 +2,7 @@ import { mapState } from 'vuex'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
import RichContent from '../rich_content/rich_content.jsx'
import localeService from '../../services/locale/locale.service.js'
+import { useAnnouncementsStore } from 'src/stores/announcements'
const Announcement = {
components: {
@@ -67,11 +68,11 @@ const Announcement = {
methods: {
markAsRead () {
if (!this.isRead) {
- return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id)
+ return useAnnouncementsStore().markAnnouncementAsRead(this.announcement.id)
}
},
deleteAnnouncement () {
- return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
+ return useAnnouncementsStore().deleteAnnouncement(this.announcement.id)
},
formatTimeOrDate (time, locale) {
const d = new Date(time)
@@ -85,7 +86,7 @@ const Announcement = {
this.editing = true
},
submitEdit () {
- this.$store.dispatch('editAnnouncement', {
+ useAnnouncementsStore().editAnnouncement({
id: this.announcement.id,
...this.editedAnnouncement
})
diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js
@@ -1,6 +1,7 @@
import { mapState } from 'vuex'
import Announcement from '../announcement/announcement.vue'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
+import { useAnnouncementsStore } from 'src/stores/announcements'
const AnnouncementsPage = {
components: {
@@ -20,14 +21,14 @@ const AnnouncementsPage = {
}
},
mounted () {
- this.$store.dispatch('fetchAnnouncements')
+ useAnnouncementsStore().fetchAnnouncements()
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
}),
announcements () {
- return this.$store.state.announcements.announcements
+ return useAnnouncementsStore().announcements
},
canPostAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
@@ -36,7 +37,7 @@ const AnnouncementsPage = {
methods: {
postAnnouncement () {
this.posting = true
- this.$store.dispatch('postAnnouncement', this.newAnnouncement)
+ useAnnouncementsStore().postAnnouncement(this.newAnnouncement)
.then(() => {
this.newAnnouncement.content = ''
this.startsAt = undefined
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
@@ -18,6 +18,7 @@ import {
faPencilAlt,
faAlignRight
} from '@fortawesome/free-solid-svg-icons'
+import { useMediaViewerStore } from 'src/stores/media_viewer'
library.add(
faFile,
@@ -147,14 +148,14 @@ const Attachment = {
openModal (event) {
if (this.useModal) {
this.$emit('setMedia')
- this.$store.dispatch('setCurrentMedia', this.attachment)
+ useMediaViewerStore().setCurrentMedia(this.attachment)
} else if (this.type === 'unknown') {
window.open(this.attachment.url)
}
},
openModalForce (event) {
this.$emit('setMedia')
- this.$store.dispatch('setCurrentMedia', this.attachment)
+ useMediaViewerStore().setCurrentMedia(this.attachment)
},
onEdit (event) {
this.edit && this.edit(this.attachment, event)
diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js
@@ -63,7 +63,7 @@ const BookmarkFolderEdit = {
this.$router.push({ name: 'bookmark-folders' })
})
.catch((e) => {
- this.$store.dispatch('pushGlobalNotice', {
+ this.$store.useInterfaceStore().pushGlobalNotice({
messageKey: 'bookmark_folders.error',
messageArgs: [e.message],
level: 'error'
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
@@ -1,6 +1,7 @@
import _ from 'lodash'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
import ChatMessage from '../chat_message/chat_message.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import ChatTitle from '../chat_title/chat_title.vue'
@@ -13,6 +14,7 @@ import {
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
library.add(
faChevronDown,
@@ -90,10 +92,12 @@ const Chat = {
'findOpenedChatByRecipientId',
'mergedConfig'
]),
+ ...mapPiniaState(useInterfaceStore, {
+ mobileLayout: store => store.layoutType === 'mobile'
+ }),
...mapState({
backendInteractor: state => state.api.backendInteractor,
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
- mobileLayout: state => state.interface.layoutType === 'mobile',
currentUser: state => state.users.currentUser
})
},
diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js
@@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
import Popover from '../popover/popover.vue'
import Attachment from '../attachment/attachment.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
@@ -12,6 +13,7 @@ import {
faTimes,
faEllipsisH
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faTimes,
@@ -65,6 +67,9 @@ const ChatMessage = {
hasAttachment () {
return this.message.attachments.length > 0
},
+ ...mapPiniaState(useInterfaceStore, {
+ betterShadow: store => store.browserSupport.cssFilter
+ }),
...mapState({
currentUser: state => state.users.currentUser,
restrictedNicknames: state => state.instance.restrictedNicknames
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
@@ -3,8 +3,10 @@ import Status from '../status/status.vue'
import ThreadTree from '../thread_tree/thread_tree.vue'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
+import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -350,8 +352,10 @@ const conversation = {
},
...mapGetters(['mergedConfig']),
...mapState({
- mobileLayout: state => state.interface.layoutType === 'mobile',
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
+ }),
+ ...mapPiniaState(useInterfaceStore, {
+ mobileLayout: store => store.layoutType === 'mobile'
})
},
components: {
diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js
@@ -14,6 +14,7 @@ import {
faCog,
faInfoCircle
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faSignInAlt,
@@ -107,10 +108,10 @@ export default {
this.searchBarHidden = hidden
},
openSettingsModal () {
- this.$store.dispatch('openSettingsModal', 'user')
+ useInterfaceStore().openSettingsModal('user')
},
openAdminModal () {
- this.$store.dispatch('openSettingsModal', 'admin')
+ useInterfaceStore().openSettingsModal('admin')
}
}
}
diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js
@@ -1,6 +1,7 @@
import EditStatusForm from '../edit_status_form/edit_status_form.vue'
import Modal from '../modal/modal.vue'
import get from 'lodash/get'
+import { useEditStatusStore } from 'src/stores/editStatus'
const EditStatusModal = {
components: {
@@ -17,13 +18,13 @@ const EditStatusModal = {
return !!this.$store.state.users.currentUser
},
modalActivated () {
- return this.$store.state.editStatus.modalActivated
+ return useEditStatusStore().modalActivated
},
isFormVisible () {
return this.isLoggedIn && !this.resettingForm && this.modalActivated
},
params () {
- return this.$store.state.editStatus.params || {}
+ return useEditStatusStore().params || {}
}
},
watch: {
@@ -46,7 +47,7 @@ const EditStatusModal = {
this.$refs.editStatusForm.requestClose()
},
doCloseModal () {
- this.$store.dispatch('closeEditStatusModal')
+ useEditStatusStore().closeEditStatusModal()
}
}
}
diff --git a/src/components/extra_notifications/extra_notifications.js b/src/components/extra_notifications/extra_notifications.js
@@ -1,4 +1,6 @@
import { mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
+import { useAnnouncementsStore } from 'src/stores/announcements'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -7,6 +9,8 @@ import {
faBullhorn
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
+
library.add(
faUserPlus,
faComments,
@@ -33,11 +37,14 @@ const ExtraNotifications = {
currentUser () {
return this.$store.state.users.currentUser
},
- ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'followRequestCount', 'mergedConfig'])
+ ...mapGetters(['unreadChatCount', 'followRequestCount', 'mergedConfig']),
+ ...mapPiniaState(useAnnouncementsStore, {
+ unreadAnnouncementCount: 'unreadAnnouncementCount'
+ })
},
methods: {
openNotificationSettings () {
- return this.$store.dispatch('openSettingsModalTab', 'notifications')
+ return useInterfaceStore().openSettingsModalTab('notifications')
},
dismissConfigurationTip () {
return this.$store.dispatch('setOption', { name: 'showExtraNotificationsTip', value: false })
diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js
@@ -1,6 +1,7 @@
import Select from '../select/select.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue'
+import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -25,7 +26,7 @@ export default {
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
],
mounted () {
- this.$store.dispatch('queryLocalFonts')
+ useInterfaceStore().queryLocalFonts()
},
emits: ['update:modelValue'],
data () {
@@ -50,10 +51,10 @@ export default {
return typeof this.modelValue !== 'undefined'
},
localFontsList () {
- return this.$store.state.interface.localFonts
+ return useInterfaceStore().localFonts
},
localFontsSize () {
- return this.$store.state.interface.localFonts?.length
+ return useInterfaceStore().localFonts?.length
}
}
}
diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js
@@ -1,3 +1,4 @@
+import { useMediaViewerStore } from 'src/stores/media_viewer'
import Attachment from '../attachment/attachment.vue'
import { sumBy, set } from 'lodash'
@@ -107,11 +108,11 @@ const Gallery = {
this.hidingLong = event
},
openGallery () {
- this.$store.dispatch('setMedia', this.attachments)
- this.$store.dispatch('setCurrentMedia', this.attachments[0])
+ useMediaViewerStore().setMedia(this.attachments)
+ useMediaViewerStore().setCurrentMedia(this.attachments[0])
},
onMedia () {
- this.$store.dispatch('setMedia', this.attachments)
+ useMediaViewerStore().setMedia(this.attachments)
}
}
}
diff --git a/src/components/global_notice_list/global_notice_list.js b/src/components/global_notice_list/global_notice_list.js
@@ -2,6 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faTimes
@@ -10,12 +11,12 @@ library.add(
const GlobalNoticeList = {
computed: {
notices () {
- return this.$store.state.interface.globalNotices
+ return useInterfaceStore().globalNotices
}
},
methods: {
closeNotice (notice) {
- this.$store.dispatch('removeGlobalNotice', notice)
+ useInterfaceStore().removeGlobalNotice(notice)
}
}
}
diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js
@@ -1,3 +1,4 @@
+import { useListsStore } from 'src/stores/lists'
import ListsCard from '../lists_card/lists_card.vue'
const Lists = {
@@ -11,7 +12,7 @@ const Lists = {
},
computed: {
lists () {
- return this.$store.state.lists.allLists
+ return useListsStore().allLists
}
},
methods: {
diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js
@@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
@@ -9,6 +10,8 @@ import {
faSearch,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useListsStore } from 'src/stores/lists'
library.add(
faSearch,
@@ -37,12 +40,12 @@ const ListsNew = {
},
created () {
if (!this.id) return
- this.$store.dispatch('fetchList', { listId: this.id })
+ useListsStore().fetchList({ listId: this.id })
.then(() => {
this.title = this.findListTitle(this.id)
this.titleDraft = this.title
})
- this.$store.dispatch('fetchListAccounts', { listId: this.id })
+ useListsStore().fetchListAccounts({ listId: this.id })
.then(() => {
this.membersUserIds = this.findListAccounts(this.id)
this.membersUserIds.forEach(userId => {
@@ -64,7 +67,8 @@ const ListsNew = {
...mapState({
currentUser: state => state.users.currentUser
}),
- ...mapGetters(['findUser', 'findListTitle', 'findListAccounts'])
+ ...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']),
+ ...mapGetters(['findUser'])
},
methods: {
onInput () {
@@ -95,10 +99,10 @@ const ListsNew = {
return this.addedUserIds.has(user.id)
},
addUser (user) {
- this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id })
+ useListsStore().addListAccount({ accountId: user.id, listId: this.id })
},
removeUser (userId) {
- this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id })
+ useListsStore().removeListAccount({ accountId: userId, listId: this.id })
},
onSearchLoading (results) {
this.searchLoading = true
@@ -111,24 +115,23 @@ const ListsNew = {
this.searchUserIds = results
},
updateListTitle () {
- this.$store.dispatch('setList', { listId: this.id, title: this.titleDraft })
+ useListsStore().setList({ listId: this.id, title: this.titleDraft })
.then(() => {
this.title = this.findListTitle(this.id)
})
},
createList () {
- this.$store.dispatch('createList', { title: this.titleDraft })
+ useListsStore().createList({ title: this.titleDraft })
.then((list) => {
- return this
- .$store
- .dispatch('setListAccounts', { listId: list.id, accountIds: [...this.addedUserIds] })
+ return useListsStore()
+ .setListAccounts({ listId: list.id, accountIds: [...this.addedUserIds] })
.then(() => list.id)
})
.then((listId) => {
this.$router.push({ name: 'lists-timeline', params: { id: listId } })
})
.catch((e) => {
- this.$store.dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
messageKey: 'lists.error',
messageArgs: [e.message],
level: 'error'
@@ -136,7 +139,7 @@ const ListsNew = {
})
},
deleteList () {
- this.$store.dispatch('deleteList', { listId: this.id })
+ useListsStore().deleteList({ listId: this.id })
this.$router.push({ name: 'lists' })
}
}
diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js
@@ -1,6 +1,8 @@
import { mapState } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { getListEntries } from 'src/components/navigation/filter.js'
+import { useListsStore } from 'src/stores/lists'
export const ListsMenuContent = {
props: [
@@ -10,8 +12,10 @@ export const ListsMenuContent = {
NavigationEntry
},
computed: {
+ ...mapPiniaState(useListsStore, {
+ lists: getListEntries
+ }),
...mapState({
- lists: getListEntries,
currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private,
federating: state => state.instance.federating
diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js
@@ -1,3 +1,4 @@
+import { useListsStore } from 'src/stores/lists'
import Timeline from '../timeline/timeline.vue'
const ListsTimeline = {
data () {
@@ -17,14 +18,14 @@ const ListsTimeline = {
this.listId = route.params.id
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
- this.$store.dispatch('fetchList', { listId: this.listId })
+ useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
}
}
},
created () {
this.listId = this.$route.params.id
- this.$store.dispatch('fetchList', { listId: this.listId })
+ useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
},
unmounted () {
diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
@@ -13,6 +13,7 @@ import {
faCircleNotch,
faTimes
} from '@fortawesome/free-solid-svg-icons'
+import { useMediaViewerStore } from 'src/stores/media_viewer'
library.add(
faChevronLeft,
@@ -44,16 +45,16 @@ const MediaModal = {
},
computed: {
showing () {
- return this.$store.state.mediaViewer.activated
+ return useMediaViewerStore().activated
},
media () {
- return this.$store.state.mediaViewer.media
+ return useMediaViewerStore().media
},
description () {
return this.currentMedia.description
},
currentIndex () {
- return this.$store.state.mediaViewer.currentIndex
+ return useMediaViewerStore().currentIndex
},
currentMedia () {
return this.media[this.currentIndex]
@@ -79,7 +80,7 @@ const MediaModal = {
// to be processed on the content below the overlay
const transitionTime = 100 // ms
setTimeout(() => {
- this.$store.dispatch('closeMediaViewer')
+ useMediaViewerStore().closeMediaViewer()
}, transitionTime)
},
hideIfNotSwiped (event) {
@@ -98,7 +99,7 @@ const MediaModal = {
if (this.getType(newMedia) === 'image') {
this.loading = true
}
- this.$store.dispatch('setCurrentMedia', newMedia)
+ useMediaViewerStore().setCurrentMedia(newMedia)
}
},
goNext () {
@@ -108,7 +109,7 @@ const MediaModal = {
if (this.getType(newMedia) === 'image') {
this.loading = true
}
- this.$store.dispatch('setCurrentMedia', newMedia)
+ useMediaViewerStore().setCurrentMedia(newMedia)
}
},
onImageLoaded () {
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
@@ -8,6 +8,7 @@ import {
import GestureService from '../../services/gesture_service/gesture_service'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { mapGetters } from 'vuex'
+import { mapState } from 'pinia'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
@@ -17,6 +18,7 @@ import {
faMinus,
faCheckDouble
} from '@fortawesome/free-solid-svg-icons'
+import { useAnnouncementsStore } from 'src/stores/announcements'
library.add(
faTimes,
@@ -68,7 +70,8 @@ const MobileNav = {
isChat () {
return this.$route.name === 'chat'
},
- ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']),
+ ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
+ ...mapGetters(['unreadChatCount']),
chatsPinned () {
return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
},
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
@@ -3,6 +3,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faPen
} from '@fortawesome/free-solid-svg-icons'
+import { usePostStatusStore } from 'src/stores/postStatus'
library.add(
faPen
@@ -71,7 +72,7 @@ const MobilePostStatusButton = {
window.removeEventListener('scroll', this.handleScrollEnd)
},
openPostForm () {
- this.$store.dispatch('openPostStatusModal')
+ usePostStatusStore().openPostStatusModal()
},
handleOSK () {
// This is a big hack: we're guessing from changed window sizes if the
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
@@ -1,11 +1,13 @@
import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue'
import { mapState, mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js'
import { filterNavigation } from 'src/components/navigation/filter.js'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
+import { useAnnouncementsStore } from 'src/stores/announcements'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -90,13 +92,16 @@ const NavPanel = {
}
},
computed: {
+ ...mapPiniaState(useAnnouncementsStore, {
+ unreadAnnouncementCount: 'unreadAnnouncementCount',
+ supportsAnnouncements: store => store.supportsAnnouncements
+ }),
...mapState({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
- supportsAnnouncements: state => state.announcements.supportsAnnouncements,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems),
collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
@@ -131,7 +136,7 @@ const NavPanel = {
}
)
},
- ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
+ ...mapGetters(['unreadChatCount'])
}
}
diff --git a/src/components/navigation/filter.js b/src/components/navigation/filter.js
@@ -12,7 +12,7 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede
})
}
-export const getListEntries = state => state.lists.allLists.map(list => ({
+export const getListEntries = store => store.allLists.map(list => ({
name: 'list-' + list.id,
routeObject: { name: 'lists-timeline', params: { id: list.id } },
labelRaw: list.title,
diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js
@@ -84,6 +84,7 @@ export const ROOT_ITEMS = {
route: 'announcements',
icon: 'bullhorn',
label: 'nav.announcements',
+ store: 'announcements',
badgeStyle: 'notification',
badgeGetter: 'unreadAnnouncementCount',
criteria: ['announcements']
diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js
@@ -3,6 +3,8 @@ import { routeTo } from 'src/components/navigation/navigation.js'
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
+import { mapStores } from 'pinia'
+import { useAnnouncementsStore } from 'src/stores/announcements'
library.add(faThumbtack)
@@ -31,6 +33,7 @@ const NavigationEntry = {
getters () {
return this.$store.getters
},
+ ...mapStores(useAnnouncementsStore),
...mapState({
currentUser: state => state.users.currentUser,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue
@@ -53,6 +53,12 @@
>
{{ 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] }}
+ </div>
<button
v-if="showPin && currentUser"
type="button"
diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js
@@ -1,4 +1,5 @@
import { mapState } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js'
import { getBookmarkFolderEntries, getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
@@ -16,6 +17,8 @@ import {
faStream,
faList
} from '@fortawesome/free-solid-svg-icons'
+import { useListsStore } from 'src/stores/lists'
+import { useAnnouncementsStore } from 'src/stores/announcements'
library.add(
faUsers,
@@ -43,15 +46,19 @@ const NavPanel = {
getters () {
return this.$store.getters
},
+ ...mapPiniaState(useListsStore, {
+ lists: getListEntries
+ }),
+ ...mapPiniaState(useAnnouncementsStore, {
+ supportsAnnouncements: store => store.supportsAnnouncements
+ }),
...mapState({
- lists: getListEntries,
bookmarks: getBookmarkFolderEntries,
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
- supportsAnnouncements: state => state.announcements.supportsAnnouncements,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
}),
pinnedList () {
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
@@ -1,5 +1,6 @@
import { computed } from 'vue'
import { mapGetters } from 'vuex'
+import { mapState } from 'pinia'
import Notification from '../notification/notification.vue'
import ExtraNotifications from '../extra_notifications/extra_notifications.vue'
import NotificationFilters from './notification_filters.vue'
@@ -14,6 +15,8 @@ import {
import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useAnnouncementsStore } from 'src/stores/announcements'
library.add(
faCircleNotch,
@@ -98,11 +101,11 @@ const Notifications = {
return this.$store.state.notifications.loading
},
noHeading () {
- const { layoutType } = this.$store.state.interface
+ const { layoutType } = useInterfaceStore()
return this.minimalMode || layoutType === 'mobile'
},
teleportTarget () {
- const { layoutType } = this.$store.state.interface
+ const { layoutType } = useInterfaceStore()
const map = {
wide: '#notifs-column',
mobile: '#mobile-notifications'
@@ -110,7 +113,7 @@ const Notifications = {
return map[layoutType] || '#notifs-sidebar'
},
popoversZLayer () {
- const { layoutType } = this.$store.state.interface
+ const { layoutType } = useInterfaceStore()
return layoutType === 'mobile' ? 'navbar' : null
},
notificationsToDisplay () {
@@ -121,7 +124,8 @@ const Notifications = {
showExtraNotifications () {
return !this.noExtra
},
- ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
+ ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
+ ...mapGetters(['unreadChatCount'])
},
mounted () {
this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
@@ -141,10 +145,10 @@ const Notifications = {
unseenCountTitle (count) {
if (count > 0) {
FaviconService.drawFaviconBadge()
- this.$store.dispatch('setPageTitle', `(${count})`)
+ useInterfaceStore().setPageTitle(`(${count})`)
} else {
FaviconService.clearFaviconBadge()
- this.$store.dispatch('setPageTitle', '')
+ useInterfaceStore().setPageTitle('')
}
},
teleportTarget () {
diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js
@@ -2,6 +2,7 @@ import Timeago from 'components/timeago/timeago.vue'
import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import RichContent from 'components/rich_content/rich_content.jsx'
import { forEach, map } from 'lodash'
+import { usePollsStore } from 'src/stores/polls'
export default {
name: 'Poll',
@@ -18,20 +19,20 @@ export default {
}
},
created () {
- if (!this.$store.state.polls.pollsObject[this.pollId]) {
- this.$store.dispatch('mergeOrAddPoll', this.basePoll)
+ if (!usePollsStore().pollsObject[this.pollId]) {
+ usePollsStore().mergeOrAddPoll(this.basePoll)
}
- this.$store.dispatch('trackPoll', this.pollId)
+ usePollsStore().trackPoll(this.pollId)
},
unmounted () {
- this.$store.dispatch('untrackPoll', this.pollId)
+ usePollsStore().untrackPoll(this.pollId)
},
computed: {
pollId () {
return this.basePoll.id
},
poll () {
- const storePoll = this.$store.state.polls.pollsObject[this.pollId]
+ const storePoll = usePollsStore().pollsObject[this.pollId]
return storePoll || {}
},
options () {
@@ -77,9 +78,6 @@ export default {
resultTitle (option) {
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
},
- fetchPoll () {
- this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
- },
activateOption (index) {
// forgive me father: doing checking the radio/checkboxes
// in code because of customized input elements need either
@@ -107,8 +105,7 @@ export default {
vote () {
if (this.choiceIndices.length === 0) return
this.loading = true
- this.$store.dispatch(
- 'votePoll',
+ usePollsStore().votePoll(
{ id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices }
).then(poll => {
this.loading = false
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
@@ -14,7 +14,8 @@ import { propsToNative } from '../../services/attributes_helper/attributes_helpe
import { pollFormToMasto } from 'src/services/poll/poll.service.js'
import { reject, map, uniqBy, debounce } from 'lodash'
import suggestor from '../emoji_input/suggestor.js'
-import { mapGetters, mapState } from 'vuex'
+import { mapGetters } from 'vuex'
+import { mapState, mapActions } from 'pinia'
import Checkbox from '../checkbox/checkbox.vue'
import Select from '../select/select.vue'
import DraftCloser from 'src/components/draft_closer/draft_closer.vue'
@@ -32,6 +33,9 @@ import {
faChevronRight
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface.js'
+import { useMediaViewerStore } from 'src/stores/media_viewer.js'
+
library.add(
faSmileBeam,
faPollH,
@@ -367,8 +371,8 @@ const PostStatusForm = {
) && this.saveable
},
...mapGetters(['mergedConfig']),
- ...mapState({
- mobileLayout: state => state.interface.mobileLayout
+ ...mapState(useInterfaceStore, {
+ mobileLayout: store => store.mobileLayout
})
},
watch: {
@@ -393,6 +397,7 @@ const PostStatusForm = {
this.removeBeforeUnloadListener()
},
methods: {
+ ...mapActions(useMediaViewerStore, ['increment']),
statusChanged () {
this.autoPreview()
this.updateIdempotencyKey()
@@ -753,7 +758,7 @@ const PostStatusForm = {
this.idempotencyKey = Date.now().toString()
},
openProfileTab () {
- this.$store.dispatch('openSettingsModalTab', 'profile')
+ useInterfaceStore().openSettingsModalTab('profile')
},
propsToNative (props) {
return propsToNative(props)
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
@@ -386,7 +386,7 @@
:nsfw="false"
:attachments="newStatus.files"
:descriptions="newStatus.mediaDescriptions"
- :set-media="() => $store.dispatch('setMedia', newStatus.files)"
+ :set-media="() => setMedia()"
:editable="true"
:edit-attachment="editAttachment"
:remove-attachment="removeMediaFile"
diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js
@@ -1,6 +1,7 @@
import PostStatusForm from '../post_status_form/post_status_form.vue'
import Modal from '../modal/modal.vue'
import get from 'lodash/get'
+import { usePostStatusStore } from 'src/stores/postStatus'
const PostStatusModal = {
components: {
@@ -17,13 +18,13 @@ const PostStatusModal = {
return !!this.$store.state.users.currentUser
},
modalActivated () {
- return this.$store.state.postStatus.modalActivated
+ return usePostStatusStore().modalActivated
},
isFormVisible () {
return this.isLoggedIn && !this.resettingForm && this.modalActivated
},
params () {
- return this.$store.state.postStatus.params || {}
+ return usePostStatusStore().params || {}
}
},
watch: {
@@ -43,11 +44,11 @@ const PostStatusModal = {
},
methods: {
closeModal () {
- this.$store.dispatch('closePostStatusModal')
+ usePostStatusStore().closePostStatusModal()
},
resetAndClose () {
- this.$store.dispatch('resetPostStatusModal')
- this.$store.dispatch('closePostStatusModal')
+ usePostStatusStore().resetPostStatusModal()
+ usePostStatusStore().closePostStatusModal()
}
}
}
diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js
@@ -1,7 +1,9 @@
import Popover from '../popover/popover.vue'
-import { mapGetters, mapState } from 'vuex'
+import { mapGetters } from 'vuex'
+import { mapState } from 'pinia'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faFilter,
@@ -23,13 +25,13 @@ const QuickFilterSettings = {
this.$store.dispatch('queueFlushAll')
},
openTab (tab) {
- this.$store.dispatch('openSettingsModalTab', tab)
+ useInterfaceStore().openSettingsModalTab(tab)
}
},
computed: {
...mapGetters(['mergedConfig']),
- ...mapState({
- mobileLayout: state => state.interface.layoutType === 'mobile'
+ ...mapState(useInterfaceStore, {
+ mobileLayout: state => state.layoutType === 'mobile'
}),
triggerAttrs () {
if (this.mobileLayout) {
diff --git a/src/components/quick_view_settings/quick_view_settings.js b/src/components/quick_view_settings/quick_view_settings.js
@@ -1,8 +1,10 @@
import Popover from 'src/components/popover/popover.vue'
import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue'
-import { mapGetters, mapState } from 'vuex'
+import { mapGetters } from 'vuex'
+import { mapState } from 'pinia'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faList, faFolderTree, faBars, faWrench } from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faList,
@@ -24,13 +26,13 @@ const QuickViewSettings = {
this.$store.dispatch('setOption', { name: 'conversationDisplay', value: visibility })
},
openTab (tab) {
- this.$store.dispatch('openSettingsModalTab', tab)
+ useInterfaceStore().openSettingsModalTab(tab)
}
},
computed: {
...mapGetters(['mergedConfig']),
- ...mapState({
- mobileLayout: state => state.interface.layoutType === 'mobile'
+ ...mapState(useInterfaceStore, {
+ mobileLayout: state => state.layoutType === 'mobile'
}),
loggedIn () {
return !!this.$store.state.users.currentUser
diff --git a/src/components/report/report.js b/src/components/report/report.js
@@ -1,3 +1,4 @@
+import { useReportsStore } from 'src/stores/reports'
import Select from '../select/select.vue'
import StatusContent from '../status_content/status_content.vue'
import Timeago from '../timeago/timeago.vue'
@@ -16,7 +17,7 @@ const Report = {
},
computed: {
report () {
- return this.$store.state.reports.reports[this.reportId] || {}
+ return useReportsStore().reports[this.reportId] || {}
},
state: {
get: function () { return this.report.state },
@@ -28,7 +29,7 @@ const Report = {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
setReportState (state) {
- return this.$store.dispatch('setReportState', { id: this.report.id, state })
+ return useReportsStore().setReportState({ id: this.report.id, state })
}
}
}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -232,7 +232,7 @@ const EmojiTab = {
})
},
displayError (msg) {
- this.$store.dispatch('pushGlobalNotice', {
+ this.$store.useInterfaceStore().pushGlobalNotice({
messageKey: 'admin_dash.emoji.error',
messageArgs: [msg],
level: 'error'
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.js b/src/components/settings_modal/admin_tabs/frontends_tab.js
@@ -80,7 +80,7 @@ const FrontendsTab = {
this.$store.dispatch('loadFrontendsStuff')
if (response.error) {
const reason = await response.error.json()
- this.$store.dispatch('pushGlobalNotice', {
+ this.$store.useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'admin_dash.frontend.failure_installing_frontend',
messageArgs: {
@@ -90,7 +90,7 @@ const FrontendsTab = {
timeout: 5000
})
} else {
- this.$store.dispatch('pushGlobalNotice', {
+ this.$store.useInterfaceStore().pushGlobalNotice({
level: 'success',
messageKey: 'admin_dash.frontend.success_installing_frontend',
messageArgs: {
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
@@ -7,6 +7,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { cloneDeep, isEqual } from 'lodash'
+import { mapState as mapPiniaState } from 'pinia'
import {
newImporter,
newExporter
@@ -20,6 +21,7 @@ import {
import {
faWindowMinimize
} from '@fortawesome/free-regular-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
const PLEROMAFE_SETTINGS_MAJOR_VERSION = 1
const PLEROMAFE_SETTINGS_MINOR_VERSION = 0
@@ -74,10 +76,10 @@ const SettingsModal = {
},
methods: {
closeModal () {
- this.$store.dispatch('closeSettingsModal')
+ useInterfaceStore().closeSettingsModal()
},
peekModal () {
- this.$store.dispatch('togglePeekSettingsModal')
+ useInterfaceStore().togglePeekSettingsModal()
},
importValidator (data) {
if (!Array.isArray(data._pleroma_settings_version)) {
@@ -109,7 +111,7 @@ const SettingsModal = {
}
if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) {
- this.$store.dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
level: 'warning',
messageKey: 'settings.file_export_import.errors.file_slightly_new'
})
@@ -119,9 +121,9 @@ const SettingsModal = {
},
onImportFailure (result) {
if (result.error) {
- this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_settings_imported', level: 'error' })
+ useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_settings_imported', level: 'error' })
} else {
- this.$store.dispatch('pushGlobalNotice', { ...result.validationResult, level: 'error' })
+ useInterfaceStore().pushGlobalNotice({ ...result.validationResult, level: 'error' })
}
},
onImport (data) {
@@ -166,24 +168,15 @@ const SettingsModal = {
}
},
computed: {
- currentSaveStateNotice () {
- return this.$store.state.interface.settings.currentSaveStateNotice
- },
- modalActivated () {
- return this.$store.state.interface.settingsModalState !== 'hidden'
- },
- modalMode () {
- return this.$store.state.interface.settingsModalMode
- },
- modalOpenedOnceUser () {
- return this.$store.state.interface.settingsModalLoadedUser
- },
- modalOpenedOnceAdmin () {
- return this.$store.state.interface.settingsModalLoadedAdmin
- },
- modalPeeked () {
- return this.$store.state.interface.settingsModalState === 'minimized'
- },
+ ...mapPiniaState(useInterfaceStore, {
+ temporaryChangesTimeoutId: store => store.layoutType === 'mobile',
+ currentSaveStateNotice: store => store.settings.currentSaveStateNotice,
+ modalActivated: store => store.settingsModalState !== 'hidden',
+ modalMode: store => store.settingsModalMode,
+ modalOpenedOnceUser: store => store.settingsModalLoadedUser,
+ modalOpenedOnceAdmin: store => store.settingsModalLoadedAdmin,
+ modalPeeked: store => store.settingsModalState === 'minimized'
+ }),
expertLevel: {
get () {
return this.$store.state.config.expertLevel > 0
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
@@ -158,7 +158,7 @@
</div>
<teleport to="#modal">
<ConfirmModal
- v-if="$store.state.interface.temporaryChangesTimeoutId"
+ v-if="temporaryChangesTimeoutId"
:title="$t('settings.confirm_new_setting')"
:cancel-text="$t('settings.revert')"
:confirm-text="$t('settings.confirm')"
diff --git a/src/components/settings_modal/settings_modal_admin_content.js b/src/components/settings_modal/settings_modal_admin_content.js
@@ -4,6 +4,7 @@ import InstanceTab from './admin_tabs/instance_tab.vue'
import LimitsTab from './admin_tabs/limits_tab.vue'
import FrontendsTab from './admin_tabs/frontends_tab.vue'
import EmojiTab from './admin_tabs/emoji_tab.vue'
+import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -45,10 +46,10 @@ const SettingsModalAdminContent = {
return !!this.$store.state.users.currentUser
},
open () {
- return this.$store.state.interface.settingsModalState !== 'hidden'
+ return useInterfaceStore().settingsModalState !== 'hidden'
},
bodyLock () {
- return this.$store.state.interface.settingsModalState === 'visible'
+ return useInterfaceStore().settingsModalState === 'visible'
},
adminDbLoaded () {
return this.$store.state.adminSettings.loaded
@@ -67,7 +68,7 @@ const SettingsModalAdminContent = {
},
methods: {
onOpen () {
- const targetTab = this.$store.state.interface.settingsModalTargetTab
+ const targetTab = useInterfaceStore().settingsModalTargetTab
// We're being told to open in specific tab
if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
@@ -79,7 +80,7 @@ const SettingsModalAdminContent = {
}
// Clear the state of target tab, so that next time settings is opened
// it doesn't force it.
- this.$store.dispatch('clearSettingsModalTargetTab')
+ useInterfaceStore().clearSettingsModalTargetTab()
}
},
mounted () {
diff --git a/src/components/settings_modal/settings_modal_user_content.js b/src/components/settings_modal/settings_modal_user_content.js
@@ -25,6 +25,7 @@ import {
faInfo,
faWindowRestore
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faWrench,
@@ -60,21 +61,21 @@ const SettingsModalContent = {
return !!this.$store.state.users.currentUser
},
open () {
- return this.$store.state.interface.settingsModalState !== 'hidden'
+ return useInterfaceStore().settingsModalState !== 'hidden'
},
bodyLock () {
- return this.$store.state.interface.settingsModalState === 'visible'
+ return useInterfaceStore().settingsModalState === 'visible'
},
expertLevel () {
return this.$store.state.config.expertLevel
},
isMobileLayout () {
- return this.$store.state.interface.layoutType === 'mobile'
+ return useInterfaceStore().layoutType === 'mobile'
}
},
methods: {
onOpen () {
- const targetTab = this.$store.state.interface.settingsModalTargetTab
+ const targetTab = useInterfaceStore().settingsModalTargetTab
// We're being told to open in specific tab
if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
@@ -86,7 +87,7 @@ const SettingsModalContent = {
}
// Clear the state of target tab, so that next time settings is opened
// it doesn't force it.
- this.$store.dispatch('clearSettingsModalTargetTab')
+ useInterfaceStore().clearSettingsModalTargetTab()
}
},
mounted () {
diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js
@@ -4,11 +4,9 @@ import IntegerSetting from '../helpers/integer_setting.vue'
import FloatSetting from '../helpers/float_setting.vue'
import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue'
import PaletteEditor from 'src/components/palette_editor/palette_editor.vue'
-
+import Preview from './theme_tab/theme_preview.vue'
import FontControl from 'src/components/font_control/font_control.vue'
-import { normalizeThemeData } from 'src/modules/interface'
-
import { newImporter } from 'src/services/export_import/export_import.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js'
@@ -20,17 +18,15 @@ import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
+
+import { mapActions } from 'pinia'
+import { useInterfaceStore, normalizeThemeData } from 'src/stores/interface'
+
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faGlobe
} from '@fortawesome/free-solid-svg-icons'
-import Preview from './theme_tab/theme_preview.vue'
-
-// helper for debugging
-// eslint-disable-next-line no-unused-vars
-const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
-
library.add(
faGlobe
)
@@ -90,7 +86,7 @@ const AppearanceTab = {
PaletteEditor
},
mounted () {
- this.$store.dispatch('getThemeData')
+ useInterfaceStore().getThemeData()
const updateIndex = (resource) => {
const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
@@ -100,7 +96,7 @@ const AppearanceTab = {
if (currentIndex) {
promise = Promise.resolve(currentIndex)
} else {
- promise = this.$store.dispatch(`fetch${capitalizedResource}sIndex`)
+ promise = useInterfaceStore()[`fetch${capitalizedResource}sIndex`]()
}
return promise.then(index => {
@@ -131,7 +127,7 @@ const AppearanceTab = {
}))
})
- this.userPalette = this.$store.state.interface.paletteDataUsed || {}
+ this.userPalette = useInterfaceStore().paletteDataUsed || {}
updateIndex('palette').then(bundledPalettes => {
bundledPalettes.forEach(([key, palettePromise]) => palettePromise.then(v => {
@@ -187,10 +183,10 @@ const AppearanceTab = {
},
computed: {
switchInProgress () {
- return this.$store.state.interface.themeChangeInProgress
+ return useInterfaceStore().themeChangeInProgress
},
paletteDataUsed () {
- return this.$store.state.interface.paletteDataUsed
+ return useInterfaceStore().paletteDataUsed
},
availableStyles () {
return [
@@ -205,7 +201,7 @@ const AppearanceTab = {
]
},
stylePalettes () {
- const ruleset = this.$store.state.interface.styleDataUsed || []
+ const ruleset = useInterfaceStore().styleDataUsed || []
if (!ruleset && ruleset.length === 0) return
const meta = ruleset.find(x => x.component === '@meta')
const result = ruleset.filter(x => x.component.startsWith('@palette'))
@@ -273,7 +269,7 @@ const AppearanceTab = {
}
},
customThemeVersion () {
- const { themeVersion } = this.$store.state.interface
+ const { themeVersion } = useInterfaceStore()
return themeVersion
},
isCustomThemeUsed () {
@@ -311,14 +307,14 @@ const AppearanceTab = {
},
onImport (parsed, filename) {
if (filename.endsWith('.json')) {
- this.$store.dispatch('setThemeCustom', parsed.source || parsed.theme)
+ useInterfaceStore().setThemeCustom(parsed.source || parsed.theme)
} else if (filename.endsWith('.iss')) {
- this.$store.dispatch('setStyleCustom', parsed)
+ useInterfaceStore().setStyleCustom(parsed)
}
},
onImportFailure (result) {
console.error('Failure importing theme:', result)
- this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
+ this.$store.useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
},
importValidator (parsed, filename) {
if (filename.endsWith('.json')) {
@@ -340,22 +336,20 @@ const AppearanceTab = {
isPaletteActive (key) {
return key === (this.mergedConfig.palette || this.$store.state.instance.palette)
},
- setStyle (name) {
- this.$store.dispatch('setStyle', name)
- },
- setTheme (name) {
- this.$store.dispatch('setTheme', name)
- },
+ ...mapActions(useInterfaceStore, [
+ 'setStyle',
+ 'setTheme'
+ ]),
setPalette (name, data) {
- this.$store.dispatch('setPalette', name)
+ useInterfaceStore().setPalette(name)
this.userPalette = data
},
setPaletteCustom (data) {
- this.$store.dispatch('setPaletteCustom', data)
+ useInterfaceStore().setPaletteCustom(data)
this.userPalette = data
},
resetTheming (name) {
- this.$store.dispatch('setStyle', 'stock')
+ useInterfaceStore().setStyle('stock')
},
previewTheme (key, version, input) {
let theme3
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
@@ -21,6 +21,7 @@ import {
faPlus,
faCircleNotch
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faTimes,
@@ -175,7 +176,7 @@ const ProfileTab = {
if (file.size > this.$store.state.instance[slot + 'limit']) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
- this.$store.dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
messageKey: 'upload.error.message',
messageArgs: [
this.$t('upload.error.file_too_big', {
@@ -266,7 +267,7 @@ const ProfileTab = {
.finally(() => { this.backgroundUploading = false })
},
displayUploadError (error) {
- this.$store.dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
messageKey: 'upload.error.message',
messageArgs: [error.message],
level: 'error'
diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js
@@ -1,5 +1,5 @@
import { ref, reactive, computed, watch, watchEffect, provide, getCurrentInstance } from 'vue'
-import { useStore } from 'vuex'
+import { useInterfaceStore } from 'src/stores/interface'
import { get, set, unset, throttle } from 'lodash'
import Select from 'src/components/select/select.vue'
@@ -80,13 +80,13 @@ export default {
},
setup (props, context) {
const exports = {}
- const store = useStore()
+ const interfaceStore = useInterfaceStore()
// All rules that are made by editor
- const allEditedRules = ref(store.state.interface.styleDataUsed || {})
- const styleDataUsed = computed(() => store.state.interface.styleDataUsed)
+ const allEditedRules = ref(interfaceStore.styleDataUsed || {})
+ const styleDataUsed = computed(() => interfaceStore.styleDataUsed)
watch([styleDataUsed], (value) => {
- onImport(store.state.interface.styleDataUsed)
+ onImport(interfaceStore.styleDataUsed)
}, { once: true })
exports.isActive = computed(() => {
@@ -640,7 +640,7 @@ export default {
parser (string) { return deserialize(string) },
onImportFailure (result) {
console.error('Failure importing style:', result)
- this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
+ this.$store.useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
},
onImport
})
@@ -664,7 +664,7 @@ export default {
})
exports.clearStyle = () => {
- onImport(store.state.interface.styleDataUsed)
+ onImport(interfaceStore().styleDataUsed)
}
exports.exportStyle = () => {
@@ -676,7 +676,7 @@ export default {
}
exports.applyStyle = () => {
- store.dispatch('setStyleCustom', exportRules.value)
+ useInterfaceStore().setStyleCustom(exportRules.value)
}
const overallPreviewRules = ref([])
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -43,6 +43,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
import Select from 'src/components/select/select.vue'
import Preview from './theme_preview.vue'
+import { useInterfaceStore } from 'src/stores/interface'
// List of color values used in v1
const v1OnlyNames = [
@@ -126,7 +127,7 @@ export default {
if (currentIndex) {
promise = Promise.resolve(currentIndex)
} else {
- promise = this.$store.dispatch('fetchThemesIndex')
+ promise = useInterfaceStore().fetchThemesIndex()
}
promise.then(themesIndex => {
@@ -296,7 +297,7 @@ export default {
}
},
themeDataUsed () {
- return this.$store.state.interface.themeDataUsed
+ return useInterfaceStore().themeDataUsed
},
shadowsAvailable () {
return Object.keys(DEFAULT_SHADOWS).sort()
@@ -492,7 +493,7 @@ export default {
}
},
setCustomTheme () {
- this.$store.dispatch('setThemeV2', {
+ useInterfaceStore().setThemeV2({
customTheme: {
ignore: true,
themeFileVersion: this.selectedVersion,
@@ -536,7 +537,7 @@ export default {
this.loadTheme(parsed, 'file', forceSource)
},
onImportFailure (result) {
- this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
+ useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
},
importValidator (parsed) {
const version = parsed._pleroma_theme_version
diff --git a/src/components/shout_panel/shout_panel.js b/src/components/shout_panel/shout_panel.js
@@ -4,6 +4,7 @@ import {
faBullhorn,
faTimes
} from '@fortawesome/free-solid-svg-icons'
+import { useShoutStore } from 'src/stores/shout'
library.add(
faBullhorn,
@@ -21,12 +22,12 @@ const shoutPanel = {
},
computed: {
messages () {
- return this.$store.state.shout.messages
+ return useShoutStore().messages
}
},
methods: {
submit (message) {
- this.$store.state.shout.channel.push('new_msg', { text: message }, 10000)
+ useShoutStore().channel.push('new_msg', { text: message }, 10000)
this.currentMessage = ''
},
togglePanel () {
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
@@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
import UserCard from '../user_card/user_card.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
@@ -20,6 +21,9 @@ import {
faList,
faFilePen
} from '@fortawesome/free-solid-svg-icons'
+import { useShoutStore } from 'src/stores/shout'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useAnnouncementsStore } from 'src/stores/announcements'
library.add(
faSignInAlt,
@@ -56,7 +60,7 @@ const SideDrawer = {
currentUser () {
return this.$store.state.users.currentUser
},
- shout () { return this.$store.state.shout.joined },
+ shout () { return useShoutStore().joined },
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},
@@ -86,8 +90,8 @@ const SideDrawer = {
},
timelinesRoute () {
let name
- if (this.$store.state.interface.lastTimeline) {
- name = this.$store.state.interface.lastTimeline
+ if (useInterfaceStore().lastTimeline) {
+ name = useInterfaceStore().lastTimeline
}
name = this.currentUser ? 'friends' : 'public-timeline'
if (USERNAME_ROUTES.has(name)) {
@@ -96,11 +100,14 @@ const SideDrawer = {
return { name }
}
},
+ ...mapPiniaState(useAnnouncementsStore, {
+ supportsAnnouncements: store => store.supportsAnnouncements,
+ unreadAnnouncementCount: 'unreadAnnouncementCount'
+ }),
...mapState({
- pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
- supportsAnnouncements: state => state.announcements.supportsAnnouncements
+ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
}),
- ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'draftCount'])
+ ...mapGetters(['unreadChatCount', 'draftCount'])
},
methods: {
toggleDrawer () {
@@ -117,10 +124,10 @@ const SideDrawer = {
GestureService.updateSwipe(e, this.closeGesture)
},
openSettingsModal () {
- this.$store.dispatch('openSettingsModal', 'user')
+ useInterfaceStore().openSettingsModal('user')
},
openAdminModal () {
- this.$store.dispatch('openSettingsModal', 'admin')
+ useInterfaceStore().openSettingsModal('admin')
}
}
}
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
@@ -13,6 +13,7 @@ import {
faLink,
faPollH
} from '@fortawesome/free-solid-svg-icons'
+import { useMediaViewerStore } from 'src/stores/media_viewer'
library.add(
faCircleNotch,
@@ -130,7 +131,7 @@ const StatusContent = {
},
setMedia () {
const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
- return () => this.$store.dispatch('setMedia', attachments)
+ return () => useMediaViewerStore().setMedia(attachments)
}
}
}
diff --git a/src/components/status_history_modal/status_history_modal.js b/src/components/status_history_modal/status_history_modal.js
@@ -1,6 +1,7 @@
import { get } from 'lodash'
import Modal from '../modal/modal.vue'
import Status from '../status/status.vue'
+import { useStatusHistoryStore } from 'src/stores/statusHistory'
const StatusHistoryModal = {
components: {
@@ -14,10 +15,10 @@ const StatusHistoryModal = {
},
computed: {
modalActivated () {
- return this.$store.state.statusHistory.modalActivated
+ return useStatusHistoryStore().modalActivated
},
params () {
- return this.$store.state.statusHistory.params
+ return useStatusHistoryStore().params
},
statusId () {
return this.params.id
@@ -52,7 +53,7 @@ const StatusHistoryModal = {
})
},
closeModal () {
- this.$store.dispatch('closeStatusHistoryModal')
+ useStatusHistoryStore().closeStatusHistoryModal()
}
}
}
diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx
@@ -1,9 +1,10 @@
// eslint-disable-next-line no-unused
import { h, Fragment } from 'vue'
-import { mapState } from 'vuex'
+import { mapState } from 'pinia'
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
import './tab_switcher.scss'
+import { useInterfaceStore } from 'src/stores/interface'
const findFirstUsable = (slots) => slots.findIndex(_ => _.props)
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
@@ -1,5 +1,5 @@
import Status from '../status/status.vue'
-import { mapState } from 'vuex'
+import { mapState } from 'pinia'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import Conversation from '../conversation/conversation.vue'
import TimelineMenu from '../timeline_menu/timeline_menu.vue'
@@ -8,6 +8,7 @@ import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
import { debounce, throttle, keyBy } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
library.add(
faCircleNotch,
@@ -103,8 +104,8 @@ const Timeline = {
virtualScrollingEnabled () {
return this.$store.getters.mergedConfig.virtualScrolling
},
- ...mapState({
- mobileLayout: state => state.interface.layoutType === 'mobile'
+ ...mapState(useInterfaceStore, {
+ mobileLayout: store => store.layoutType === 'mobile'
})
},
created () {
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
@@ -9,6 +9,8 @@ import { filterNavigation } from 'src/components/navigation/filter.js'
import {
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
+import { useInterfaceStore } from 'src/stores/interface'
+import { useListsStore } from 'src/stores/lists'
library.add(faChevronDown)
@@ -39,7 +41,7 @@ const TimelineMenu = {
},
created () {
if (timelineNames(this.bookmarkFolders)[this.$route.name]) {
- this.$store.dispatch('setLastTimeline', this.$route.name)
+ useInterfaceStore().setLastTimeline(this.$route.name)
}
},
computed: {
@@ -95,7 +97,7 @@ const TimelineMenu = {
return '#' + this.$route.params.tag
}
if (route === 'lists-timeline') {
- return this.$store.getters.findListTitle(this.$route.params.id)
+ return useListsStore().findListTitle(this.$route.params.id)
}
if (route === 'bookmark-folder') {
return this.$store.getters.findBookmarkFolderName(this.$route.params.id)
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
@@ -1,4 +1,5 @@
import StillImage from '../still-image/still-image.vue'
+import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -22,7 +23,7 @@ const UserAvatar = {
return {
showPlaceholder: false,
defaultAvatar: `${this.$store.state.instance.server + this.$store.state.instance.defaultAvatar}`,
- betterShadow: this.$store.state.interface.browserSupport.cssFilter
+ betterShadow: useInterfaceStore().browserSupport.cssFilter
}
},
components: {
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
@@ -11,6 +11,7 @@ import RichContent from 'src/components/rich_content/rich_content.jsx'
import MuteConfirm from '../confirm_modal/mute_confirm.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex'
+import { usePostStatusStore } from 'src/stores/postStatus'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBell,
@@ -22,6 +23,9 @@ import {
faExpandAlt
} from '@fortawesome/free-solid-svg-icons'
+import { useMediaViewerStore } from '../../stores/media_viewer'
+import { useInterfaceStore } from '../../stores/interface'
+
library.add(
faRss,
faBell,
@@ -188,18 +192,18 @@ export default {
)
},
openProfileTab () {
- this.$store.dispatch('openSettingsModalTab', 'profile')
+ useInterfaceStore().openSettingsModalTab('profile')
},
zoomAvatar () {
const attachment = {
url: this.user.profile_image_url_original,
mimetype: 'image'
}
- this.$store.dispatch('setMedia', [attachment])
- this.$store.dispatch('setCurrentMedia', attachment)
+ useMediaViewerStore().setMedia([attachment])
+ useMediaViewerStore().setCurrentMedia(attachment)
},
mentionUser () {
- this.$store.dispatch('openPostStatusModal', { profileMention: true, repliedUser: this.user })
+ usePostStatusStore().openPostStatusModal({ profileMention: true, repliedUser: this.user })
},
onAvatarClickHandler (e) {
if (this.onAvatarClick) {
diff --git a/src/components/user_list_menu/user_list_menu.js b/src/components/user_list_menu/user_list_menu.js
@@ -1,9 +1,10 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
-import { mapState } from 'vuex'
+import { mapState } from 'pinia'
import DialogModal from '../dialog_modal/dialog_modal.vue'
import Popover from '../popover/popover.vue'
+import { useListsStore } from 'src/stores/lists'
library.add(faChevronRight)
@@ -22,8 +23,8 @@ const UserListMenu = {
this.$store.dispatch('fetchUserInLists', this.user.id)
},
computed: {
- ...mapState({
- allLists: state => state.lists.allLists
+ ...mapState(useListsStore, {
+ allLists: store => store.allLists
}),
inListsSet () {
return new Set(this.user.inLists.map(x => x.id))
@@ -44,12 +45,12 @@ const UserListMenu = {
methods: {
toggleList (listId) {
if (this.inListsSet.has(listId)) {
- this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId }).then((response) => {
+ useListsStore().removeListAccount({ accountId: this.user.id, listId }).then((response) => {
if (!response.ok) { return }
this.$store.dispatch('fetchUserInLists', this.user.id)
})
} else {
- this.$store.dispatch('addListAccount', { accountId: this.user.id, listId }).then((response) => {
+ useListsStore().addListAccount({ accountId: this.user.id, listId }).then((response) => {
if (!response.ok) { return }
this.$store.dispatch('fetchUserInLists', this.user.id)
})
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -3,6 +3,7 @@ import List from '../list/list.vue'
import Checkbox from '../checkbox/checkbox.vue'
import Modal from '../modal/modal.vue'
import UserLink from '../user_link/user_link.vue'
+import { useReportsStore } from 'src/stores/reports'
const UserReportingModal = {
components: {
@@ -23,7 +24,7 @@ const UserReportingModal = {
},
computed: {
reportModal () {
- return this.$store.state.reports.reportModal
+ return useReportsStore().reportModal
},
isLoggedIn () {
return !!this.$store.state.users.currentUser
@@ -63,7 +64,7 @@ const UserReportingModal = {
this.error = false
},
closeModal () {
- this.$store.dispatch('closeUserReportingModal')
+ useReportsStore().closeUserReportingModal()
},
reportUser () {
this.processing = true
diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js
@@ -1,5 +1,6 @@
import merge from 'lodash.merge'
import { each, get, set, cloneDeep } from 'lodash'
+import { useInterfaceStore } from 'src/stores/interface'
import { storage } from './storage.js'
let loaded = false
@@ -76,12 +77,12 @@ export default function createPersistedState ({
.then(success => {
if (typeof success !== 'undefined') {
if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') {
- store.dispatch('settingsSaved', { success })
+ useInterfaceStore().settingsSaved({ success })
}
}
}, error => {
if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') {
- store.dispatch('settingsSaved', { error })
+ useInterfaceStore().settingsSaved({ error })
}
})
}
diff --git a/src/lib/push_notifications_plugin.js b/src/lib/push_notifications_plugin.js
@@ -1,8 +1,10 @@
+import { useInterfaceStore } from 'src/stores/interface'
+
export default (store) => {
store.subscribe((mutation, state) => {
const vapidPublicKey = state.instance.vapidPublicKey
const webPushNotification = state.config.webPushNotifications
- const permission = state.interface.notificationPermission === 'granted'
+ const permission = useInterfaceStore().notificationPermission === 'granted'
const user = state.users.currentUser
const isUserMutation = mutation.type === 'setCurrentUser'
diff --git a/src/main.js b/src/main.js
@@ -1,32 +1,23 @@
import { createStore } from 'vuex'
+import { createPinia } from 'pinia'
import 'custom-event-polyfill'
import './lib/event_target_polyfill.js'
-import interfaceModule from './modules/interface.js'
import instanceModule from './modules/instance.js'
import statusesModule from './modules/statuses.js'
import notificationsModule from './modules/notifications.js'
-import listsModule from './modules/lists.js'
import usersModule from './modules/users.js'
import apiModule from './modules/api.js'
import configModule from './modules/config.js'
import profileConfigModule from './modules/profileConfig.js'
import serverSideStorageModule from './modules/serverSideStorage.js'
import adminSettingsModule from './modules/adminSettings.js'
-import shoutModule from './modules/shout.js'
import oauthModule from './modules/oauth.js'
import authFlowModule from './modules/auth_flow.js'
-import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js'
-import reportsModule from './modules/reports.js'
-import pollsModule from './modules/polls.js'
-import postStatusModule from './modules/postStatus.js'
-import editStatusModule from './modules/editStatus.js'
-import statusHistoryModule from './modules/statusHistory.js'
import draftsModule from './modules/drafts.js'
import chatsModule from './modules/chats.js'
-import announcementsModule from './modules/announcements.js'
import bookmarkFoldersModule from './modules/bookmark_folders.js'
import { createI18n } from 'vue-i18n'
@@ -85,6 +76,7 @@ const persistedStateOptions = {
try {
let storageError
const plugins = [pushNotifications]
+ const pinia = createPinia()
try {
const persistedState = await createPersistedState(persistedStateOptions)
plugins.push(persistedState)
@@ -98,36 +90,21 @@ const persistedStateOptions = {
document.querySelector('#splash-credit').textContent = i18n.global.t('update.art_by', { linkToArtist: 'pipivovott' })
const store = createStore({
modules: {
- i18n: {
- getters: {
- i18n: () => i18n.global
- }
- },
- interface: interfaceModule,
instance: instanceModule,
// TODO refactor users/statuses modules, they depend on each other
users: usersModule,
statuses: statusesModule,
notifications: notificationsModule,
- lists: listsModule,
api: apiModule,
config: configModule,
profileConfig: profileConfigModule,
serverSideStorage: serverSideStorageModule,
adminSettings: adminSettingsModule,
- shout: shoutModule,
oauth: oauthModule,
authFlow: authFlowModule,
- mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule,
- reports: reportsModule,
- polls: pollsModule,
- postStatus: postStatusModule,
- editStatus: editStatusModule,
- statusHistory: statusHistoryModule,
drafts: draftsModule,
chats: chatsModule,
- announcements: announcementsModule,
bookmarkFolders: bookmarkFoldersModule
},
plugins,
@@ -137,10 +114,9 @@ const persistedStateOptions = {
strict: false // Socket modifies itself, let's ignore this for now.
// strict: process.env.NODE_ENV !== 'production'
})
- if (storageError) {
- store.dispatch('pushGlobalNotice', { messageKey: 'errors.storage_unavailable', level: 'error' })
- }
- return await afterStoreSetup({ store, i18n })
+ window.vuex = store
+ // Temporarily passing pinia and vuex stores along with storageError result until migration is fully complete.
+ return await afterStoreSetup({ pinia, store, storageError, i18n })
} catch (e) {
splashError(i18n, e)
}
diff --git a/src/modules/announcements.js b/src/modules/announcements.js
@@ -1,135 +0,0 @@
-const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
-
-export const defaultState = {
- announcements: [],
- supportsAnnouncements: true,
- fetchAnnouncementsTimer: undefined
-}
-
-export const mutations = {
- setAnnouncements (state, announcements) {
- state.announcements = announcements
- },
- setAnnouncementRead (state, { id, read }) {
- const index = state.announcements.findIndex(a => a.id === id)
-
- if (index < 0) {
- return
- }
-
- state.announcements[index].read = read
- },
- setFetchAnnouncementsTimer (state, timer) {
- state.fetchAnnouncementsTimer = timer
- },
- setSupportsAnnouncements (state, supportsAnnouncements) {
- state.supportsAnnouncements = supportsAnnouncements
- }
-}
-
-export const getters = {
- unreadAnnouncementCount (state, _getters, rootState) {
- if (!rootState.users.currentUser) {
- return 0
- }
-
- const unread = state.announcements.filter(announcement => !(announcement.inactive || announcement.read))
- return unread.length
- }
-}
-
-const announcements = {
- state: defaultState,
- mutations,
- getters,
- actions: {
- fetchAnnouncements (store) {
- if (!store.state.supportsAnnouncements) {
- return Promise.resolve()
- }
-
- const currentUser = store.rootState.users.currentUser
- const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
-
- const getAnnouncements = async () => {
- if (!isAdmin) {
- return store.rootState.api.backendInteractor.fetchAnnouncements()
- }
-
- const all = await store.rootState.api.backendInteractor.adminFetchAnnouncements()
- const visible = await store.rootState.api.backendInteractor.fetchAnnouncements()
- const visibleObject = visible.reduce((a, c) => {
- a[c.id] = c
- return a
- }, {})
- const getWithinVisible = announcement => visibleObject[announcement.id]
-
- all.forEach(announcement => {
- const visibleAnnouncement = getWithinVisible(announcement)
- if (!visibleAnnouncement) {
- announcement.inactive = true
- } else {
- announcement.read = visibleAnnouncement.read
- }
- })
-
- return all
- }
-
- return getAnnouncements()
- .then(announcements => {
- store.commit('setAnnouncements', announcements)
- })
- .catch(error => {
- // If and only if backend does not support announcements, it would return 404.
- // In this case, silently ignores it.
- if (error && error.statusCode === 404) {
- store.commit('setSupportsAnnouncements', false)
- } else {
- throw error
- }
- })
- },
- markAnnouncementAsRead (store, id) {
- return store.rootState.api.backendInteractor.dismissAnnouncement({ id })
- .then(() => {
- store.commit('setAnnouncementRead', { id, read: true })
- })
- },
- startFetchingAnnouncements (store) {
- if (store.state.fetchAnnouncementsTimer) {
- return
- }
-
- const interval = setInterval(() => store.dispatch('fetchAnnouncements'), FETCH_ANNOUNCEMENT_INTERVAL_MS)
- store.commit('setFetchAnnouncementsTimer', interval)
-
- return store.dispatch('fetchAnnouncements')
- },
- stopFetchingAnnouncements (store) {
- const interval = store.state.fetchAnnouncementsTimer
- store.commit('setFetchAnnouncementsTimer', undefined)
- clearInterval(interval)
- },
- postAnnouncement (store, { content, startsAt, endsAt, allDay }) {
- return store.rootState.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
- .then(() => {
- return store.dispatch('fetchAnnouncements')
- })
- },
- editAnnouncement (store, { id, content, startsAt, endsAt, allDay }) {
- return store.rootState.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
- .then(() => {
- return store.dispatch('fetchAnnouncements')
- })
- },
- deleteAnnouncement (store, id) {
- return store.rootState.api.backendInteractor.deleteAnnouncement({ id })
- .then(() => {
- return store.dispatch('fetchAnnouncements')
- })
- }
- }
-}
-
-export default announcements
diff --git a/src/modules/api.js b/src/modules/api.js
@@ -2,6 +2,8 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { WSConnectionStatus } from '../services/api/api.service.js'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
import { Socket } from 'phoenix'
+import { useShoutStore } from 'src/stores/shout.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
const retryTimeout = (multiplier) => 1000 * multiplier
@@ -134,7 +136,7 @@ const api = {
state.mastoUserSocket.addEventListener('open', () => {
// Do not show notification when we just opened up the page
if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) {
- dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
level: 'success',
messageKey: 'timeline.socket_reconnected',
timeout: 5000
@@ -176,7 +178,7 @@ const api = {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
dispatch('startFetchingChats')
- dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'timeline.socket_broke',
messageArgs: [code],
@@ -300,7 +302,7 @@ const api = {
socket.connect()
commit('setSocket', socket)
- dispatch('initializeShout', socket)
+ useShoutStore().initializeShout(socket)
}
},
disconnectFromSocket ({ commit, state }) {
diff --git a/src/modules/config.js b/src/modules/config.js
@@ -3,6 +3,10 @@ import { applyConfig } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages'
import { set } from 'lodash'
import localeService from '../services/locale/locale.service.js'
+import { useI18nStore } from 'src/stores/i18n.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
+
+import { defaultState } from './default_config_state.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
const APPEARANCE_SETTINGS_KEYS = new Set([
@@ -17,8 +21,6 @@ const APPEARANCE_SETTINGS_KEYS = new Set([
'emojiReactionsScale'
])
-const browserLocale = (window.navigator.language || 'en').split('-')[0]
-
/* TODO this is a bit messy.
* We need to declare settings with their types and also deal with
* instance-default settings in some way, hopefully try to avoid copy-pasta
@@ -34,170 +36,6 @@ export const multiChoiceProperties = [
'unsavedPostAction' // save | discard | confirm
]
-export const defaultState = {
- expertLevel: 0, // used to track which settings to show and hide
-
- // Theme stuff
- theme: undefined, // Very old theme store, stores preset name, still in use
-
- // V1
- colors: {}, // VERY old theme store, just colors of V1, probably not even used anymore
-
- // V2
- customTheme: undefined, // "snapshot", previously was used as actual theme store for V2 so it's still used in case of PleromaFE downgrade event.
- customThemeSource: undefined, // "source", stores original theme data
-
- // V3
- style: null,
- styleCustomData: null,
- palette: null,
- paletteCustomData: null,
- themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions
- forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists
- theme3hacks: { // Hacks, user overrides that are independent of theme used
- underlay: 'none',
- fonts: {
- interface: undefined,
- input: undefined,
- post: undefined,
- monospace: undefined
- }
- },
-
- hideISP: false,
- hideInstanceWallpaper: false,
- hideShoutbox: false,
- // bad name: actually hides posts of muted USERS
- hideMutedPosts: undefined, // instance default
- hideMutedThreads: undefined, // instance default
- hideWordFilteredPosts: undefined, // instance default
- muteBotStatuses: undefined, // instance default
- muteSensitiveStatuses: undefined, // instance default
- collapseMessageWithSubject: undefined, // instance default
- padEmoji: true,
- hideAttachments: false,
- hideAttachmentsInConv: false,
- hideScrobbles: false,
- hideScrobblesAfter: '2d',
- maxThumbnails: 16,
- hideNsfw: true,
- preloadImage: true,
- loopVideo: true,
- loopVideoSilentOnly: true,
- streaming: false,
- emojiReactionsOnTimeline: true,
- alwaysShowNewPostButton: false,
- autohideFloatingPostButton: false,
- pauseOnUnfocused: true,
- stopGifs: true,
- replyVisibility: 'all',
- thirdColumnMode: 'notifications',
- notificationVisibility: {
- follows: true,
- mentions: true,
- statuses: true,
- likes: true,
- repeats: true,
- moves: true,
- emojiReactions: true,
- followRequest: true,
- reports: true,
- chatMention: true,
- polls: true
- },
- notificationNative: {
- follows: true,
- mentions: true,
- statuses: true,
- likes: false,
- repeats: false,
- moves: false,
- emojiReactions: false,
- followRequest: true,
- reports: true,
- chatMention: true,
- polls: true
- },
- webPushNotifications: false,
- webPushAlwaysShowNotifications: false,
- muteWords: [],
- highlight: {},
- interfaceLanguage: browserLocale,
- hideScopeNotice: false,
- useStreamingApi: false,
- sidebarRight: undefined, // instance default
- scopeCopy: undefined, // instance default
- subjectLineBehavior: undefined, // instance default
- alwaysShowSubjectInput: undefined, // instance default
- postContentType: undefined, // instance default
- minimalScopesMode: undefined, // instance default
- // This hides statuses filtered via a word filter
- hideFilteredStatuses: undefined, // instance default
- modalOnRepeat: undefined, // instance default
- modalOnUnfollow: undefined, // instance default
- modalOnBlock: undefined, // instance default
- modalOnMute: undefined, // instance default
- modalOnMuteConversation: undefined, // instance default
- modalOnMuteDomain: undefined, // instance default
- modalOnDelete: undefined, // instance default
- modalOnLogout: undefined, // instance default
- modalOnApproveFollow: undefined, // instance default
- modalOnDenyFollow: undefined, // instance default
- modalOnRemoveUserFromFollowers: undefined, // instance default
- modalMobileCenter: undefined,
- playVideosInModal: false,
- useOneClickNsfw: false,
- useContainFit: true,
- disableStickyHeaders: false,
- showScrollbars: false,
- userPopoverAvatarAction: 'open',
- userPopoverOverlay: false,
- sidebarColumnWidth: '25rem',
- contentColumnWidth: '45rem',
- notifsColumnWidth: '25rem',
- emojiReactionsScale: undefined,
- textSize: undefined, // instance default
- emojiSize: undefined, // instance default
- navbarSize: undefined, // instance default
- panelHeaderSize: undefined, // instance default
- forcedRoundness: undefined, // instance default
- navbarColumnStretch: false,
- greentext: undefined, // instance default
- useAtIcon: undefined, // instance default
- mentionLinkDisplay: undefined, // instance default
- mentionLinkShowTooltip: undefined, // instance default
- mentionLinkShowAvatar: undefined, // instance default
- mentionLinkFadeDomain: undefined, // instance default
- mentionLinkShowYous: undefined, // instance default
- mentionLinkBoldenYou: undefined, // instance default
- hidePostStats: undefined, // instance default
- hideBotIndication: undefined, // instance default
- hideUserStats: undefined, // instance default
- virtualScrolling: undefined, // instance default
- sensitiveByDefault: undefined, // instance default
- conversationDisplay: undefined, // instance default
- conversationTreeAdvanced: undefined, // instance default
- conversationOtherRepliesButton: undefined, // instance default
- conversationTreeFadeAncestors: undefined, // instance default
- showExtraNotifications: undefined, // instance default
- showExtraNotificationsTip: undefined, // instance default
- showChatsInExtraNotifications: undefined, // instance default
- showAnnouncementsInExtraNotifications: undefined, // instance default
- showFollowRequestsInExtraNotifications: undefined, // instance default
- maxDepthInThread: undefined, // instance default
- autocompleteSelect: undefined, // instance default
- closingDrawerMarksAsSeen: undefined, // instance default
- unseenAtTop: undefined, // instance default
- ignoreInactionableSeen: undefined, // instance default
- unsavedPostAction: undefined, // instance default
- autoSaveDraft: undefined, // instance default
- useAbsoluteTimeFormat: undefined, // instance default
- absoluteTimeFormatMinAge: undefined, // instance default
- absoluteTime12h: undefined, // instance default
- imageCompression: true,
- alwaysUseJpeg: false
-}
-
// caching the instance default properties
export const instanceDefaultProperties = Object.entries(defaultState)
.filter(([key, value]) => value === undefined)
@@ -260,7 +98,7 @@ const config = {
commit('setHighlight', { user, color, type })
},
setOptionTemporarily ({ commit, dispatch, state, rootState }, { name, value }) {
- if (rootState.interface.temporaryChangesTimeoutId !== null) {
+ if (useInterfaceStore().temporaryChangesTimeoutId !== null) {
console.warn('Can\'t track more than one temporary change')
return
}
@@ -328,7 +166,7 @@ const config = {
break
}
case 'interfaceLanguage':
- messages.setLanguage(this.getters.i18n, value)
+ messages.setLanguage(useI18nStore().i18n, value)
dispatch('loadUnicodeEmojiData', value)
Cookies.set(
BACKEND_LANGUAGE_COOKIE_NAME,
@@ -336,7 +174,7 @@ const config = {
)
break
case 'thirdColumnMode':
- dispatch('setLayoutWidth', undefined)
+ useInterfaceStore().setLayoutWidth(undefined)
break
}
}
diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js
@@ -0,0 +1,165 @@
+const browserLocale = (window.navigator.language || 'en').split('-')[0]
+
+export const defaultState = {
+ expertLevel: 0, // used to track which settings to show and hide
+
+ // Theme stuff
+ theme: undefined, // Very old theme store, stores preset name, still in use
+
+ // V1
+ colors: {}, // VERY old theme store, just colors of V1, probably not even used anymore
+
+ // V2
+ customTheme: undefined, // "snapshot", previously was used as actual theme store for V2 so it's still used in case of PleromaFE downgrade event.
+ customThemeSource: undefined, // "source", stores original theme data
+
+ // V3
+ style: null,
+ styleCustomData: null,
+ palette: null,
+ paletteCustomData: null,
+ themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions
+ forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists
+ theme3hacks: { // Hacks, user overrides that are independent of theme used
+ underlay: 'none',
+ fonts: {
+ interface: undefined,
+ input: undefined,
+ post: undefined,
+ monospace: undefined
+ }
+ },
+
+ hideISP: false,
+ hideInstanceWallpaper: false,
+ hideShoutbox: false,
+ // bad name: actually hides posts of muted USERS
+ hideMutedPosts: undefined, // instance default
+ hideMutedThreads: undefined, // instance default
+ hideWordFilteredPosts: undefined, // instance default
+ muteBotStatuses: undefined, // instance default
+ muteSensitiveStatuses: undefined, // instance default
+ collapseMessageWithSubject: undefined, // instance default
+ padEmoji: true,
+ hideAttachments: false,
+ hideAttachmentsInConv: false,
+ hideScrobbles: false,
+ hideScrobblesAfter: '2d',
+ maxThumbnails: 16,
+ hideNsfw: true,
+ preloadImage: true,
+ loopVideo: true,
+ loopVideoSilentOnly: true,
+ streaming: false,
+ emojiReactionsOnTimeline: true,
+ alwaysShowNewPostButton: false,
+ autohideFloatingPostButton: false,
+ pauseOnUnfocused: true,
+ stopGifs: true,
+ replyVisibility: 'all',
+ thirdColumnMode: 'notifications',
+ notificationVisibility: {
+ follows: true,
+ mentions: true,
+ statuses: true,
+ likes: true,
+ repeats: true,
+ moves: true,
+ emojiReactions: true,
+ followRequest: true,
+ reports: true,
+ chatMention: true,
+ polls: true
+ },
+ notificationNative: {
+ follows: true,
+ mentions: true,
+ statuses: true,
+ likes: false,
+ repeats: false,
+ moves: false,
+ emojiReactions: false,
+ followRequest: true,
+ reports: true,
+ chatMention: true,
+ polls: true
+ },
+ webPushNotifications: false,
+ webPushAlwaysShowNotifications: false,
+ muteWords: [],
+ highlight: {},
+ interfaceLanguage: browserLocale,
+ hideScopeNotice: false,
+ useStreamingApi: false,
+ sidebarRight: undefined, // instance default
+ scopeCopy: undefined, // instance default
+ subjectLineBehavior: undefined, // instance default
+ alwaysShowSubjectInput: undefined, // instance default
+ postContentType: undefined, // instance default
+ minimalScopesMode: undefined, // instance default
+ // This hides statuses filtered via a word filter
+ hideFilteredStatuses: undefined, // instance default
+ modalOnRepeat: undefined, // instance default
+ modalOnUnfollow: undefined, // instance default
+ modalOnBlock: undefined, // instance default
+ modalOnMute: undefined, // instance default
+ modalOnMuteConversation: undefined, // instance default
+ modalOnMuteDomain: undefined, // instance default
+ modalOnDelete: undefined, // instance default
+ modalOnLogout: undefined, // instance default
+ modalOnApproveFollow: undefined, // instance default
+ modalOnDenyFollow: undefined, // instance default
+ modalOnRemoveUserFromFollowers: undefined, // instance default
+ modalMobileCenter: undefined,
+ playVideosInModal: false,
+ useOneClickNsfw: false,
+ useContainFit: true,
+ disableStickyHeaders: false,
+ showScrollbars: false,
+ userPopoverAvatarAction: 'open',
+ userPopoverOverlay: false,
+ sidebarColumnWidth: '25rem',
+ contentColumnWidth: '45rem',
+ notifsColumnWidth: '25rem',
+ emojiReactionsScale: undefined,
+ textSize: undefined, // instance default
+ emojiSize: undefined, // instance default
+ navbarSize: undefined, // instance default
+ panelHeaderSize: undefined, // instance default
+ forcedRoundness: undefined, // instance default
+ navbarColumnStretch: false,
+ greentext: undefined, // instance default
+ useAtIcon: undefined, // instance default
+ mentionLinkDisplay: undefined, // instance default
+ mentionLinkShowTooltip: undefined, // instance default
+ mentionLinkShowAvatar: undefined, // instance default
+ mentionLinkFadeDomain: undefined, // instance default
+ mentionLinkShowYous: undefined, // instance default
+ mentionLinkBoldenYou: undefined, // instance default
+ hidePostStats: undefined, // instance default
+ hideBotIndication: undefined, // instance default
+ hideUserStats: undefined, // instance default
+ virtualScrolling: undefined, // instance default
+ sensitiveByDefault: undefined, // instance default
+ conversationDisplay: undefined, // instance default
+ conversationTreeAdvanced: undefined, // instance default
+ conversationOtherRepliesButton: undefined, // instance default
+ conversationTreeFadeAncestors: undefined, // instance default
+ showExtraNotifications: undefined, // instance default
+ showExtraNotificationsTip: undefined, // instance default
+ showChatsInExtraNotifications: undefined, // instance default
+ showAnnouncementsInExtraNotifications: undefined, // instance default
+ showFollowRequestsInExtraNotifications: undefined, // instance default
+ maxDepthInThread: undefined, // instance default
+ autocompleteSelect: undefined, // instance default
+ closingDrawerMarksAsSeen: undefined, // instance default
+ unseenAtTop: undefined, // instance default
+ ignoreInactionableSeen: undefined, // instance default
+ unsavedPostAction: undefined, // instance default
+ autoSaveDraft: undefined, // instance default
+ useAbsoluteTimeFormat: undefined, // instance default
+ absoluteTimeFormatMinAge: undefined, // instance default
+ absoluteTime12h: undefined, // instance default
+ imageCompression: true,
+ alwaysUseJpeg: false
+}
diff --git a/src/modules/editStatus.js b/src/modules/editStatus.js
@@ -1,25 +0,0 @@
-const editStatus = {
- state: {
- params: null,
- modalActivated: false
- },
- mutations: {
- openEditStatusModal (state, params) {
- state.params = params
- state.modalActivated = true
- },
- closeEditStatusModal (state) {
- state.modalActivated = false
- }
- },
- actions: {
- openEditStatusModal ({ commit }, params) {
- commit('openEditStatusModal', params)
- },
- closeEditStatusModal ({ commit }) {
- commit('closeEditStatusModal')
- }
- }
-}
-
-export default editStatus
diff --git a/src/modules/instance.js b/src/modules/instance.js
@@ -1,6 +1,7 @@
import apiService from '../services/api/api.service.js'
import { instanceDefaultProperties } from './config.js'
import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
const SORTED_EMOJI_GROUP_IDS = [
'smileys-and-emotion',
@@ -292,7 +293,7 @@ const instance = {
commit('setInstanceOption', { name, value })
switch (name) {
case 'name':
- dispatch('setPageTitle')
+ useInterfaceStore().setPageTitle()
break
case 'shoutAvailable':
if (value) {
diff --git a/src/modules/interface.js b/src/modules/interface.js
@@ -1,724 +0,0 @@
-import { getResourcesIndex, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
-import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
-import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
-import { deserialize } from '../services/theme_data/iss_deserializer.js'
-
-// helper for debugging
-// eslint-disable-next-line no-unused-vars
-const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
-
-const defaultState = {
- localFonts: null,
- themeApplied: false,
- themeChangeInProgress: false,
- themeVersion: 'v3',
- styleNameUsed: null,
- styleDataUsed: null,
- useStylePalette: false, // hack for applying styles from appearance tab
- paletteNameUsed: null,
- paletteDataUsed: null,
- themeNameUsed: null,
- themeDataUsed: null,
- temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout
- temporaryChangesConfirm: () => {}, // used for applying temporary options
- temporaryChangesRevert: () => {}, // used for reverting temporary options
- settingsModalState: 'hidden',
- settingsModalLoadedUser: false,
- settingsModalLoadedAdmin: false,
- settingsModalTargetTab: null,
- settingsModalMode: 'user',
- settings: {
- currentSaveStateNotice: null,
- noticeClearTimeout: null,
- notificationPermission: null
- },
- browserSupport: {
- cssFilter: window.CSS && window.CSS.supports && (
- window.CSS.supports('filter', 'drop-shadow(0 0)') ||
- window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
- ),
- localFonts: typeof window.queryLocalFonts === 'function'
- },
- layoutType: 'normal',
- globalNotices: [],
- layoutHeight: 0,
- lastTimeline: null
-}
-
-const interfaceMod = {
- state: defaultState,
- mutations: {
- settingsSaved (state, { success, error }) {
- if (success) {
- if (state.noticeClearTimeout) {
- clearTimeout(state.noticeClearTimeout)
- }
- state.settings.currentSaveStateNotice = { error: false, data: success }
- state.settings.noticeClearTimeout = setTimeout(() => delete state.settings.currentSaveStateNotice, 2000)
- } else {
- state.settings.currentSaveStateNotice = { error: true, errorData: error }
- }
- },
- setTemporaryChanges (state, { timeoutId, confirm, revert }) {
- state.temporaryChangesTimeoutId = timeoutId
- state.temporaryChangesConfirm = confirm
- state.temporaryChangesRevert = revert
- },
- clearTemporaryChanges (state) {
- clearTimeout(state.temporaryChangesTimeoutId)
- state.temporaryChangesTimeoutId = null
- state.temporaryChangesConfirm = () => {}
- state.temporaryChangesRevert = () => {}
- },
- setThemeApplied (state) {
- state.themeApplied = true
- },
- setNotificationPermission (state, permission) {
- state.notificationPermission = permission
- },
- setLayoutType (state, value) {
- state.layoutType = value
- },
- closeSettingsModal (state) {
- state.settingsModalState = 'hidden'
- },
- togglePeekSettingsModal (state) {
- switch (state.settingsModalState) {
- case 'minimized':
- state.settingsModalState = 'visible'
- return
- case 'visible':
- state.settingsModalState = 'minimized'
- return
- default:
- throw new Error('Illegal minimization state of settings modal')
- }
- },
- openSettingsModal (state, value) {
- state.settingsModalMode = value
- state.settingsModalState = 'visible'
- if (value === 'user') {
- if (!state.settingsModalLoadedUser) {
- state.settingsModalLoadedUser = true
- }
- } else if (value === 'admin') {
- if (!state.settingsModalLoadedAdmin) {
- state.settingsModalLoadedAdmin = true
- }
- }
- },
- setSettingsModalTargetTab (state, value) {
- state.settingsModalTargetTab = value
- },
- pushGlobalNotice (state, notice) {
- state.globalNotices.push(notice)
- },
- removeGlobalNotice (state, notice) {
- state.globalNotices = state.globalNotices.filter(n => n !== notice)
- },
- setLayoutHeight (state, value) {
- state.layoutHeight = value
- },
- setLayoutWidth (state, value) {
- state.layoutWidth = value
- },
- setLastTimeline (state, value) {
- state.lastTimeline = value
- },
- setFontsList (state, value) {
- // Set is used here so that we filter out duplicate fonts (possibly same font but with different weight)
- state.localFonts = [...(new Set(value.map(font => font.family))).values()]
- }
- },
- actions: {
- setPageTitle ({ rootState }, option = '') {
- document.title = `${option} ${rootState.instance.name}`
- },
- settingsSaved ({ commit, dispatch }, { success, error }) {
- commit('settingsSaved', { success, error })
- },
- setNotificationPermission ({ commit }, permission) {
- commit('setNotificationPermission', permission)
- },
- closeSettingsModal ({ commit }) {
- commit('closeSettingsModal')
- },
- openSettingsModal ({ commit }, value = 'user') {
- commit('openSettingsModal', value)
- },
- togglePeekSettingsModal ({ commit }) {
- commit('togglePeekSettingsModal')
- },
- clearSettingsModalTargetTab ({ commit }) {
- commit('setSettingsModalTargetTab', null)
- },
- openSettingsModalTab ({ commit }, value) {
- commit('setSettingsModalTargetTab', value)
- commit('openSettingsModal', 'user')
- },
- pushGlobalNotice (
- { commit, dispatch, state },
- {
- messageKey,
- messageArgs = {},
- level = 'error',
- timeout = 0
- }) {
- const notice = {
- messageKey,
- messageArgs,
- level
- }
- commit('pushGlobalNotice', notice)
- // Adding a new element to array wraps it in a Proxy, which breaks the comparison
- // TODO: Generate UUID or something instead or relying on !== operator?
- const newNotice = state.globalNotices[state.globalNotices.length - 1]
- if (timeout) {
- setTimeout(() => dispatch('removeGlobalNotice', newNotice), timeout)
- }
- return newNotice
- },
- removeGlobalNotice ({ commit }, notice) {
- commit('removeGlobalNotice', notice)
- },
- setLayoutHeight ({ commit }, value) {
- commit('setLayoutHeight', value)
- },
- // value is optional, assuming it was cached prior
- setLayoutWidth ({ commit, state, rootGetters, rootState }, value) {
- let width = value
- if (value !== undefined) {
- commit('setLayoutWidth', value)
- } else {
- width = state.layoutWidth
- }
- const mobileLayout = width <= 800
- const normalOrMobile = mobileLayout ? 'mobile' : 'normal'
- const { thirdColumnMode } = rootGetters.mergedConfig
- if (thirdColumnMode === 'none' || !rootState.users.currentUser) {
- commit('setLayoutType', normalOrMobile)
- } else {
- const wideLayout = width >= 1300
- commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
- }
- },
- queryLocalFonts ({ commit, dispatch, state }) {
- if (state.localFonts !== null) return
- commit('setFontsList', [])
- if (!state.browserSupport.localFonts) {
- return
- }
- window
- .queryLocalFonts()
- .then((fonts) => {
- commit('setFontsList', fonts)
- })
- .catch((e) => {
- dispatch('pushGlobalNotice', {
- messageKey: 'settings.style.themes3.font.font_list_unavailable',
- messageArgs: {
- error: e
- },
- level: 'error'
- })
- })
- },
- setLastTimeline ({ commit }, value) {
- commit('setLastTimeline', value)
- },
- async fetchPalettesIndex ({ commit, state }) {
- try {
- const value = await getResourcesIndex('/static/palettes/index.json')
- commit('setInstanceOption', { name: 'palettesIndex', value })
- return value
- } catch (e) {
- console.error('Could not fetch palettes index', e)
- commit('setInstanceOption', { name: 'palettesIndex', value: { _error: e } })
- return Promise.resolve({})
- }
- },
- setPalette ({ dispatch, commit }, value) {
- dispatch('resetThemeV3Palette')
- dispatch('resetThemeV2')
-
- commit('setOption', { name: 'palette', value })
-
- dispatch('applyTheme', { recompile: true })
- },
- setPaletteCustom ({ dispatch, commit }, value) {
- dispatch('resetThemeV3Palette')
- dispatch('resetThemeV2')
-
- commit('setOption', { name: 'paletteCustomData', value })
-
- dispatch('applyTheme', { recompile: true })
- },
- async fetchStylesIndex ({ commit, state }) {
- try {
- const value = await getResourcesIndex(
- '/static/styles/index.json',
- deserialize
- )
- commit('setInstanceOption', { name: 'stylesIndex', value })
- return value
- } catch (e) {
- console.error('Could not fetch styles index', e)
- commit('setInstanceOption', { name: 'stylesIndex', value: { _error: e } })
- return Promise.resolve({})
- }
- },
- setStyle ({ dispatch, commit, state }, value) {
- dispatch('resetThemeV3')
- dispatch('resetThemeV2')
- dispatch('resetThemeV3Palette')
-
- commit('setOption', { name: 'style', value })
- state.useStylePalette = true
-
- dispatch('applyTheme', { recompile: true }).then(() => {
- state.useStylePalette = false
- })
- },
- setStyleCustom ({ dispatch, commit, state }, value) {
- dispatch('resetThemeV3')
- dispatch('resetThemeV2')
- dispatch('resetThemeV3Palette')
-
- commit('setOption', { name: 'styleCustomData', value })
-
- state.useStylePalette = true
- dispatch('applyTheme', { recompile: true }).then(() => {
- state.useStylePalette = false
- })
- },
- async fetchThemesIndex ({ commit, state }) {
- try {
- const value = await getResourcesIndex('/static/styles.json')
- commit('setInstanceOption', { name: 'themesIndex', value })
- return value
- } catch (e) {
- console.error('Could not fetch themes index', e)
- commit('setInstanceOption', { name: 'themesIndex', value: { _error: e } })
- return Promise.resolve({})
- }
- },
- setTheme ({ dispatch, commit }, value) {
- dispatch('resetThemeV3')
- dispatch('resetThemeV3Palette')
- dispatch('resetThemeV2')
-
- commit('setOption', { name: 'theme', value })
-
- dispatch('applyTheme', { recompile: true })
- },
- setThemeCustom ({ dispatch, commit }, value) {
- dispatch('resetThemeV3')
- dispatch('resetThemeV3Palette')
- dispatch('resetThemeV2')
-
- commit('setOption', { name: 'customTheme', value })
- commit('setOption', { name: 'customThemeSource', value })
-
- dispatch('applyTheme', { recompile: true })
- },
- resetThemeV3 ({ dispatch, commit }) {
- commit('setOption', { name: 'style', value: null })
- commit('setOption', { name: 'styleCustomData', value: null })
- },
- resetThemeV3Palette ({ dispatch, commit }) {
- commit('setOption', { name: 'palette', value: null })
- commit('setOption', { name: 'paletteCustomData', value: null })
- },
- resetThemeV2 ({ dispatch, commit }) {
- commit('setOption', { name: 'theme', value: null })
- commit('setOption', { name: 'customTheme', value: null })
- commit('setOption', { name: 'customThemeSource', value: null })
- },
- async getThemeData ({ dispatch, commit, rootState, state }) {
- const getData = async (resource, index, customData, name) => {
- const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
- const result = {}
-
- if (customData) {
- result.nameUsed = 'custom' // custom data overrides name
- result.dataUsed = customData
- } else {
- result.nameUsed = name
-
- if (result.nameUsed == null) {
- result.dataUsed = null
- return result
- }
-
- let fetchFunc = index[result.nameUsed]
- // Fallbacks
- if (!fetchFunc) {
- if (resource === 'style' || resource === 'palette') {
- return result
- }
- const newName = Object.keys(index)[0]
- fetchFunc = index[newName]
- console.warn(`${capitalizedResource} with id '${state.styleNameUsed}' not found, trying back to '${newName}'`)
- if (!fetchFunc) {
- console.warn(`${capitalizedResource} doesn't have a fallback, defaulting to stock.`)
- fetchFunc = () => Promise.resolve(null)
- }
- }
- result.dataUsed = await fetchFunc()
- }
- return result
- }
-
- const {
- style: instanceStyleName,
- palette: instancePaletteName
- } = rootState.instance
-
- let {
- theme: instanceThemeV2Name,
- themesIndex,
- stylesIndex,
- palettesIndex
- } = rootState.instance
-
- const {
- style: userStyleName,
- styleCustomData: userStyleCustomData,
- palette: userPaletteName,
- paletteCustomData: userPaletteCustomData
- } = rootState.config
-
- let {
- theme: userThemeV2Name,
- customTheme: userThemeV2Snapshot,
- customThemeSource: userThemeV2Source
- } = rootState.config
-
- let majorVersionUsed
-
- console.debug(
- `User V3 palette: ${userPaletteName}, style: ${userStyleName} , custom: ${!!userStyleCustomData}`
- )
- console.debug(
- `User V2 name: ${userThemeV2Name}, source: ${!!userThemeV2Source}, snapshot: ${!!userThemeV2Snapshot}`
- )
-
- console.debug(`Instance V3 palette: ${instancePaletteName}, style: ${instanceStyleName}`)
- console.debug('Instance V2 theme: ' + instanceThemeV2Name)
-
- if (userPaletteName || userPaletteCustomData ||
- userStyleName || userStyleCustomData ||
- (
- // User V2 overrides instance V3
- (instancePaletteName ||
- instanceStyleName) &&
- instanceThemeV2Name == null &&
- userThemeV2Name == null
- )
- ) {
- // Palette and/or style overrides V2 themes
- instanceThemeV2Name = null
- userThemeV2Name = null
- userThemeV2Source = null
- userThemeV2Snapshot = null
-
- majorVersionUsed = 'v3'
- } else if (
- (userThemeV2Name ||
- userThemeV2Snapshot ||
- userThemeV2Source ||
- instanceThemeV2Name)
- ) {
- majorVersionUsed = 'v2'
- } else {
- // if all fails fallback to v3
- majorVersionUsed = 'v3'
- }
-
- if (majorVersionUsed === 'v3') {
- const result = await Promise.all([
- dispatch('fetchPalettesIndex'),
- dispatch('fetchStylesIndex')
- ])
-
- palettesIndex = result[0]
- stylesIndex = result[1]
- } else {
- // Promise.all just to be uniform with v3
- const result = await Promise.all([
- dispatch('fetchThemesIndex')
- ])
-
- themesIndex = result[0]
- }
-
- state.themeVersion = majorVersionUsed
-
- console.debug('Version used', majorVersionUsed)
-
- if (majorVersionUsed === 'v3') {
- state.themeDataUsed = null
- state.themeNameUsed = null
-
- const style = await getData(
- 'style',
- stylesIndex,
- userStyleCustomData,
- userStyleName || instanceStyleName
- )
- state.styleNameUsed = style.nameUsed
- state.styleDataUsed = style.dataUsed
-
- let firstStylePaletteName = null
- style
- .dataUsed
- ?.filter(x => x.component === '@palette')
- .map(x => {
- const cleanDirectives = Object.fromEntries(
- Object
- .entries(x.directives)
- .filter(([k, v]) => k)
- )
-
- return { name: x.variant, ...cleanDirectives }
- })
- .forEach(palette => {
- const key = 'style.' + palette.name.toLowerCase().replace(/ /g, '_')
- if (!firstStylePaletteName) firstStylePaletteName = key
- palettesIndex[key] = () => Promise.resolve(palette)
- })
-
- const palette = await getData(
- 'palette',
- palettesIndex,
- userPaletteCustomData,
- state.useStylePalette ? firstStylePaletteName : (userPaletteName || instancePaletteName)
- )
-
- if (state.useStylePalette) {
- commit('setOption', { name: 'palette', value: firstStylePaletteName })
- }
-
- state.paletteNameUsed = palette.nameUsed
- state.paletteDataUsed = palette.dataUsed
-
- if (state.paletteDataUsed) {
- state.paletteDataUsed.link = state.paletteDataUsed.link || state.paletteDataUsed.accent
- state.paletteDataUsed.accent = state.paletteDataUsed.accent || state.paletteDataUsed.link
- }
- if (Array.isArray(state.paletteDataUsed)) {
- const [
- name,
- bg,
- fg,
- text,
- link,
- cRed = '#FF0000',
- cGreen = '#00FF00',
- cBlue = '#0000FF',
- cOrange = '#E3FF00'
- ] = palette.dataUsed
- state.paletteDataUsed = {
- name,
- bg,
- fg,
- text,
- link,
- accent: link,
- cRed,
- cBlue,
- cGreen,
- cOrange
- }
- }
- console.debug('Palette data used', palette.dataUsed)
- } else {
- state.styleNameUsed = null
- state.styleDataUsed = null
- state.paletteNameUsed = null
- state.paletteDataUsed = null
-
- const theme = await getData(
- 'theme',
- themesIndex,
- userThemeV2Source || userThemeV2Snapshot,
- userThemeV2Name || instanceThemeV2Name
- )
- state.themeNameUsed = theme.nameUsed
- state.themeDataUsed = theme.dataUsed
- }
- },
- async applyTheme (
- { dispatch, commit, rootState, state },
- { recompile = false } = {}
- ) {
- const {
- forceThemeRecompilation,
- themeDebug,
- theme3hacks
- } = rootState.config
- state.themeChangeInProgress = true
- // If we're not not forced to recompile try using
- // cache (tryLoadCache return true if load successful)
-
- const forceRecompile = forceThemeRecompilation || recompile
- if (!forceRecompile && !themeDebug && await tryLoadCache()) {
- state.themeChangeInProgress = false
- return commit('setThemeApplied')
- }
- window.splashUpdate('splash.theme')
- await dispatch('getThemeData')
-
- try {
- const paletteIss = (() => {
- if (!state.paletteDataUsed) return null
- const result = {
- component: 'Root',
- directives: {}
- }
-
- Object
- .entries(state.paletteDataUsed)
- .filter(([k]) => k !== 'name')
- .forEach(([k, v]) => {
- let issRootDirectiveName
- switch (k) {
- case 'background':
- issRootDirectiveName = 'bg'
- break
- case 'foreground':
- issRootDirectiveName = 'fg'
- break
- default:
- issRootDirectiveName = k
- }
- result.directives['--' + issRootDirectiveName] = 'color | ' + v
- })
- return result
- })()
-
- const theme2ruleset = state.themeDataUsed && convertTheme2To3(normalizeThemeData(state.themeDataUsed))
- const hacks = []
-
- Object.entries(theme3hacks).forEach(([key, value]) => {
- switch (key) {
- case 'fonts': {
- Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
- if (!font?.family) return
- switch (fontKey) {
- case 'interface':
- hacks.push({
- component: 'Root',
- directives: {
- '--font': 'generic | ' + font.family
- }
- })
- break
- case 'input':
- hacks.push({
- component: 'Input',
- directives: {
- '--font': 'generic | ' + font.family
- }
- })
- break
- case 'post':
- hacks.push({
- component: 'RichContent',
- directives: {
- '--font': 'generic | ' + font.family
- }
- })
- break
- case 'monospace':
- hacks.push({
- component: 'Root',
- directives: {
- '--monoFont': 'generic | ' + font.family
- }
- })
- break
- }
- })
- break
- }
- case 'underlay': {
- if (value !== 'none') {
- const newRule = {
- component: 'Underlay',
- directives: {}
- }
- if (value === 'opaque') {
- newRule.directives.opacity = 1
- newRule.directives.background = '--wallpaper'
- }
- if (value === 'transparent') {
- newRule.directives.opacity = 0
- }
- hacks.push(newRule)
- }
- break
- }
- }
- })
-
- const rulesetArray = [
- theme2ruleset,
- state.styleDataUsed,
- paletteIss,
- hacks
- ].filter(x => x)
-
- return applyTheme(
- rulesetArray.flat(),
- () => commit('setThemeApplied'),
- () => {
- state.themeChangeInProgress = false
- },
- themeDebug
- )
- } catch (e) {
- window.splashError(e)
- }
- }
- }
-}
-
-export default interfaceMod
-
-export const normalizeThemeData = (input) => {
- let themeData, themeSource
-
- if (input.themeFileVerison === 1) {
- // this might not be even used at all, some leftover of unimplemented code in V2 editor
- return generatePreset(input).theme
- } else if (
- Object.prototype.hasOwnProperty.call(input, '_pleroma_theme_version') ||
- Object.prototype.hasOwnProperty.call(input, 'source') ||
- Object.prototype.hasOwnProperty.call(input, 'theme')
- ) {
- // We got passed a full theme file
- themeData = input.theme
- themeSource = input.source
- } else if (
- Object.prototype.hasOwnProperty.call(input, 'themeEngineVersion') ||
- Object.prototype.hasOwnProperty.call(input, 'colors')
- ) {
- // We got passed a source/snapshot
- themeData = input
- themeSource = input
- }
- // New theme presets don't have 'theme' property, they use 'source'
-
- let out // shout, shout let it all out
- if (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION) {
- // There are some themes in wild that have completely broken source
- out = { ...(themeData || {}), ...themeSource }
- } else {
- out = themeData
- }
-
- // generatePreset here basically creates/updates "snapshot",
- // while also fixing the 2.2 -> 2.3 colors/shadows/etc
- return generatePreset(out).theme
-}
diff --git a/src/modules/lists.js b/src/modules/lists.js
@@ -1,130 +0,0 @@
-import { remove, find } from 'lodash'
-
-export const defaultState = {
- allLists: [],
- allListsObject: {}
-}
-
-export const mutations = {
- setLists (state, value) {
- state.allLists = value
- },
- setList (state, { listId, title }) {
- if (!state.allListsObject[listId]) {
- state.allListsObject[listId] = { accountIds: [] }
- }
- state.allListsObject[listId].title = title
-
- const entry = find(state.allLists, { id: listId })
- if (!entry) {
- state.allLists.push({ id: listId, title })
- } else {
- entry.title = title
- }
- },
- setListAccounts (state, { listId, accountIds }) {
- if (!state.allListsObject[listId]) {
- state.allListsObject[listId] = { accountIds: [] }
- }
- state.allListsObject[listId].accountIds = accountIds
- },
- addListAccount (state, { listId, accountId }) {
- if (!state.allListsObject[listId]) {
- state.allListsObject[listId] = { accountIds: [] }
- }
- state.allListsObject[listId].accountIds.push(accountId)
- },
- removeListAccount (state, { listId, accountId }) {
- if (!state.allListsObject[listId]) {
- state.allListsObject[listId] = { accountIds: [] }
- }
- const { accountIds } = state.allListsObject[listId]
- const set = new Set(accountIds)
- set.delete(accountId)
- state.allListsObject[listId].accountIds = [...set]
- },
- deleteList (state, { listId }) {
- delete state.allListsObject[listId]
- remove(state.allLists, list => list.id === listId)
- }
-}
-
-const actions = {
- setLists ({ commit }, value) {
- commit('setLists', value)
- },
- createList ({ rootState, commit }, { title }) {
- return rootState.api.backendInteractor.createList({ title })
- .then((list) => {
- commit('setList', { listId: list.id, title })
- return list
- })
- },
- fetchList ({ rootState, commit }, { listId }) {
- return rootState.api.backendInteractor.getList({ listId })
- .then((list) => commit('setList', { listId: list.id, title: list.title }))
- },
- fetchListAccounts ({ rootState, commit }, { listId }) {
- return rootState.api.backendInteractor.getListAccounts({ listId })
- .then((accountIds) => commit('setListAccounts', { listId, accountIds }))
- },
- setList ({ rootState, commit }, { listId, title }) {
- rootState.api.backendInteractor.updateList({ listId, title })
- commit('setList', { listId, title })
- },
- setListAccounts ({ rootState, commit }, { listId, accountIds }) {
- const saved = rootState.lists.allListsObject[listId].accountIds || []
- const added = accountIds.filter(id => !saved.includes(id))
- const removed = saved.filter(id => !accountIds.includes(id))
- commit('setListAccounts', { listId, accountIds })
- if (added.length > 0) {
- rootState.api.backendInteractor.addAccountsToList({ listId, accountIds: added })
- }
- if (removed.length > 0) {
- rootState.api.backendInteractor.removeAccountsFromList({ listId, accountIds: removed })
- }
- },
- addListAccount ({ rootState, commit }, { listId, accountId }) {
- return rootState
- .api
- .backendInteractor
- .addAccountsToList({ listId, accountIds: [accountId] })
- .then((result) => {
- commit('addListAccount', { listId, accountId })
- return result
- })
- },
- removeListAccount ({ rootState, commit }, { listId, accountId }) {
- return rootState
- .api
- .backendInteractor
- .removeAccountsFromList({ listId, accountIds: [accountId] })
- .then((result) => {
- commit('removeListAccount', { listId, accountId })
- return result
- })
- },
- deleteList ({ rootState, commit }, { listId }) {
- rootState.api.backendInteractor.deleteList({ listId })
- commit('deleteList', { listId })
- }
-}
-
-export const getters = {
- findListTitle: state => id => {
- if (!state.allListsObject[id]) return
- return state.allListsObject[id].title
- },
- findListAccounts: state => id => {
- return [...state.allListsObject[id].accountIds]
- }
-}
-
-const lists = {
- state: defaultState,
- mutations,
- actions,
- getters
-}
-
-export default lists
diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js
@@ -1,40 +0,0 @@
-import fileTypeService from '../services/file_type/file_type.service.js'
-const supportedTypes = new Set(['image', 'video', 'audio', 'flash'])
-
-const mediaViewer = {
- state: {
- media: [],
- currentIndex: 0,
- activated: false
- },
- mutations: {
- setMedia (state, media) {
- state.media = media
- },
- setCurrentMedia (state, index) {
- state.activated = true
- state.currentIndex = index
- },
- close (state) {
- state.activated = false
- }
- },
- actions: {
- setMedia ({ commit }, attachments) {
- const media = attachments.filter(attachment => {
- const type = fileTypeService.fileType(attachment.mimetype)
- return supportedTypes.has(type)
- })
- commit('setMedia', media)
- },
- setCurrentMedia ({ commit, state }, current) {
- const index = state.media.indexOf(current)
- commit('setCurrentMedia', index || 0)
- },
- closeMediaViewer ({ commit }) {
- commit('close')
- }
- }
-}
-
-export default mediaViewer
diff --git a/src/modules/notifications.js b/src/modules/notifications.js
@@ -11,6 +11,8 @@ import {
closeAllDesktopNotifications
} from '../services/desktop_notification_utils/desktop_notification_utils.js'
+import { useReportsStore } from 'src/stores/reports.js'
+
const emptyNotifications = () => ({
desktopNotificationSilence: true,
maxId: 0,
@@ -94,7 +96,7 @@ export const notifications = {
validNotifications.forEach(notification => {
if (notification.type === 'pleroma:report') {
- dispatch('addReport', notification.report)
+ useReportsStore().addReport(notification.report)
}
if (notification.type === 'pleroma:emoji_reaction') {
diff --git a/src/modules/polls.js b/src/modules/polls.js
@@ -1,69 +0,0 @@
-import { merge } from 'lodash'
-
-const polls = {
- state: {
- // Contains key = id, value = number of trackers for this poll
- trackedPolls: {},
- pollsObject: {}
- },
- mutations: {
- mergeOrAddPoll (state, poll) {
- const existingPoll = state.pollsObject[poll.id]
- // Make expired-state change trigger re-renders properly
- poll.expired = Date.now() > Date.parse(poll.expires_at)
- if (existingPoll) {
- state.pollsObject[poll.id] = merge(existingPoll, poll)
- } else {
- state.pollsObject[poll.id] = poll
- }
- },
- trackPoll (state, pollId) {
- const currentValue = state.trackedPolls[pollId]
- if (currentValue) {
- state.trackedPolls[pollId] = currentValue + 1
- } else {
- state.trackedPolls[pollId] = 1
- }
- },
- untrackPoll (state, pollId) {
- const currentValue = state.trackedPolls[pollId]
- if (currentValue) {
- state.trackedPolls[pollId] = currentValue - 1
- } else {
- state.trackedPolls[pollId] = 0
- }
- }
- },
- actions: {
- mergeOrAddPoll ({ commit }, poll) {
- commit('mergeOrAddPoll', poll)
- },
- updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
- rootState.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
- setTimeout(() => {
- if (rootState.polls.trackedPolls[pollId]) {
- dispatch('updateTrackedPoll', pollId)
- }
- }, 30 * 1000)
- commit('mergeOrAddPoll', poll)
- })
- },
- trackPoll ({ rootState, commit, dispatch }, pollId) {
- if (!rootState.polls.trackedPolls[pollId]) {
- setTimeout(() => dispatch('updateTrackedPoll', pollId), 30 * 1000)
- }
- commit('trackPoll', pollId)
- },
- untrackPoll ({ commit }, pollId) {
- commit('untrackPoll', pollId)
- },
- votePoll ({ rootState, commit }, { id, pollId, choices }) {
- return rootState.api.backendInteractor.vote({ pollId, choices }).then(poll => {
- commit('mergeOrAddPoll', poll)
- return poll
- })
- }
- }
-}
-
-export default polls
diff --git a/src/modules/postStatus.js b/src/modules/postStatus.js
@@ -1,31 +0,0 @@
-const postStatus = {
- state: {
- params: null,
- modalActivated: false
- },
- mutations: {
- openPostStatusModal (state, params) {
- state.params = params
- state.modalActivated = true
- },
- closePostStatusModal (state) {
- state.modalActivated = false
- },
- resetPostStatusModal (state) {
- state.params = null
- }
- },
- actions: {
- openPostStatusModal ({ commit }, params) {
- commit('openPostStatusModal', params)
- },
- closePostStatusModal ({ commit }) {
- commit('closePostStatusModal')
- },
- resetPostStatusModal ({ commit }) {
- commit('resetPostStatusModal')
- }
- }
-}
-
-export default postStatus
diff --git a/src/modules/reports.js b/src/modules/reports.js
@@ -1,64 +0,0 @@
-import filter from 'lodash/filter'
-
-const reports = {
- state: {
- reportModal: {
- userId: null,
- statuses: [],
- preTickedIds: [],
- activated: false
- },
- reports: {}
- },
- mutations: {
- openUserReportingModal (state, { userId, statuses, preTickedIds }) {
- state.reportModal.userId = userId
- state.reportModal.statuses = statuses
- state.reportModal.preTickedIds = preTickedIds
- state.reportModal.activated = true
- },
- closeUserReportingModal (state) {
- state.reportModal.activated = false
- },
- setReportState (reportsState, { id, state }) {
- reportsState.reports[id].state = state
- },
- addReport (state, report) {
- state.reports[report.id] = report
- }
- },
- actions: {
- openUserReportingModal ({ rootState, commit }, { userId, statusIds = [] }) {
- const preTickedStatuses = statusIds.map(id => rootState.statuses.allStatusesObject[id])
- const preTickedIds = statusIds
- const statuses = preTickedStatuses.concat(
- filter(rootState.statuses.allStatuses,
- status => status.user.id === userId && !preTickedIds.includes(status.id)
- )
- )
- commit('openUserReportingModal', { userId, statuses, preTickedIds })
- },
- closeUserReportingModal ({ commit }) {
- commit('closeUserReportingModal')
- },
- setReportState ({ commit, dispatch, rootState }, { id, state }) {
- const oldState = rootState.reports.reports[id].state
- commit('setReportState', { id, state })
- rootState.api.backendInteractor.setReportState({ id, state }).catch(e => {
- console.error('Failed to set report state', e)
- dispatch('pushGlobalNotice', {
- level: 'error',
- messageKey: 'general.generic_error_message',
- messageArgs: [e.message],
- timeout: 5000
- })
- commit('setReportState', { id, state: oldState })
- })
- },
- addReport ({ commit }, report) {
- commit('addReport', report)
- }
- }
-}
-
-export default reports
diff --git a/src/modules/shout.js b/src/modules/shout.js
@@ -1,46 +0,0 @@
-const shout = {
- state: {
- messages: [],
- channel: { state: '' },
- joined: false
- },
- mutations: {
- setChannel (state, channel) {
- state.channel = channel
- },
- addMessage (state, message) {
- state.messages.push(message)
- state.messages = state.messages.slice(-19, 20)
- },
- setMessages (state, messages) {
- state.messages = messages.slice(-19, 20)
- },
- setJoined (state, joined) {
- state.joined = joined
- }
- },
- actions: {
- initializeShout (store, socket) {
- const channel = socket.channel('chat:public')
- channel.joinPush.receive('ok', () => {
- store.commit('setJoined', true)
- })
- channel.onClose(() => {
- store.commit('setJoined', false)
- })
- channel.onError(() => {
- store.commit('setJoined', false)
- })
- channel.on('new_msg', (msg) => {
- store.commit('addMessage', msg)
- })
- channel.on('messages', ({ messages }) => {
- store.commit('setMessages', messages)
- })
- channel.join()
- store.commit('setChannel', channel)
- }
- }
-}
-
-export default shout
diff --git a/src/modules/statusHistory.js b/src/modules/statusHistory.js
@@ -1,25 +0,0 @@
-const statusHistory = {
- state: {
- params: {},
- modalActivated: false
- },
- mutations: {
- openStatusHistoryModal (state, params) {
- state.params = params
- state.modalActivated = true
- },
- closeStatusHistoryModal (state) {
- state.modalActivated = false
- }
- },
- actions: {
- openStatusHistoryModal ({ commit }, params) {
- commit('openStatusHistoryModal', params)
- },
- closeStatusHistoryModal ({ commit }) {
- commit('closeStatusHistoryModal')
- }
- }
-}
-
-export default statusHistory
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
@@ -13,6 +13,7 @@ import {
omitBy
} from 'lodash'
import apiService from '../services/api/api.service.js'
+import { useInterfaceStore } from 'src/stores/interface'
const emptyTl = (userId = 0) => ({
statuses: [],
@@ -510,7 +511,7 @@ const statuses = {
commit('setDeleted', { status })
})
.catch((e) => {
- dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'status.delete_error',
messageArgs: [e.message],
diff --git a/src/modules/users.js b/src/modules/users.js
@@ -3,6 +3,7 @@ import { windowWidth, windowHeight } from '../services/window_utils/window_utils
import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@@ -584,9 +585,9 @@ const users = {
store.commit('clearNotifications')
store.commit('resetStatuses')
store.dispatch('resetChats')
- store.dispatch('setLastTimeline', 'public-timeline')
- store.dispatch('setLayoutWidth', windowWidth())
- store.dispatch('setLayoutHeight', windowHeight())
+ useInterfaceStore().setLastTimeline('public-timeline')
+ useInterfaceStore().setLayoutWidth(windowWidth())
+ useInterfaceStore().setLayoutHeight(windowHeight())
store.commit('clearServerSideStorage')
})
},
@@ -611,7 +612,7 @@ const users = {
dispatch('fetchEmoji')
getNotificationPermission()
- .then(permission => commit('setNotificationPermission', permission))
+ .then(permission => useInterfaceStore().setNotificationPermission(permission))
// Set our new backend interactor
commit('setBackendInteractor', backendInteractorService(accessToken))
@@ -658,8 +659,8 @@ const users = {
// Get user mutes
dispatch('fetchMutes')
- dispatch('setLayoutWidth', windowWidth())
- dispatch('setLayoutHeight', windowHeight())
+ useInterfaceStore().setLayoutWidth(windowWidth())
+ useInterfaceStore().setLayoutHeight(windowHeight())
// Fetch our friends
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
diff --git a/src/services/lists_fetcher/lists_fetcher.service.js b/src/services/lists_fetcher/lists_fetcher.service.js
@@ -1,10 +1,11 @@
+import { useListsStore } from 'src/stores/lists.js'
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
const fetchAndUpdate = ({ store, credentials }) => {
return apiService.fetchLists({ credentials })
.then(lists => {
- store.commit('setLists', lists)
+ useListsStore().setLists(lists)
}, () => {})
.catch(() => {})
}
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
@@ -1,5 +1,7 @@
import { muteWordHits } from '../status_parser/status_parser.js'
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
+import { useI18nStore } from 'src/stores/i18n.js'
+import { useAnnouncementsStore } from 'src/stores/announcements'
import FaviconService from 'src/services/favicon_service/favicon_service.js'
@@ -64,13 +66,12 @@ const isMutedNotification = (store, notification) => {
export const maybeShowNotification = (store, notification) => {
const rootState = store.rootState || store.state
- const rootGetters = store.rootGetters || store.getters
if (notification.seen) return
if (!visibleTypes(store).includes(notification.type)) return
if (notification.type === 'mention' && isMutedNotification(store, notification)) return
- const notificationObject = prepareNotificationObject(notification, rootGetters.i18n)
+ const notificationObject = prepareNotificationObject(notification, useI18nStore().i18n)
showDesktopNotification(rootState, notificationObject)
}
@@ -169,7 +170,7 @@ export const countExtraNotifications = (store) => {
return [
mergedConfig.showChatsInExtraNotifications ? rootGetters.unreadChatCount : 0,
- mergedConfig.showAnnouncementsInExtraNotifications ? rootGetters.unreadAnnouncementCount : 0,
+ mergedConfig.showAnnouncementsInExtraNotifications ? useAnnouncementsStore().unreadAnnouncementCount : 0,
mergedConfig.showFollowRequestsInExtraNotifications ? rootGetters.followRequestCount : 0
].reduce((a, c) => a + c, 0)
}
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -1,3 +1,4 @@
+import { useInterfaceStore } from 'src/stores/interface.js'
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
@@ -78,7 +79,7 @@ const fetchNotifications = ({ store, args, older }) => {
return notifications
})
.catch((error) => {
- store.dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'notifications.error',
messageArgs: [error.message],
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
@@ -1,10 +1,12 @@
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
import { getCssRules } from '../theme_data/css_utils.js'
-import { defaultState } from '../../modules/config.js'
+import { defaultState } from 'src/modules/default_config_state.js'
import { chunk } from 'lodash'
import pako from 'pako'
import localforage from 'localforage'
+console.log('CONFIG', defaultState)
+
// On platforms where this is not supported, it will return undefined
// Otherwise it will return an array
const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
@@ -212,6 +214,7 @@ const extractStyleConfig = ({
return result
}
+console.log(defaultState)
const defaultStyleConfig = extractStyleConfig(defaultState)
export const applyConfig = (input, i18n) => {
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -2,6 +2,7 @@ import { camelCase } from 'lodash'
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
+import { useInterfaceStore } from 'src/stores/interface.js'
const update = ({ store, statuses, timeline, showImmediately, userId, listId, pagination }) => {
const ccTimeline = camelCase(timeline)
@@ -73,7 +74,7 @@ const fetchAndUpdate = ({
return { statuses, pagination }
})
.catch((error) => {
- store.dispatch('pushGlobalNotice', {
+ useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'timeline.error',
messageArgs: [error.message],
diff --git a/src/stores/announcements.js b/src/stores/announcements.js
@@ -0,0 +1,115 @@
+import { defineStore } from 'pinia'
+
+const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
+
+export const useAnnouncementsStore = defineStore('announcements', {
+ state: () => ({
+ announcements: [],
+ supportsAnnouncements: true,
+ fetchAnnouncementsTimer: undefined
+ }),
+ getters: {
+ unreadAnnouncementCount () {
+ if (!window.vuex.state.users.currentUser) {
+ return 0
+ }
+
+ const unread = this.announcements.filter(announcement => !(announcement.inactive || announcement.read))
+ return unread.length
+ }
+ },
+ actions: {
+ fetchAnnouncements () {
+ if (!this.supportsAnnouncements) {
+ return Promise.resolve()
+ }
+
+ const currentUser = window.vuex.state.users.currentUser
+ const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
+
+ const getAnnouncements = async () => {
+ if (!isAdmin) {
+ return window.vuex.state.api.backendInteractor.fetchAnnouncements()
+ }
+
+ const all = await window.vuex.state.api.backendInteractor.adminFetchAnnouncements()
+ const visible = await window.vuex.state.api.backendInteractor.fetchAnnouncements()
+ const visibleObject = visible.reduce((a, c) => {
+ a[c.id] = c
+ return a
+ }, {})
+ const getWithinVisible = announcement => visibleObject[announcement.id]
+
+ all.forEach(announcement => {
+ const visibleAnnouncement = getWithinVisible(announcement)
+ if (!visibleAnnouncement) {
+ announcement.inactive = true
+ } else {
+ announcement.read = visibleAnnouncement.read
+ }
+ })
+
+ return all
+ }
+
+ return getAnnouncements()
+ .then(announcements => {
+ this.announcements = announcements
+ })
+ .catch(error => {
+ // If and only if backend does not support announcements, it would return 404.
+ // In this case, silently ignores it.
+ if (error && error.statusCode === 404) {
+ this.supportsAnnouncements = false
+ } else {
+ throw error
+ }
+ })
+ },
+ markAnnouncementAsRead (id) {
+ return window.vuex.state.api.backendInteractor.dismissAnnouncement({ id })
+ .then(() => {
+ const index = this.announcements.findIndex(a => a.id === id)
+
+ if (index < 0) {
+ return
+ }
+
+ this.announcements[index].read = true
+ })
+ },
+ startFetchingAnnouncements () {
+ if (this.fetchAnnouncementsTimer) {
+ return
+ }
+
+ const interval = setInterval(() => this.fetchAnnouncements(), FETCH_ANNOUNCEMENT_INTERVAL_MS)
+ this.fetchAnnouncementsTimer = interval
+
+ return this.fetchAnnouncements()
+ },
+ stopFetchingAnnouncements () {
+ const interval = this.fetchAnnouncementsTimer
+ this.fetchAnnouncementsTimer = undefined
+ clearInterval(interval)
+ },
+ postAnnouncement ({ content, startsAt, endsAt, allDay }) {
+ return window.vuex.state.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
+ .then(() => {
+ return this.fetchAnnouncements()
+ })
+ },
+ editAnnouncement ({ id, content, startsAt, endsAt, allDay }) {
+ return window.vuex.state.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
+ .then(() => {
+ return this.fetchAnnouncements()
+ })
+ },
+ deleteAnnouncement (id) {
+ return window.vuex.state.api.backendInteractor.deleteAnnouncement({ id })
+ .then(() => {
+ return this.fetchAnnouncements()
+ })
+ }
+ }
+})
diff --git a/src/stores/editStatus.js b/src/stores/editStatus.js
@@ -0,0 +1,17 @@
+import { defineStore } from 'pinia'
+
+export const useEditStatusStore = defineStore('editStatus', {
+ state: () => ({
+ params: null,
+ modalActivated: false
+ }),
+ actions: {
+ openEditStatusModal (params) {
+ this.params = params
+ this.modalActivated = true
+ },
+ closeEditStatusModal () {
+ this.modalActivated = false
+ }
+ }
+})
diff --git a/src/stores/i18n.js b/src/stores/i18n.js
@@ -0,0 +1,14 @@
+import { defineStore } from 'pinia'
+
+export const useI18nStore = defineStore('i18n', {
+ state: () => ({
+ i18n: null
+ }),
+ actions: {
+ setI18n (newI18n) {
+ this.$patch({
+ i18n: newI18n.global
+ })
+ }
+ }
+})
diff --git a/src/stores/interface.js b/src/stores/interface.js
@@ -0,0 +1,674 @@
+import { defineStore } from 'pinia'
+
+import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
+import { getResourcesIndex, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
+import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
+import { deserialize } from '../services/theme_data/iss_deserializer.js'
+
+export const useInterfaceStore = defineStore('interface', {
+ state: () => ({
+ localFonts: null,
+ themeApplied: false,
+ themeChangeInProgress: false,
+ themeVersion: 'v3',
+ styleNameUsed: null,
+ styleDataUsed: null,
+ useStylePalette: false, // hack for applying styles from appearance tab
+ paletteNameUsed: null,
+ paletteDataUsed: null,
+ themeNameUsed: null,
+ themeDataUsed: null,
+ temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout
+ temporaryChangesConfirm: () => {}, // used for applying temporary options
+ temporaryChangesRevert: () => {}, // used for reverting temporary options
+ settingsModalState: 'hidden',
+ settingsModalLoadedUser: false,
+ settingsModalLoadedAdmin: false,
+ settingsModalTargetTab: null,
+ settingsModalMode: 'user',
+ settings: {
+ currentSaveStateNotice: null,
+ noticeClearTimeout: null,
+ notificationPermission: null
+ },
+ browserSupport: {
+ cssFilter: window.CSS && window.CSS.supports && (
+ window.CSS.supports('filter', 'drop-shadow(0 0)') ||
+ window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
+ ),
+ localFonts: typeof window.queryLocalFonts === 'function'
+ },
+ layoutType: 'normal',
+ globalNotices: [],
+ layoutHeight: 0,
+ lastTimeline: null
+ }),
+ actions: {
+ setPageTitle (option = '') {
+ try {
+ document.title = `${option} ${window.vuex.state.instance.name}`
+ } catch (error) {
+ console.error(`${error}`)
+ }
+ },
+ settingsSaved ({ success, error }) {
+ if (success) {
+ if (this.noticeClearTimeout) {
+ clearTimeout(this.noticeClearTimeout)
+ }
+ this.settings.currentSaveStateNotice = { error: false, data: success }
+ this.settings.noticeClearTimeout = setTimeout(() => delete this.settings.currentSaveStateNotice, 2000)
+ } else {
+ this.settings.currentSaveStateNotice = { error: true, errorData: error }
+ }
+ },
+ setNotificationPermission (permission) {
+ this.notificationPermission = permission
+ },
+ closeSettingsModal () {
+ this.settingsModalState = 'hidden'
+ },
+ openSettingsModal (value) {
+ this.settingsModalMode = value
+ this.settingsModalState = 'visible'
+ if (value === 'user') {
+ if (!this.settingsModalLoadedUser) {
+ this.settingsModalLoadedUser = true
+ }
+ } else if (value === 'admin') {
+ if (!this.settingsModalLoadedAdmin) {
+ this.settingsModalLoadedAdmin = true
+ }
+ }
+ },
+ togglePeekSettingsModal () {
+ switch (this.settingsModalState) {
+ case 'minimized':
+ this.settingsModalState = 'visible'
+ return
+ case 'visible':
+ this.settingsModalState = 'minimized'
+ return
+ default:
+ throw new Error('Illegal minimization state of settings modal')
+ }
+ },
+ clearSettingsModalTargetTab () {
+ this.settingsModalTargetTab = null
+ },
+ openSettingsModalTab (value, mode = 'user') {
+ this.settingsModalTargetTab = value
+ this.openSettingsModal(mode)
+ },
+ removeGlobalNotice (notice) {
+ this.globalNotices = this.globalNotices.filter(n => n !== notice)
+ },
+ pushGlobalNotice (
+ {
+ messageKey,
+ messageArgs = {},
+ level = 'error',
+ timeout = 0
+ }) {
+ const notice = {
+ messageKey,
+ messageArgs,
+ level
+ }
+
+ this.globalNotices.push(notice)
+
+ // Adding a new element to array wraps it in a Proxy, which breaks the comparison
+ // TODO: Generate UUID or something instead or relying on !== operator?
+ const newNotice = this.globalNotices[this.globalNotices.length - 1]
+ if (timeout) {
+ setTimeout(() => this.removeGlobalNotice(newNotice), timeout)
+ }
+
+ return newNotice
+ },
+ setLayoutHeight (value) {
+ this.layoutHeight = value
+ },
+ setLayoutWidth (value) {
+ let width = value
+ if (value !== undefined) {
+ this.layoutWidth = value
+ } else {
+ width = this.layoutWidth
+ }
+
+ const mobileLayout = width <= 800
+ const normalOrMobile = mobileLayout ? 'mobile' : 'normal'
+ const { thirdColumnMode } = window.vuex.getters.mergedConfig
+ if (thirdColumnMode === 'none' || !window.vuex.state.users.currentUser) {
+ this.layoutType = normalOrMobile
+ } else {
+ const wideLayout = width >= 1300
+ this.layoutType = wideLayout ? 'wide' : normalOrMobile
+ }
+ },
+ setFontsList (value) {
+ this.localFonts = [...(new Set(value.map(font => font.family))).values()]
+ },
+ queryLocalFonts () {
+ if (this.localFonts !== null) return
+ this.setFontsList([])
+
+ if (!this.browserSupport.localFonts) {
+ return
+ }
+ window
+ .queryLocalFonts()
+ .then((fonts) => {
+ this.setFontsList(fonts)
+ })
+ .catch((e) => {
+ this.pushGlobalNotice({
+ messageKey: 'settings.style.themes3.font.font_list_unavailable',
+ messageArgs: {
+ error: e
+ },
+ level: 'error'
+ })
+ })
+ },
+ setLastTimeline (value) {
+ this.lastTimeline = value
+ },
+ async fetchPalettesIndex () {
+ try {
+ const value = await getResourcesIndex('/static/palettes/index.json')
+ window.vuex.commit('setInstanceOption', { name: 'palettesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch palettes index', e)
+ window.vuex.commit('setInstanceOption', { name: 'palettesIndex', value: { _error: e } })
+ return Promise.resolve({})
+ }
+ },
+ setPalette (value) {
+ this.resetThemeV3Palette()
+ this.resetThemeV2()
+
+ window.vuex.commit('setOption', { name: 'palette', value })
+
+ this.applyTheme({ recompile: true })
+ },
+ setPaletteCustom (value) {
+ this.resetThemeV3Palette()
+ this.resetThemeV2()
+
+ window.vuex.commit('setOption', { name: 'paletteCustomData', value })
+
+ this.applyTheme({ recompile: true })
+ },
+ async fetchStylesIndex () {
+ try {
+ const value = await getResourcesIndex(
+ '/static/styles/index.json',
+ deserialize
+ )
+ window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch styles index', e)
+ window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value: { _error: e } })
+ return Promise.resolve({})
+ }
+ },
+ setStyle (value) {
+ this.resetThemeV3()
+ this.resetThemeV2()
+ this.resetThemeV3Palette()
+
+ window.vuex.commit('setOption', { name: 'style', value })
+ this.useStylePalette = true
+
+ this.applyTheme({ recompile: true }).then(() => {
+ this.useStylePalette = false
+ })
+ },
+ setStyleCustom (value) {
+ this.resetThemeV3()
+ this.resetThemeV2()
+ this.resetThemeV3Palette()
+
+ window.vuex.commit('setOption', { name: 'styleCustomData', value })
+
+ this.useStylePalette = true
+ this.applyTheme({ recompile: true }).then(() => {
+ this.useStylePalette = false
+ })
+ },
+ async fetchThemesIndex () {
+ try {
+ const value = await getResourcesIndex('/static/styles.json')
+ window.vuex.commit('setInstanceOption', { name: 'themesIndex', value })
+ return value
+ } catch (e) {
+ console.error('Could not fetch themes index', e)
+ window.vuex.commit('setInstanceOption', { name: 'themesIndex', value: { _error: e } })
+ return Promise.resolve({})
+ }
+ },
+ setTheme (value) {
+ this.resetThemeV3()
+ this.resetThemeV3Palette()
+ this.resetThemeV2()
+
+ window.vuex.commit('setOption', { name: 'theme', value })
+
+ this.applyTheme({ recompile: true })
+ },
+ setThemeCustom (value) {
+ this.resetThemeV3()
+ this.resetThemeV3Palette()
+ this.resetThemeV2()
+
+ window.vuex.commit('setOption', { name: 'customTheme', value })
+ window.vuex.commit('setOption', { name: 'customThemeSource', value })
+
+ this.applyTheme({ recompile: true })
+ },
+ resetThemeV3 () {
+ window.vuex.commit('setOption', { name: 'style', value: null })
+ window.vuex.commit('setOption', { name: 'styleCustomData', value: null })
+ },
+ resetThemeV3Palette () {
+ window.vuex.commit('setOption', { name: 'palette', value: null })
+ window.vuex.commit('setOption', { name: 'paletteCustomData', value: null })
+ },
+ resetThemeV2 () {
+ window.vuex.commit('setOption', { name: 'theme', value: null })
+ window.vuex.commit('setOption', { name: 'customTheme', value: null })
+ window.vuex.commit('setOption', { name: 'customThemeSource', value: null })
+ },
+ async getThemeData () {
+ const getData = async (resource, index, customData, name) => {
+ const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
+ const result = {}
+
+ if (customData) {
+ result.nameUsed = 'custom' // custom data overrides name
+ result.dataUsed = customData
+ } else {
+ result.nameUsed = name
+
+ if (result.nameUsed == null) {
+ result.dataUsed = null
+ return result
+ }
+
+ let fetchFunc = index[result.nameUsed]
+ // Fallbacks
+ if (!fetchFunc) {
+ if (resource === 'style' || resource === 'palette') {
+ return result
+ }
+ const newName = Object.keys(index)[0]
+ fetchFunc = index[newName]
+ console.warn(`${capitalizedResource} with id '${this.styleNameUsed}' not found, trying back to '${newName}'`)
+ if (!fetchFunc) {
+ console.warn(`${capitalizedResource} doesn't have a fallback, defaulting to stock.`)
+ fetchFunc = () => Promise.resolve(null)
+ }
+ }
+ result.dataUsed = await fetchFunc()
+ }
+ return result
+ }
+
+ const {
+ style: instanceStyleName,
+ palette: instancePaletteName
+ } = window.vuex.state.instance
+
+ let {
+ theme: instanceThemeV2Name,
+ themesIndex,
+ stylesIndex,
+ palettesIndex
+ } = window.vuex.state.instance
+
+ const {
+ style: userStyleName,
+ styleCustomData: userStyleCustomData,
+ palette: userPaletteName,
+ paletteCustomData: userPaletteCustomData
+ } = window.vuex.state.config
+
+ let {
+ theme: userThemeV2Name,
+ customTheme: userThemeV2Snapshot,
+ customThemeSource: userThemeV2Source
+ } = window.vuex.state.config
+
+ let majorVersionUsed
+
+ console.debug(
+ `User V3 palette: ${userPaletteName}, style: ${userStyleName} , custom: ${!!userStyleCustomData}`
+ )
+ console.debug(
+ `User V2 name: ${userThemeV2Name}, source: ${!!userThemeV2Source}, snapshot: ${!!userThemeV2Snapshot}`
+ )
+
+ console.debug(`Instance V3 palette: ${instancePaletteName}, style: ${instanceStyleName}`)
+ console.debug('Instance V2 theme: ' + instanceThemeV2Name)
+
+ if (userPaletteName || userPaletteCustomData ||
+ userStyleName || userStyleCustomData ||
+ (
+ // User V2 overrides instance V3
+ (instancePaletteName ||
+ instanceStyleName) &&
+ instanceThemeV2Name == null &&
+ userThemeV2Name == null
+ )
+ ) {
+ // Palette and/or style overrides V2 themes
+ instanceThemeV2Name = null
+ userThemeV2Name = null
+ userThemeV2Source = null
+ userThemeV2Snapshot = null
+
+ majorVersionUsed = 'v3'
+ } else if (
+ (userThemeV2Name ||
+ userThemeV2Snapshot ||
+ userThemeV2Source ||
+ instanceThemeV2Name)
+ ) {
+ majorVersionUsed = 'v2'
+ } else {
+ // if all fails fallback to v3
+ majorVersionUsed = 'v3'
+ }
+
+ if (majorVersionUsed === 'v3') {
+ const result = await Promise.all([
+ this.fetchPalettesIndex(),
+ this.fetchStylesIndex()
+ ])
+
+ palettesIndex = result[0]
+ stylesIndex = result[1]
+ } else {
+ // Promise.all just to be uniform with v3
+ const result = await Promise.all([
+ this.fetchThemesIndex()
+ ])
+
+ themesIndex = result[0]
+ }
+
+ this.themeVersion = majorVersionUsed
+
+ console.debug('Version used', majorVersionUsed)
+
+ if (majorVersionUsed === 'v3') {
+ this.themeDataUsed = null
+ this.themeNameUsed = null
+
+ const style = await getData(
+ 'style',
+ stylesIndex,
+ userStyleCustomData,
+ userStyleName || instanceStyleName
+ )
+ this.styleNameUsed = style.nameUsed
+ this.styleDataUsed = style.dataUsed
+
+ let firstStylePaletteName = null
+ style
+ .dataUsed
+ ?.filter(x => x.component === '@palette')
+ .map(x => {
+ const cleanDirectives = Object.fromEntries(
+ Object
+ .entries(x.directives)
+ .filter(([k, v]) => k)
+ )
+
+ return { name: x.variant, ...cleanDirectives }
+ })
+ .forEach(palette => {
+ const key = 'style.' + palette.name.toLowerCase().replace(/ /g, '_')
+ if (!firstStylePaletteName) firstStylePaletteName = key
+ palettesIndex[key] = () => Promise.resolve(palette)
+ })
+
+ const palette = await getData(
+ 'palette',
+ palettesIndex,
+ userPaletteCustomData,
+ this.useStylePalette ? firstStylePaletteName : (userPaletteName || instancePaletteName)
+ )
+
+ if (this.useStylePalette) {
+ window.vuex.commit('setOption', { name: 'palette', value: firstStylePaletteName })
+ }
+
+ this.paletteNameUsed = palette.nameUsed
+ this.paletteDataUsed = palette.dataUsed
+
+ if (this.paletteDataUsed) {
+ this.paletteDataUsed.link = this.paletteDataUsed.link || this.paletteDataUsed.accent
+ this.paletteDataUsed.accent = this.paletteDataUsed.accent || this.paletteDataUsed.link
+ }
+ if (Array.isArray(this.paletteDataUsed)) {
+ const [
+ name,
+ bg,
+ fg,
+ text,
+ link,
+ cRed = '#FF0000',
+ cGreen = '#00FF00',
+ cBlue = '#0000FF',
+ cOrange = '#E3FF00'
+ ] = palette.dataUsed
+ this.paletteDataUsed = {
+ name,
+ bg,
+ fg,
+ text,
+ link,
+ accent: link,
+ cRed,
+ cBlue,
+ cGreen,
+ cOrange
+ }
+ }
+ console.debug('Palette data used', palette.dataUsed)
+ } else {
+ this.styleNameUsed = null
+ this.styleDataUsed = null
+ this.paletteNameUsed = null
+ this.paletteDataUsed = null
+
+ const theme = await getData(
+ 'theme',
+ themesIndex,
+ userThemeV2Source || userThemeV2Snapshot,
+ userThemeV2Name || instanceThemeV2Name
+ )
+ this.themeNameUsed = theme.nameUsed
+ this.themeDataUsed = theme.dataUsed
+ }
+ },
+ async setThemeApplied () {
+ this.themeApplied = true
+ },
+ async applyTheme (
+ { recompile = false } = {}
+ ) {
+ const {
+ forceThemeRecompilation,
+ themeDebug,
+ theme3hacks
+ } = window.vuex.state.config
+ this.themeChangeInProgress = true
+ // If we're not not forced to recompile try using
+ // cache (tryLoadCache return true if load successful)
+
+ const forceRecompile = forceThemeRecompilation || recompile
+ if (!forceRecompile && !themeDebug && await tryLoadCache()) {
+ this.themeChangeInProgress = false
+ return this.setThemeApplied()
+ }
+ window.splashUpdate('splash.theme')
+ await this.getThemeData()
+
+ try {
+ const paletteIss = (() => {
+ if (!this.paletteDataUsed) return null
+ const result = {
+ component: 'Root',
+ directives: {}
+ }
+
+ Object
+ .entries(this.paletteDataUsed)
+ .filter(([k]) => k !== 'name')
+ .forEach(([k, v]) => {
+ let issRootDirectiveName
+ switch (k) {
+ case 'background':
+ issRootDirectiveName = 'bg'
+ break
+ case 'foreground':
+ issRootDirectiveName = 'fg'
+ break
+ default:
+ issRootDirectiveName = k
+ }
+ result.directives['--' + issRootDirectiveName] = 'color | ' + v
+ })
+ return result
+ })()
+
+ const theme2ruleset = this.themeDataUsed && convertTheme2To3(normalizeThemeData(this.themeDataUsed))
+ const hacks = []
+
+ Object.entries(theme3hacks).forEach(([key, value]) => {
+ switch (key) {
+ case 'fonts': {
+ Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
+ if (!font?.family) return
+ switch (fontKey) {
+ case 'interface':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'input':
+ hacks.push({
+ component: 'Input',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'post':
+ hacks.push({
+ component: 'RichContent',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'monospace':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--monoFont': 'generic | ' + font.family
+ }
+ })
+ break
+ }
+ })
+ break
+ }
+ case 'underlay': {
+ if (value !== 'none') {
+ const newRule = {
+ component: 'Underlay',
+ directives: {}
+ }
+ if (value === 'opaque') {
+ newRule.directives.opacity = 1
+ newRule.directives.background = '--wallpaper'
+ }
+ if (value === 'transparent') {
+ newRule.directives.opacity = 0
+ }
+ hacks.push(newRule)
+ }
+ break
+ }
+ }
+ })
+
+ const rulesetArray = [
+ theme2ruleset,
+ this.styleDataUsed,
+ paletteIss,
+ hacks
+ ].filter(x => x)
+
+ return applyTheme(
+ rulesetArray.flat(),
+ () => this.setThemeApplied(),
+ () => {
+ this.themeChangeInProgress = false
+ },
+ themeDebug
+ )
+ } catch (e) {
+ window.splashError(e)
+ }
+ }
+ }
+})
+
+export const normalizeThemeData = (input) => {
+ let themeData, themeSource
+
+ if (input.themeFileVerison === 1) {
+ // this might not be even used at all, some leftover of unimplemented code in V2 editor
+ return generatePreset(input).theme
+ } else if (
+ Object.prototype.hasOwnProperty.call(input, '_pleroma_theme_version') ||
+ Object.prototype.hasOwnProperty.call(input, 'source') ||
+ Object.prototype.hasOwnProperty.call(input, 'theme')
+ ) {
+ // We got passed a full theme file
+ themeData = input.theme
+ themeSource = input.source
+ } else if (
+ Object.prototype.hasOwnProperty.call(input, 'themeEngineVersion') ||
+ Object.prototype.hasOwnProperty.call(input, 'colors')
+ ) {
+ // We got passed a source/snapshot
+ themeData = input
+ themeSource = input
+ }
+ // New theme presets don't have 'theme' property, they use 'source'
+
+ let out // shout, shout let it all out
+ if (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION) {
+ // There are some themes in wild that have completely broken source
+ out = { ...(themeData || {}), ...themeSource }
+ } else {
+ out = themeData
+ }
+
+ // generatePreset here basically creates/updates "snapshot",
+ // while also fixing the 2.2 -> 2.3 colors/shadows/etc
+ return generatePreset(out).theme
+}
diff --git a/src/stores/lists.js b/src/stores/lists.js
@@ -0,0 +1,110 @@
+import { defineStore } from 'pinia'
+
+import { remove, find } from 'lodash'
+
+export const useListsStore = defineStore('lists', {
+ state: () => ({
+ allLists: [],
+ allListsObject: {}
+ }),
+ getters: {
+ findListTitle (state) {
+ return (id) => {
+ if (!this.allListsObject[id]) return
+ return this.allListsObject[id].title
+ }
+ },
+ findListAccounts (state) {
+ return (id) => [...this.allListsObject[id].accountIds]
+ }
+ },
+ actions: {
+ setLists (value) {
+ this.allLists = value
+ },
+ createList ({ title }) {
+ return window.vuex.state.api.backendInteractor.createList({ title })
+ .then((list) => {
+ this.setList({ listId: list.id, title })
+ return list
+ })
+ },
+ fetchList ({ listId }) {
+ return window.vuex.state.api.backendInteractor.getList({ listId })
+ .then((list) => this.setList({ listId: list.id, title: list.title }))
+ },
+ fetchListAccounts ({ listId }) {
+ return window.vuex.state.api.backendInteractor.getListAccounts({ listId })
+ .then((accountIds) => {
+ if (!this.allListsObject[listId]) {
+ this.allListsObject[listId] = { accountIds: [] }
+ }
+ this.allListsObject[listId].accountIds = accountIds
+ })
+ },
+ setList ({ listId, title }) {
+ if (!this.allListsObject[listId]) {
+ this.allListsObject[listId] = { accountIds: [] }
+ }
+ this.allListsObject[listId].title = title
+
+ const entry = find(this.allLists, { id: listId })
+ if (!entry) {
+ this.allLists.push({ id: listId, title })
+ } else {
+ entry.title = title
+ }
+ },
+ setListAccounts ({ listId, accountIds }) {
+ const saved = this.allListsObject[listId]?.accountIds || []
+ const added = accountIds.filter(id => !saved.includes(id))
+ const removed = saved.filter(id => !accountIds.includes(id))
+ if (!this.allListsObject[listId]) {
+ this.allListsObject[listId] = { accountIds: [] }
+ }
+ this.allListsObject[listId].accountIds = accountIds
+ if (added.length > 0) {
+ window.vuex.state.api.backendInteractor.addAccountsToList({ listId, accountIds: added })
+ }
+ if (removed.length > 0) {
+ window.vuex.state.api.backendInteractor.removeAccountsFromList({ listId, accountIds: removed })
+ }
+ },
+ addListAccount ({ listId, accountId }) {
+ return window.vuex.state
+ .api
+ .backendInteractor
+ .addAccountsToList({ listId, accountIds: [accountId] })
+ .then((result) => {
+ if (!this.allListsObject[listId]) {
+ this.allListsObject[listId] = { accountIds: [] }
+ }
+ this.allListsObject[listId].accountIds.push(accountId)
+ return result
+ })
+ },
+ removeListAccount ({ listId, accountId }) {
+ return window.vuex.state
+ .api
+ .backendInteractor
+ .removeAccountsFromList({ listId, accountIds: [accountId] })
+ .then((result) => {
+ if (!this.allListsObject[listId]) {
+ this.allListsObject[listId] = { accountIds: [] }
+ }
+ const { accountIds } = this.allListsObject[listId]
+ const set = new Set(accountIds)
+ set.delete(accountId)
+ this.allListsObject[listId].accountIds = [...set]
+
+ return result
+ })
+ },
+ deleteList ({ listId }) {
+ window.vuex.state.api.backendInteractor.deleteList({ listId })
+
+ delete this.allListsObject[listId]
+ remove(this.allLists, list => list.id === listId)
+ }
+ }
+})
diff --git a/src/stores/media_viewer.js b/src/stores/media_viewer.js
@@ -0,0 +1,30 @@
+import { defineStore } from 'pinia'
+import fileTypeService from '../services/file_type/file_type.service.js'
+
+const supportedTypes = new Set(['image', 'video', 'audio', 'flash'])
+
+export const useMediaViewerStore = defineStore('mediaViewer', {
+ state: () => ({
+ media: [],
+ currentIndex: 0,
+ activated: false
+ }),
+ actions: {
+ setMedia (attachments) {
+ const media = attachments.filter(attachment => {
+ const type = fileTypeService.fileType(attachment.mimetype)
+ return supportedTypes.has(type)
+ })
+
+ this.media = media
+ },
+ setCurrentMedia (current) {
+ const index = this.media.indexOf(current)
+ this.activated = true
+ this.currentIndex = index
+ },
+ closeMediaViewer () {
+ this.activated = false
+ }
+ }
+})
diff --git a/src/stores/polls.js b/src/stores/polls.js
@@ -0,0 +1,57 @@
+import { merge } from 'lodash'
+import { defineStore } from 'pinia'
+
+export const usePollsStore = defineStore('polls', {
+ state: () => ({
+ // Contains key = id, value = number of trackers for this poll
+ trackedPolls: {},
+ pollsObject: {}
+ }),
+ actions: {
+ mergeOrAddPoll (poll) {
+ const existingPoll = this.pollsObject[poll.id]
+ // Make expired-state change trigger re-renders properly
+ poll.expired = Date.now() > Date.parse(poll.expires_at)
+ if (existingPoll) {
+ this.pollsObject[poll.id] = merge(existingPoll, poll)
+ } else {
+ this.pollsObject[poll.id] = poll
+ }
+ },
+ updateTrackedPoll (pollId) {
+ window.vuex.state.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
+ setTimeout(() => {
+ if (this.trackedPolls[pollId]) {
+ this.updateTrackedPoll(pollId)
+ }
+ }, 30 * 1000)
+ this.mergeOrAddPoll(poll)
+ })
+ },
+ trackPoll (pollId) {
+ if (!this.trackedPolls[pollId]) {
+ setTimeout(() => this.updateTrackedPoll(pollId), 30 * 1000)
+ }
+ const currentValue = this.trackedPolls[pollId]
+ if (currentValue) {
+ this.trackedPolls[pollId] = currentValue + 1
+ } else {
+ this.trackedPolls[pollId] = 1
+ }
+ },
+ untrackPoll (pollId) {
+ const currentValue = this.trackedPolls[pollId]
+ if (currentValue) {
+ this.trackedPolls[pollId] = currentValue - 1
+ } else {
+ this.trackedPolls[pollId] = 0
+ }
+ },
+ votePoll ({ id, pollId, choices }) {
+ return window.vuex.state.api.backendInteractor.vote({ pollId, choices }).then(poll => {
+ this.mergeOrAddPoll(poll)
+ return poll
+ })
+ }
+ }
+})
diff --git a/src/stores/postStatus.js b/src/stores/postStatus.js
@@ -0,0 +1,17 @@
+import { defineStore } from 'pinia'
+
+export const usePostStatusStore = defineStore('postStatus', {
+ state: () => ({
+ params: null,
+ modalActivated: false
+ }),
+ actions: {
+ openPostStatusModal (params) {
+ this.params = params
+ this.modalActivated = true
+ },
+ closePostStatusModal () {
+ this.modalActivated = false
+ }
+ }
+})
diff --git a/src/stores/reports.js b/src/stores/reports.js
@@ -0,0 +1,52 @@
+import { defineStore } from 'pinia'
+
+import filter from 'lodash/filter'
+import { useInterfaceStore } from 'src/stores/interface'
+
+export const useReportsStore = defineStore('reports', {
+ state: () => ({
+ reportModal: {
+ userId: null,
+ statuses: [],
+ preTickedIds: [],
+ activated: false
+ },
+ reports: {}
+ }),
+ actions: {
+ openUserReportingModal ({ userId, statusIds = [] }) {
+ const preTickedStatuses = statusIds.map(id => window.vuex.state.statuses.allStatusesObject[id])
+ const preTickedIds = statusIds
+ const statuses = preTickedStatuses.concat(
+ filter(window.vuex.state.statuses.allStatuses,
+ status => status.user.id === userId && !preTickedIds.includes(status.id)
+ )
+ )
+
+ this.reportModal.userId = userId
+ this.reportModal.statuses = statuses
+ this.reportModal.preTickedIds = preTickedIds
+ this.reportModal.activated = true
+ },
+ closeUserReportingModal () {
+ this.reportModal.activated = false
+ },
+ setReportState ({ id, state }) {
+ const oldState = window.vuex.state.reports.reports[id].state
+ this.reports[id].state = state
+ window.vuex.state.api.backendInteractor.setReportState({ id, state }).catch(e => {
+ console.error('Failed to set report state', e)
+ useInterfaceStore().pushGlobalNotice({
+ level: 'error',
+ messageKey: 'general.generic_error_message',
+ messageArgs: [e.message],
+ timeout: 5000
+ })
+ this.reports[id].state = oldState
+ })
+ },
+ addReport (report) {
+ this.reports[report.id] = report
+ }
+ }
+})
diff --git a/src/stores/shout.js b/src/stores/shout.js
@@ -0,0 +1,32 @@
+import { defineStore } from 'pinia'
+
+export const useShoutStore = defineStore('shout', {
+ state: () => ({
+ messages: [],
+ channel: { state: '' },
+ joined: false
+ }),
+ actions: {
+ initializeShout (socket) {
+ const channel = socket.channel('chat:public')
+ channel.joinPush.receive('ok', () => {
+ this.joined = true
+ })
+ channel.onClose(() => {
+ this.joined = false
+ })
+ channel.onError(() => {
+ this.joined = false
+ })
+ channel.on('new_msg', (msg) => {
+ this.messages.push(msg)
+ this.messages = this.messages.slice(-19, 20)
+ })
+ channel.on('messages', ({ messages }) => {
+ this.messages = messages.slice(-19, 20)
+ })
+ channel.join()
+ this.channel = channel
+ }
+ }
+})
diff --git a/src/stores/statusHistory.js b/src/stores/statusHistory.js
@@ -0,0 +1,17 @@
+import { defineStore } from 'pinia'
+
+export const useStatusHistoryStore = defineStore('statusHistory', {
+ state: () => ({
+ params: {},
+ modalActivated: false
+ }),
+ actions: {
+ openStatusHistoryModal (params) {
+ this.params = params
+ this.modalActivated = true
+ },
+ closeStatusHistoryModal () {
+ this.modalActivated = false
+ }
+ }
+})
diff --git a/test/unit/specs/modules/lists.spec.js b/test/unit/specs/modules/lists.spec.js
@@ -1,83 +0,0 @@
-import { cloneDeep } from 'lodash'
-import { defaultState, mutations, getters } from '../../../../src/modules/lists.js'
-
-describe('The lists module', () => {
- describe('mutations', () => {
- it('updates array of all lists', () => {
- const state = cloneDeep(defaultState)
- const list = { id: '1', title: 'testList' }
-
- mutations.setLists(state, [list])
- expect(state.allLists).to.have.length(1)
- expect(state.allLists).to.eql([list])
- })
-
- it('adds a new list with a title, updating the title for existing lists', () => {
- const state = cloneDeep(defaultState)
- const list = { id: '1', title: 'testList' }
- const modList = { id: '1', title: 'anotherTestTitle' }
-
- mutations.setList(state, { listId: list.id, title: list.title })
- expect(state.allListsObject[list.id]).to.eql({ title: list.title, accountIds: [] })
- expect(state.allLists).to.have.length(1)
- expect(state.allLists[0]).to.eql(list)
-
- mutations.setList(state, { listId: modList.id, title: modList.title })
- expect(state.allListsObject[modList.id]).to.eql({ title: modList.title, accountIds: [] })
- expect(state.allLists).to.have.length(1)
- expect(state.allLists[0]).to.eql(modList)
- })
-
- it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
- const state = cloneDeep(defaultState)
- const list = { id: '1', accountIds: ['1', '2', '3'] }
- const modList = { id: '1', accountIds: ['3', '4', '5'] }
-
- mutations.setListAccounts(state, { listId: list.id, accountIds: list.accountIds })
- expect(state.allListsObject[list.id]).to.eql({ accountIds: list.accountIds })
-
- mutations.setListAccounts(state, { listId: modList.id, accountIds: modList.accountIds })
- expect(state.allListsObject[modList.id]).to.eql({ accountIds: modList.accountIds })
- })
-
- it('deletes a list', () => {
- const state = {
- allLists: [{ id: '1', title: 'testList' }],
- allListsObject: {
- 1: { title: 'testList', accountIds: ['1', '2', '3'] }
- }
- }
- const listId = '1'
-
- mutations.deleteList(state, { listId })
- expect(state.allLists).to.have.length(0)
- expect(state.allListsObject).to.eql({})
- })
- })
-
- describe('getters', () => {
- it('returns list title', () => {
- const state = {
- allLists: [{ id: '1', title: 'testList' }],
- allListsObject: {
- 1: { title: 'testList', accountIds: ['1', '2', '3'] }
- }
- }
- const id = '1'
-
- expect(getters.findListTitle(state)(id)).to.eql('testList')
- })
-
- it('returns list accounts', () => {
- const state = {
- allLists: [{ id: '1', title: 'testList' }],
- allListsObject: {
- 1: { title: 'testList', accountIds: ['1', '2', '3'] }
- }
- }
- const id = '1'
-
- expect(getters.findListAccounts(state)(id)).to.eql(['1', '2', '3'])
- })
- })
-})
diff --git a/test/unit/specs/modules/serverSideStorage.spec.js b/test/unit/specs/modules/serverSideStorage.spec.js
@@ -74,7 +74,7 @@ describe('The serverSideStorage module', () => {
})
})
- it.only('should reset local timestamp to remote if contents are the same', () => {
+ it('should reset local timestamp to remote if contents are the same', () => {
const state = {
...cloneDeep(defaultState),
cache: null
diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js
@@ -0,0 +1,93 @@
+import { createPinia, setActivePinia } from 'pinia'
+import { useListsStore } from 'src/stores/lists.js'
+import { createStore } from 'vuex'
+import apiModule from 'src/modules/api.js'
+
+setActivePinia(createPinia())
+const store = useListsStore()
+window.vuex = createStore({
+ modules: {
+ api: apiModule
+ }
+})
+
+describe('The lists store', () => {
+ describe('actions', () => {
+ it('updates array of all lists', () => {
+ store.$reset()
+ const list = { id: '1', title: 'testList' }
+
+ store.setLists([list])
+ expect(store.allLists).to.have.length(1)
+ expect(store.allLists).to.eql([list])
+ })
+
+ it('adds a new list with a title, updating the title for existing lists', () => {
+ store.$reset()
+ const list = { id: '1', title: 'testList' }
+ const modList = { id: '1', title: 'anotherTestTitle' }
+
+ store.setList({ listId: list.id, title: list.title })
+ expect(store.allListsObject[list.id]).to.eql({ title: list.title, accountIds: [] })
+ expect(store.allLists).to.have.length(1)
+ expect(store.allLists[0]).to.eql(list)
+
+ store.setList({ listId: modList.id, title: modList.title })
+ expect(store.allListsObject[modList.id]).to.eql({ title: modList.title, accountIds: [] })
+ expect(store.allLists).to.have.length(1)
+ expect(store.allLists[0]).to.eql(modList)
+ })
+
+ it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
+ store.$reset()
+ const list = { id: '1', accountIds: ['1', '2', '3'] }
+ const modList = { id: '1', accountIds: ['3', '4', '5'] }
+
+ store.setListAccounts({ listId: list.id, accountIds: list.accountIds })
+ expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds)
+
+ store.setListAccounts({ listId: modList.id, accountIds: modList.accountIds })
+ expect(store.allListsObject[modList.id].accountIds).to.eql(modList.accountIds)
+ })
+
+ it('deletes a list', () => {
+ store.$patch({
+ allLists: [{ id: '1', title: 'testList' }],
+ allListsObject: {
+ 1: { title: 'testList', accountIds: ['1', '2', '3'] }
+ }
+ })
+ const listId = '1'
+
+ store.deleteList({ listId })
+ expect(store.allLists).to.have.length(0)
+ expect(store.allListsObject).to.eql({})
+ })
+ })
+
+ describe('getters', () => {
+ it('returns list title', () => {
+ store.$patch({
+ allLists: [{ id: '1', title: 'testList' }],
+ allListsObject: {
+ 1: { title: 'testList', accountIds: ['1', '2', '3'] }
+ }
+ })
+ const id = '1'
+
+ expect(store.findListTitle(id)).to.eql('testList')
+ })
+
+ it('returns list accounts', () => {
+ store.$patch({
+ allLists: [{ id: '1', title: 'testList' }],
+ allListsObject: {
+ 1: { title: 'testList', accountIds: ['1', '2', '3'] }
+ }
+ })
+ const id = '1'
+
+ expect(store.findListAccounts(id)).to.eql(['1', '2', '3'])
+ })
+ })
+})
diff --git a/yarn.lock b/yarn.lock
@@ -1521,7 +1521,7 @@
"@vue/compiler-dom" "3.5.13"
"@vue/shared" "3.5.13"
-"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.4":
+"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.3", "@vue/devtools-api@^6.6.4":
version "6.6.4"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
@@ -6609,6 +6609,14 @@ pify@^4.0.1:
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
+pinia@^2.0.33:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.3.1.tgz#54c476675b72f5abcfafa24a7582531ea8c23d94"
+ integrity sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==
+ dependencies:
+ "@vue/devtools-api" "^6.6.3"
+ vue-demi "^0.14.10"
+
pirates@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
@@ -8505,6 +8513,11 @@ vue-demi@^0.13.11:
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
+vue-demi@^0.14.10:
+ version "0.14.10"
+ resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04"
+ integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==
+
vue-eslint-parser@^9.4.3:
version "9.4.3"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz#9b04b22c71401f1e8bca9be7c3e3416a4bde76a8"