biome format --write

This commit is contained in:
Henry Jameson 2026-01-06 16:22:52 +02:00
commit 9262e803ec
415 changed files with 54076 additions and 17419 deletions

View file

@ -8,24 +8,24 @@ export const defaultState = {
modifiedPaths: null,
descriptions: null,
draft: null,
dbConfigEnabled: null
dbConfigEnabled: null,
}
export const newUserFlags = {
...defaultState.flagStorage
...defaultState.flagStorage,
}
const adminSettingsStorage = {
state: {
...cloneDeep(defaultState)
...cloneDeep(defaultState),
},
mutations: {
setInstanceAdminNoDbConfig (state) {
setInstanceAdminNoDbConfig(state) {
state.loaded = false
state.dbConfigEnabled = false
},
setAvailableFrontends (state, { frontends }) {
state.frontends = frontends.map(f => {
setAvailableFrontends(state, { frontends }) {
state.frontends = frontends.map((f) => {
f.installedRefs = f.installed_refs
if (f.name === 'pleroma-fe') {
f.refs = ['master', 'develop']
@ -35,16 +35,16 @@ const adminSettingsStorage = {
return f
})
},
updateAdminSettings (state, { config, modifiedPaths }) {
updateAdminSettings(state, { config, modifiedPaths }) {
state.loaded = true
state.dbConfigEnabled = true
state.config = config
state.modifiedPaths = modifiedPaths
},
updateAdminDescriptions (state, { descriptions }) {
updateAdminDescriptions(state, { descriptions }) {
state.descriptions = descriptions
},
updateAdminDraft (state, { path, value }) {
updateAdminDraft(state, { path, value }) {
const [group, key, subkey] = path
const parent = [group, key, subkey]
@ -55,21 +55,23 @@ const adminSettingsStorage = {
set(state.draft, parent, cloneDeep(get(state.draft, parent)))
}
},
resetAdminDraft (state) {
resetAdminDraft(state) {
state.draft = cloneDeep(state.config)
}
},
},
actions: {
loadFrontendsStuff ({ rootState, commit }) {
rootState.api.backendInteractor.fetchAvailableFrontends()
.then(frontends => commit('setAvailableFrontends', { frontends }))
loadFrontendsStuff({ rootState, commit }) {
rootState.api.backendInteractor
.fetchAvailableFrontends()
.then((frontends) => commit('setAvailableFrontends', { frontends }))
},
loadAdminStuff ({ state, rootState, dispatch, commit }) {
rootState.api.backendInteractor.fetchInstanceDBConfig()
.then(backendDbConfig => {
loadAdminStuff({ state, rootState, dispatch, commit }) {
rootState.api.backendInteractor
.fetchInstanceDBConfig()
.then((backendDbConfig) => {
if (backendDbConfig.error) {
if (backendDbConfig.error.status === 400) {
backendDbConfig.error.json().then(errorJson => {
backendDbConfig.error.json().then((errorJson) => {
if (/configurable_from_database/.test(errorJson.error)) {
commit('setInstanceAdminNoDbConfig')
}
@ -80,20 +82,23 @@ const adminSettingsStorage = {
}
})
if (state.descriptions === null) {
rootState.api.backendInteractor.fetchInstanceConfigDescriptions()
.then(backendDescriptions => dispatch('setInstanceAdminDescriptions', { backendDescriptions }))
rootState.api.backendInteractor
.fetchInstanceConfigDescriptions()
.then((backendDescriptions) =>
dispatch('setInstanceAdminDescriptions', { backendDescriptions }),
)
}
},
setInstanceAdminSettings ({ state, commit }, { backendDbConfig }) {
setInstanceAdminSettings({ state, commit }, { backendDbConfig }) {
const config = state.config || {}
const modifiedPaths = new Set()
backendDbConfig.configs.forEach(c => {
backendDbConfig.configs.forEach((c) => {
const path = [c.group, c.key]
if (c.db) {
// Path elements can contain dot, therefore we use ' -> ' as a separator instead
// Using strings for modified paths for easier searching
c.db.forEach(x => modifiedPaths.add([...path, x].join(' -> ')))
c.db.forEach((x) => modifiedPaths.add([...path, x].join(' -> ')))
}
// we need to preserve tuples on second level only, possibly third
@ -102,10 +107,13 @@ const adminSettingsStorage = {
if (Array.isArray(value) && value.length > 0 && value[0].tuple) {
if (!preserveTuples) {
return value.reduce((acc, c) => {
return { ...acc, [c.tuple[0]]: convert(c.tuple[1], preserveTuplesLv2) }
return {
...acc,
[c.tuple[0]]: convert(c.tuple[1], preserveTuplesLv2),
}
}, {})
} else {
return value.map(x => x.tuple)
return value.map((x) => x.tuple)
}
} else {
if (!preserveTuples) {
@ -120,7 +128,7 @@ const adminSettingsStorage = {
// so for those cases we want to preserve tuples as-is
// right now it's made exclusively for :pleroma.:rate_limit
// so it might not work properly elsewhere
const preserveTuples = path.find(x => x === ':rate_limit')
const preserveTuples = path.find((x) => x === ':rate_limit')
set(config, path, convert(c.value, false, preserveTuples))
})
// patching http adapter config to be easier to handle
@ -128,19 +136,23 @@ const adminSettingsStorage = {
if (Array.isArray(adapter)) {
config[':pleroma'][':http'][':adapter'] = {
[':ssl_options']: {
[':versions']: []
}
[':versions']: [],
},
}
}
commit('updateAdminSettings', { config, modifiedPaths })
commit('resetAdminDraft')
},
setInstanceAdminDescriptions ({ commit }, { backendDescriptions }) {
const convert = ({ children, description, label, key = '<ROOT>', group, suggestions }, path, acc) => {
setInstanceAdminDescriptions({ commit }, { backendDescriptions }) {
const convert = (
{ children, description, label, key = '<ROOT>', group, suggestions },
path,
acc,
) => {
const newPath = group ? [group, key] : [key]
const obj = { description, label, suggestions }
if (Array.isArray(children)) {
children.forEach(c => {
children.forEach((c) => {
convert(c, newPath, obj)
})
}
@ -149,13 +161,13 @@ const adminSettingsStorage = {
const descriptions = {}
backendDescriptions.forEach(d => convert(d, '', descriptions))
backendDescriptions.forEach((d) => convert(d, '', descriptions))
commit('updateAdminDescriptions', { descriptions })
},
// This action takes draft state, diffs it with live config state and then pushes
// only differences between the two. Difference detection only work up to subkey (third) level.
pushAdminDraft ({ rootState, state, dispatch }) {
pushAdminDraft({ rootState, state, dispatch }) {
// TODO cleanup paths in modifiedPaths
const convert = (value) => {
if (typeof value !== 'object') {
@ -169,13 +181,9 @@ const adminSettingsStorage = {
// Getting all group-keys used in config
const allGroupKeys = flatten(
Object
.entries(state.config)
.map(
([group, lv1data]) => Object
.keys(lv1data)
.map((key) => ({ group, key }))
)
Object.entries(state.config).map(([group, lv1data]) =>
Object.keys(lv1data).map((key) => ({ group, key })),
),
)
// Only using group-keys where there are changes detected
@ -194,19 +202,30 @@ const adminSettingsStorage = {
// Then those entries array we diff so only changed subkey entries remain
// We use the diffed array to reconstruct the object and then shove it into convert()
return ({ group, key, value: convert(Object.fromEntries(differenceWith(eDraft, eConfig, isEqual))) })
})
rootState.api.backendInteractor.pushInstanceDBConfig({
payload: {
configs: changed
return {
group,
key,
value: convert(
Object.fromEntries(differenceWith(eDraft, eConfig, isEqual)),
),
}
})
rootState.api.backendInteractor
.pushInstanceDBConfig({
payload: {
configs: changed,
},
})
.then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
.then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
.then((backendDbConfig) =>
dispatch('setInstanceAdminSettings', { backendDbConfig }),
)
},
pushAdminSetting ({ rootState, dispatch }, { path, value }) {
const [group, key, ...rest] = Array.isArray(path) ? path : path.split(/\./g)
pushAdminSetting({ rootState, dispatch }, { path, value }) {
const [group, key, ...rest] = Array.isArray(path)
? path
: path.split(/\./g)
const clone = {} // not actually cloning the entire thing to avoid excessive writes
set(clone, rest, value)
@ -221,37 +240,49 @@ const adminSettingsStorage = {
}
}
rootState.api.backendInteractor.pushInstanceDBConfig({
payload: {
configs: [{
group,
key,
value: convert(clone)
}]
}
})
rootState.api.backendInteractor
.pushInstanceDBConfig({
payload: {
configs: [
{
group,
key,
value: convert(clone),
},
],
},
})
.then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
.then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
.then((backendDbConfig) =>
dispatch('setInstanceAdminSettings', { backendDbConfig }),
)
},
resetAdminSetting ({ rootState, state, dispatch }, { path }) {
const [group, key, subkey] = Array.isArray(path) ? path : path.split(/\./g)
resetAdminSetting({ rootState, state, dispatch }, { path }) {
const [group, key, subkey] = Array.isArray(path)
? path
: path.split(/\./g)
state.modifiedPaths.delete(path)
return rootState.api.backendInteractor.pushInstanceDBConfig({
payload: {
configs: [{
group,
key,
delete: true,
subkeys: [subkey]
}]
}
})
return rootState.api.backendInteractor
.pushInstanceDBConfig({
payload: {
configs: [
{
group,
key,
delete: true,
subkeys: [subkey],
},
],
},
})
.then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
.then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
}
}
.then((backendDbConfig) =>
dispatch('setInstanceAdminSettings', { backendDbConfig }),
)
},
},
}
export default adminSettingsStorage

View file

@ -15,40 +15,40 @@ const api = {
socket: null,
mastoUserSocket: null,
mastoUserSocketStatus: null,
followRequests: []
followRequests: [],
},
getters: {
followRequestCount: state => state.followRequests.length
followRequestCount: (state) => state.followRequests.length,
},
mutations: {
setBackendInteractor (state, backendInteractor) {
setBackendInteractor(state, backendInteractor) {
state.backendInteractor = backendInteractor
},
addFetcher (state, { fetcherName, fetcher }) {
addFetcher(state, { fetcherName, fetcher }) {
state.fetchers[fetcherName] = fetcher
},
removeFetcher (state, { fetcherName }) {
removeFetcher(state, { fetcherName }) {
state.fetchers[fetcherName].stop()
delete state.fetchers[fetcherName]
},
setWsToken (state, token) {
setWsToken(state, token) {
state.wsToken = token
},
setSocket (state, socket) {
setSocket(state, socket) {
state.socket = socket
},
setFollowRequests (state, value) {
setFollowRequests(state, value) {
state.followRequests = value
},
setMastoUserSocketStatus (state, value) {
setMastoUserSocketStatus(state, value) {
state.mastoUserSocketStatus = value
},
incrementRetryMultiplier (state) {
incrementRetryMultiplier(state) {
state.retryMultiplier = Math.max(++state.retryMultiplier, 3)
},
resetRetryMultiplier (state) {
resetRetryMultiplier(state) {
state.retryMultiplier = 1
}
},
},
actions: {
/**
@ -56,15 +56,14 @@ const api = {
*
* @param {Boolean} [initial] - whether this enabling happened at boot time or not
*/
enableMastoSockets (store, initial) {
enableMastoSockets(store, initial) {
const { state, dispatch, commit } = store
// Do not initialize unless nonexistent or closed
if (
state.mastoUserSocket &&
![
WebSocket.CLOSED,
WebSocket.CLOSING
].includes(state.mastoUserSocket.getState())
![WebSocket.CLOSED, WebSocket.CLOSING].includes(
state.mastoUserSocket.getState(),
)
) {
return
}
@ -75,7 +74,7 @@ const api = {
}
return dispatch('startMastoUserSocket')
},
disableMastoSockets (store) {
disableMastoSockets(store) {
const { state, dispatch, commit } = store
if (!state.mastoUserSocket) return
commit('setMastoUserSocketStatus', WSConnectionStatus.DISABLED)
@ -83,15 +82,20 @@ const api = {
},
// MastoAPI 'User' sockets
startMastoUserSocket (store) {
startMastoUserSocket(store) {
return new Promise((resolve, reject) => {
try {
const { state, commit, dispatch, rootState } = store
const timelineData = rootState.statuses.timelines.friends
state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
state.mastoUserSocket.addEventListener('pleroma:authenticated', () => {
state.mastoUserSocket.subscribe('user')
state.mastoUserSocket = state.backendInteractor.startUserSocket({
store,
})
state.mastoUserSocket.addEventListener(
'pleroma:authenticated',
() => {
state.mastoUserSocket.subscribe('user')
},
)
state.mastoUserSocket.addEventListener(
'message',
({ detail: message }) => {
@ -99,21 +103,22 @@ const api = {
if (message.event === 'notification') {
dispatch('addNewNotifications', {
notifications: [message.notification],
older: false
older: false,
})
} else if (message.event === 'update') {
dispatch('addNewStatuses', {
statuses: [message.status],
userId: false,
showImmediately: timelineData.visibleStatuses.length === 0,
timeline: 'friends'
timeline: 'friends',
})
} else if (message.event === 'status.update') {
dispatch('addNewStatuses', {
statuses: [message.status],
userId: false,
showImmediately: message.status.id in timelineData.visibleStatusesObject,
timeline: 'friends'
showImmediately:
message.status.id in timelineData.visibleStatusesObject,
timeline: 'friends',
})
} else if (message.event === 'delete') {
dispatch('deleteStatusById', message.id)
@ -125,28 +130,33 @@ const api = {
setTimeout(() => {
dispatch('addChatMessages', {
chatId: message.chatUpdate.id,
messages: [message.chatUpdate.lastMessage]
messages: [message.chatUpdate.lastMessage],
})
dispatch('updateChat', { chat: message.chatUpdate })
maybeShowChatNotification(store, message.chatUpdate)
}, 100)
}
}
},
)
state.mastoUserSocket.addEventListener('open', () => {
// Do not show notification when we just opened up the page
if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) {
if (
state.mastoUserSocketStatus !==
WSConnectionStatus.STARTING_INITIAL
) {
useInterfaceStore().pushGlobalNotice({
level: 'success',
messageKey: 'timeline.socket_reconnected',
timeout: 5000
timeout: 5000,
})
}
// Stop polling if we were errored or disabled
if (new Set([
WSConnectionStatus.ERROR,
WSConnectionStatus.DISABLED
]).has(state.mastoUserSocketStatus)) {
if (
new Set([
WSConnectionStatus.ERROR,
WSConnectionStatus.DISABLED,
]).has(state.mastoUserSocketStatus)
) {
dispatch('stopFetchingTimeline', { timeline: 'friends' })
dispatch('stopFetchingNotifications')
dispatch('stopFetchingChats')
@ -154,48 +164,58 @@ const api = {
commit('resetRetryMultiplier')
commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)
})
state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
console.error('Error in MastoAPI websocket:', error)
// TODO is this needed?
dispatch('clearOpenedChats')
})
state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
const ignoreCodes = new Set([
1000, // Normal (intended) closure
1001 // Going away
])
const { code } = closeEvent
if (ignoreCodes.has(code)) {
console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
} else {
console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
setTimeout(() => {
dispatch('startMastoUserSocket')
}, retryTimeout(state.retryMultiplier))
commit('incrementRetryMultiplier')
if (state.mastoUserSocketStatus !== WSConnectionStatus.ERROR) {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
dispatch('startFetchingChats')
useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'timeline.socket_broke',
messageArgs: [code],
timeout: 5000
})
state.mastoUserSocket.addEventListener(
'error',
({ detail: error }) => {
console.error('Error in MastoAPI websocket:', error)
// TODO is this needed?
dispatch('clearOpenedChats')
},
)
state.mastoUserSocket.addEventListener(
'close',
({ detail: closeEvent }) => {
const ignoreCodes = new Set([
1000, // Normal (intended) closure
1001, // Going away
])
const { code } = closeEvent
if (ignoreCodes.has(code)) {
console.debug(
`Not restarting socket becasue of closure code ${code} is in ignore list`,
)
commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
} else {
console.warn(
`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`,
)
setTimeout(() => {
dispatch('startMastoUserSocket')
}, retryTimeout(state.retryMultiplier))
commit('incrementRetryMultiplier')
if (state.mastoUserSocketStatus !== WSConnectionStatus.ERROR) {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
dispatch('startFetchingChats')
useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'timeline.socket_broke',
messageArgs: [code],
timeout: 5000,
})
}
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
}
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
}
dispatch('clearOpenedChats')
})
dispatch('clearOpenedChats')
},
)
resolve()
} catch (e) {
reject(e)
}
})
},
stopMastoUserSocket ({ state, dispatch }) {
stopMastoUserSocket({ state, dispatch }) {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
dispatch('startFetchingChats')
@ -203,103 +223,127 @@ const api = {
},
// Timelines
startFetchingTimeline (store, {
timeline = 'friends',
tag = false,
userId = false,
listId = false,
statusId = false,
bookmarkFolderId = false
}) {
if (timeline === 'favourites' && !store.rootState.instance.pleromaPublicFavouritesAvailable) return
startFetchingTimeline(
store,
{
timeline = 'friends',
tag = false,
userId = false,
listId = false,
statusId = false,
bookmarkFolderId = false,
},
) {
if (
timeline === 'favourites' &&
!store.rootState.instance.pleromaPublicFavouritesAvailable
)
return
if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({
timeline, store, userId, listId, statusId, bookmarkFolderId, tag
timeline,
store,
userId,
listId,
statusId,
bookmarkFolderId,
tag,
})
store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
stopFetchingTimeline (store, timeline) {
stopFetchingTimeline(store, timeline) {
const fetcher = store.state.fetchers[timeline]
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: timeline, fetcher })
},
fetchTimeline (store, { timeline, ...rest }) {
fetchTimeline(store, { timeline, ...rest }) {
store.state.backendInteractor.fetchTimeline({
store,
timeline,
...rest
...rest,
})
},
// Notifications
startFetchingNotifications (store) {
startFetchingNotifications(store) {
if (store.state.fetchers.notifications) return
const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
const fetcher = store.state.backendInteractor.startFetchingNotifications({
store,
})
store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
},
stopFetchingNotifications (store) {
stopFetchingNotifications(store) {
const fetcher = store.state.fetchers.notifications
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
},
fetchNotifications (store, { ...rest }) {
fetchNotifications(store, { ...rest }) {
store.state.backendInteractor.fetchNotifications({
store,
...rest
...rest,
})
},
// Follow requests
startFetchingFollowRequests (store) {
startFetchingFollowRequests(store) {
if (store.state.fetchers.followRequests) return
const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
const fetcher = store.state.backendInteractor.startFetchingFollowRequests(
{ store },
)
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
},
stopFetchingFollowRequests (store) {
stopFetchingFollowRequests(store) {
const fetcher = store.state.fetchers.followRequests
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
},
removeFollowRequest (store, request) {
removeFollowRequest(store, request) {
const requests = store.state.followRequests.filter((it) => it !== request)
store.commit('setFollowRequests', requests)
},
// Lists
startFetchingLists (store) {
startFetchingLists(store) {
if (store.state.fetchers.lists) return
const fetcher = store.state.backendInteractor.startFetchingLists({ store })
const fetcher = store.state.backendInteractor.startFetchingLists({
store,
})
store.commit('addFetcher', { fetcherName: 'lists', fetcher })
},
stopFetchingLists (store) {
stopFetchingLists(store) {
const fetcher = store.state.fetchers.lists
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'lists', fetcher })
},
// Bookmark folders
startFetchingBookmarkFolders (store) {
startFetchingBookmarkFolders(store) {
if (store.state.fetchers.bookmarkFolders) return
if (!store.rootState.instance.pleromaBookmarkFoldersAvailable) return
const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store })
const fetcher =
store.state.backendInteractor.startFetchingBookmarkFolders({ store })
store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher })
},
stopFetchingBookmarkFolders (store) {
stopFetchingBookmarkFolders(store) {
const fetcher = store.state.fetchers.bookmarkFolders
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'bookmarkFolders', fetcher })
},
// Pleroma websocket
setWsToken (store, token) {
setWsToken(store, token) {
store.commit('setWsToken', token)
},
initializeSocket ({ commit, state, rootState }) {
initializeSocket({ commit, state, rootState }) {
// Set up websocket connection
const token = state.wsToken
if (rootState.instance.shoutAvailable && typeof token !== 'undefined' && state.socket === null) {
if (
rootState.instance.shoutAvailable &&
typeof token !== 'undefined' &&
state.socket === null
) {
const socket = new Socket('/socket', { params: { token } })
socket.connect()
@ -307,11 +351,11 @@ const api = {
useShoutStore().initializeShout(socket)
}
},
disconnectFromSocket ({ commit, state }) {
disconnectFromSocket({ commit, state }) {
state.socket && state.socket.disconnect()
commit('setSocket', null)
}
}
},
},
}
export default api

View file

@ -1,13 +1,16 @@
import { reactive } from 'vue'
import { find, omitBy, orderBy, sumBy } from 'lodash'
import chatService from '../services/chat_service/chat_service.js'
import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js'
import {
parseChat,
parseChatMessage,
} from '../services/entity_normalizer/entity_normalizer.service.js'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
import { promiseInterval } from '../services/promise_interval/promise_interval.js'
const emptyChatList = () => ({
data: [],
idStore: {}
idStore: {},
})
const defaultState = {
@ -17,7 +20,7 @@ const defaultState = {
openedChatMessageServices: reactive({}),
fetcher: undefined,
currentChatId: null,
lastReadMessageId: null
lastReadMessageId: null,
}
const getChatById = (state, id) => {
@ -35,65 +38,74 @@ const unreadChatCount = (state) => {
const chats = {
state: { ...defaultState },
getters: {
currentChat: state => state.openedChats[state.currentChatId],
currentChatMessageService: state => state.openedChatMessageServices[state.currentChatId],
findOpenedChatByRecipientId: state => recipientId => find(state.openedChats, c => c.account.id === recipientId),
currentChat: (state) => state.openedChats[state.currentChatId],
currentChatMessageService: (state) =>
state.openedChatMessageServices[state.currentChatId],
findOpenedChatByRecipientId: (state) => (recipientId) =>
find(state.openedChats, (c) => c.account.id === recipientId),
sortedChatList,
unreadChatCount
unreadChatCount,
},
actions: {
// Chat list
startFetchingChats ({ dispatch, commit }) {
startFetchingChats({ dispatch, commit }) {
const fetcher = () => dispatch('fetchChats', { latest: true })
fetcher()
commit('setChatListFetcher', {
fetcher: () => promiseInterval(fetcher, 5000)
fetcher: () => promiseInterval(fetcher, 5000),
})
},
stopFetchingChats ({ commit }) {
stopFetchingChats({ commit }) {
commit('setChatListFetcher', { fetcher: undefined })
},
fetchChats ({ dispatch, rootState }) {
return rootState.api.backendInteractor.chats()
.then(({ chats }) => {
dispatch('addNewChats', { chats })
return chats
})
fetchChats({ dispatch, rootState }) {
return rootState.api.backendInteractor.chats().then(({ chats }) => {
dispatch('addNewChats', { chats })
return chats
})
},
addNewChats (store, { chats }) {
addNewChats(store, { chats }) {
const { commit, dispatch, rootGetters } = store
const newChatMessageSideEffects = (chat) => {
maybeShowChatNotification(store, chat)
}
commit('addNewUsers', chats.map(k => k.account).filter(k => k))
commit('addNewChats', { dispatch, chats, rootGetters, newChatMessageSideEffects })
commit(
'addNewUsers',
chats.map((k) => k.account).filter((k) => k),
)
commit('addNewChats', {
dispatch,
chats,
rootGetters,
newChatMessageSideEffects,
})
},
updateChat ({ commit }, { chat }) {
updateChat({ commit }, { chat }) {
commit('updateChat', { chat })
},
// Opened Chats
startFetchingCurrentChat ({ dispatch }, { fetcher }) {
startFetchingCurrentChat({ dispatch }, { fetcher }) {
dispatch('setCurrentChatFetcher', { fetcher })
},
setCurrentChatFetcher ({ commit }, { fetcher }) {
setCurrentChatFetcher({ commit }, { fetcher }) {
commit('setCurrentChatFetcher', { fetcher })
},
addOpenedChat ({ commit, dispatch }, { chat }) {
addOpenedChat({ commit, dispatch }, { chat }) {
commit('addOpenedChat', { dispatch, chat: parseChat(chat) })
dispatch('addNewUsers', [chat.account])
},
addChatMessages ({ commit }, value) {
addChatMessages({ commit }, value) {
commit('addChatMessages', { commit, ...value })
},
resetChatNewMessageCount ({ commit }, value) {
resetChatNewMessageCount({ commit }, value) {
commit('resetChatNewMessageCount', value)
},
clearCurrentChat ({ commit }) {
clearCurrentChat({ commit }) {
commit('setCurrentChatId', { chatId: undefined })
commit('setCurrentChatFetcher', { fetcher: undefined })
},
readChat ({ rootState, commit, dispatch }, { id, lastReadId }) {
readChat({ rootState, commit, dispatch }, { id, lastReadId }) {
const isNewMessage = rootState.chats.lastReadMessageId !== lastReadId
dispatch('resetChatNewMessageCount')
@ -103,40 +115,40 @@ const chats = {
rootState.api.backendInteractor.readChat({ id, lastReadId })
}
},
deleteChatMessage ({ rootState, commit }, value) {
deleteChatMessage({ rootState, commit }, value) {
rootState.api.backendInteractor.deleteChatMessage(value)
commit('deleteChatMessage', { commit, ...value })
},
resetChats ({ commit, dispatch }) {
resetChats({ commit, dispatch }) {
dispatch('clearCurrentChat')
commit('resetChats', { commit })
},
clearOpenedChats ({ commit }) {
clearOpenedChats({ commit }) {
commit('clearOpenedChats', { commit })
},
handleMessageError ({ commit }, value) {
handleMessageError({ commit }, value) {
commit('handleMessageError', { commit, ...value })
},
cullOlderMessages ({ commit }, chatId) {
cullOlderMessages({ commit }, chatId) {
commit('cullOlderMessages', chatId)
}
},
},
mutations: {
setChatListFetcher (state, { fetcher }) {
setChatListFetcher(state, { fetcher }) {
const prevFetcher = state.chatListFetcher
if (prevFetcher) {
prevFetcher.stop()
}
state.chatListFetcher = fetcher && fetcher()
},
setCurrentChatFetcher (state, { fetcher }) {
setCurrentChatFetcher(state, { fetcher }) {
const prevFetcher = state.fetcher
if (prevFetcher) {
prevFetcher.stop()
}
state.fetcher = fetcher && fetcher()
},
addOpenedChat (state, { chat }) {
addOpenedChat(state, { chat }) {
state.currentChatId = chat.id
state.openedChats[chat.id] = chat
@ -144,15 +156,17 @@ const chats = {
state.openedChatMessageServices[chat.id] = chatService.empty(chat.id)
}
},
setCurrentChatId (state, { chatId }) {
setCurrentChatId(state, { chatId }) {
state.currentChatId = chatId
},
addNewChats (state, { chats, newChatMessageSideEffects }) {
addNewChats(state, { chats, newChatMessageSideEffects }) {
chats.forEach((updatedChat) => {
const chat = getChatById(state, updatedChat.id)
if (chat) {
const isNewMessage = (chat.lastMessage && chat.lastMessage.id) !== (updatedChat.lastMessage && updatedChat.lastMessage.id)
const isNewMessage =
(chat.lastMessage && chat.lastMessage.id) !==
(updatedChat.lastMessage && updatedChat.lastMessage.id)
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
chat.updated_at = updatedChat.updated_at
@ -165,23 +179,28 @@ const chats = {
}
})
},
updateChat (state, { chat: updatedChat }) {
updateChat(state, { chat: updatedChat }) {
const chat = getChatById(state, updatedChat.id)
if (chat) {
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
chat.updated_at = updatedChat.updated_at
}
if (!chat) { state.chatList.data.unshift(updatedChat) }
if (!chat) {
state.chatList.data.unshift(updatedChat)
}
state.chatList.idStore[updatedChat.id] = updatedChat
},
deleteChat (state, { id }) {
state.chats.data = state.chats.data.filter(conversation =>
conversation.last_status.id !== id
deleteChat(state, { id }) {
state.chats.data = state.chats.data.filter(
(conversation) => conversation.last_status.id !== id,
)
state.chats.idStore = omitBy(
state.chats.idStore,
(conversation) => conversation.last_status.id === id,
)
state.chats.idStore = omitBy(state.chats.idStore, conversation => conversation.last_status.id === id)
},
resetChats (state, { commit }) {
resetChats(state, { commit }) {
state.chatList = emptyChatList()
state.currentChatId = null
commit('setChatListFetcher', { fetcher: undefined })
@ -191,27 +210,31 @@ const chats = {
delete state.openedChatMessageServices[chatId]
}
},
setChatsLoading (state, { value }) {
setChatsLoading(state, { value }) {
state.chats.loading = value
},
addChatMessages (state, { chatId, messages, updateMaxId }) {
addChatMessages(state, { chatId, messages, updateMaxId }) {
const chatMessageService = state.openedChatMessageServices[chatId]
if (chatMessageService) {
chatService.add(chatMessageService, { messages: messages.map(parseChatMessage), updateMaxId })
chatService.add(chatMessageService, {
messages: messages.map(parseChatMessage),
updateMaxId,
})
}
},
deleteChatMessage (state, { chatId, messageId }) {
deleteChatMessage(state, { chatId, messageId }) {
const chatMessageService = state.openedChatMessageServices[chatId]
if (chatMessageService) {
chatService.deleteMessage(chatMessageService, messageId)
}
},
resetChatNewMessageCount (state) {
const chatMessageService = state.openedChatMessageServices[state.currentChatId]
resetChatNewMessageCount(state) {
const chatMessageService =
state.openedChatMessageServices[state.currentChatId]
chatService.resetNewMessageCount(chatMessageService)
},
// Used when a connection loss occurs
clearOpenedChats (state) {
clearOpenedChats(state) {
const currentChatId = state.currentChatId
for (const chatId in state.openedChats) {
if (currentChatId !== chatId) {
@ -221,21 +244,21 @@ const chats = {
}
}
},
readChat (state, { id, lastReadId }) {
readChat(state, { id, lastReadId }) {
state.lastReadMessageId = lastReadId
const chat = getChatById(state, id)
if (chat) {
chat.unread = 0
}
},
handleMessageError (state, { chatId, fakeId, isRetry }) {
handleMessageError(state, { chatId, fakeId, isRetry }) {
const chatMessageService = state.openedChatMessageServices[chatId]
chatService.handleMessageError(chatMessageService, fakeId, isRetry)
},
cullOlderMessages (state, chatId) {
cullOlderMessages(state, chatId) {
chatService.cullOlderMessages(state.openedChatMessageServices[chatId])
}
}
},
},
}
export default chats

View file

@ -19,7 +19,7 @@ const APPEARANCE_SETTINGS_KEYS = new Set([
'panelHeaderSize',
'forcedRoundness',
'emojiSize',
'emojiReactionsScale'
'emojiReactionsScale',
])
/* TODO this is a bit messy.
@ -34,7 +34,7 @@ export const multiChoiceProperties = [
'conversationOtherRepliesButton', // below | inside
'mentionLinkDisplay', // short | full_for_remote | full
'userPopoverAvatarAction', // close | zoom | open
'unsavedPostAction' // save | discard | confirm
'unsavedPostAction', // save | discard | confirm
]
// caching the instance default properties
@ -43,43 +43,48 @@ export const instanceDefaultProperties = Object.keys(instanceDefaultConfig)
const config = {
state: { ...defaultState },
getters: {
defaultConfig (state, getters, rootState) {
defaultConfig(state, getters, rootState) {
const { instance } = rootState
return {
...defaultState,
...Object.fromEntries(
instanceDefaultProperties.map(key => [key, instance[key]])
)
instanceDefaultProperties.map((key) => [key, instance[key]]),
),
}
},
mergedConfig (state, getters, rootState, rootGetters) {
mergedConfig(state, getters, rootState, rootGetters) {
const { defaultConfig } = rootGetters
return {
...defaultConfig,
// Do not override with undefined
...Object.fromEntries(Object.entries(state).filter(([, v]) => v !== undefined))
...Object.fromEntries(
Object.entries(state).filter(([, v]) => v !== undefined),
),
}
}
},
},
mutations: {
setOptionTemporarily (state, { name, value }) {
setOptionTemporarily(state, { name, value }) {
set(state, name, value)
applyConfig(state)
},
setOption (state, { name, value }) {
setOption(state, { name, value }) {
set(state, name, value)
},
setHighlight (state, { user, color, type }) {
setHighlight(state, { user, color, type }) {
const data = this.state.config.highlight[user]
if (color || type) {
state.highlight[user] = { color: color || data.color, type: type || data.type }
state.highlight[user] = {
color: color || data.color,
type: type || data.type,
}
} else {
delete state.highlight[user]
}
}
},
},
actions: {
loadSettings ({ dispatch }, data) {
loadSettings({ dispatch }, data) {
const knownKeys = new Set(Object.keys(defaultState))
const presentKeys = new Set(Object.keys(data))
const intersection = new Set()
@ -89,16 +94,16 @@ const config = {
}
}
intersection.forEach(
name => dispatch('setOption', { name, value: data[name] })
intersection.forEach((name) =>
dispatch('setOption', { name, value: data[name] }),
)
},
setHighlight ({ commit }, { user, color, type }) {
setHighlight({ commit }, { user, color, type }) {
commit('setHighlight', { user, color, type })
},
setOptionTemporarily ({ commit, dispatch, state }, { name, value }) {
setOptionTemporarily({ commit, dispatch, state }, { name, value }) {
if (useInterfaceStore().temporaryChangesTimeoutId !== null) {
console.warn('Can\'t track more than one temporary change')
console.warn("Can't track more than one temporary change")
return
}
const oldValue = state[name]
@ -117,32 +122,35 @@ const config = {
useInterfaceStore().setTemporaryChanges({
confirm,
revert
revert,
})
},
setThemeV2 ({ commit, dispatch }, { customTheme, customThemeSource }) {
setThemeV2({ commit, dispatch }, { customTheme, customThemeSource }) {
commit('setOption', { name: 'theme', value: 'custom' })
commit('setOption', { name: 'customTheme', value: customTheme })
commit('setOption', { name: 'customThemeSource', value: customThemeSource })
commit('setOption', {
name: 'customThemeSource',
value: customThemeSource,
})
dispatch('setTheme', { themeData: customThemeSource, recompile: true })
},
setOption ({ commit, dispatch, state }, { name, value }) {
const exceptions = new Set([
'useStreamingApi'
])
setOption({ commit, dispatch, state }, { name, value }) {
const exceptions = new Set(['useStreamingApi'])
if (exceptions.has(name)) {
switch (name) {
case 'useStreamingApi': {
const action = value ? 'enableMastoSockets' : 'disableMastoSockets'
dispatch(action).then(() => {
commit('setOption', { name: 'useStreamingApi', value })
}).catch((e) => {
console.error('Failed starting MastoAPI Streaming socket', e)
dispatch('disableMastoSockets')
dispatch('setOption', { name: 'useStreamingApi', value: false })
})
dispatch(action)
.then(() => {
commit('setOption', { name: 'useStreamingApi', value })
})
.catch((e) => {
console.error('Failed starting MastoAPI Streaming socket', e)
dispatch('disableMastoSockets')
dispatch('setOption', { name: 'useStreamingApi', value: false })
})
break
}
}
@ -157,7 +165,11 @@ const config = {
switch (name) {
case 'theme':
if (value === 'custom') break
dispatch('setTheme', { themeName: value, recompile: true, saveData: true })
dispatch('setTheme', {
themeName: value,
recompile: true,
saveData: true,
})
break
case 'themeDebug': {
dispatch('setTheme', { recompile: true })
@ -168,7 +180,7 @@ const config = {
dispatch('loadUnicodeEmojiData', value)
Cookies.set(
BACKEND_LANGUAGE_COOKIE_NAME,
localeService.internalToBackendLocaleMulti(value)
localeService.internalToBackendLocaleMulti(value),
)
break
case 'thirdColumnMode':
@ -176,8 +188,8 @@ const config = {
break
}
}
}
}
},
},
}
export default config

View file

@ -1,5 +1,5 @@
export const CONFIG_MIGRATION = 1
import { v4 as uuidv4 } from 'uuid';
import { v4 as uuidv4 } from 'uuid'
// for future use
/*
@ -20,7 +20,7 @@ export const declarations = [
migrationNum: 1,
description: 'Mute filters, wordfilter/regexp/etc',
valueType: 'complex',
migration (serverside, rootState) {
migration(serverside, rootState) {
rootState.config.muteWords.forEach((word, order) => {
const uniqueId = uuidv4()
@ -33,10 +33,10 @@ export const declarations = [
enabled: true,
expires: null,
hide: false,
order
}
order,
},
})
})
}
}
},
},
]

View file

@ -70,7 +70,7 @@ export const instanceDefaultConfig = {
followRequest: true,
reports: true,
chatMention: true,
polls: true
polls: true,
},
notificationNative: {
follows: true,
@ -83,7 +83,7 @@ export const instanceDefaultConfig = {
followRequest: true,
reports: true,
chatMention: true,
polls: true
polls: true,
},
webPushNotifications: false,
webPushAlwaysShowNotifications: false,
@ -170,10 +170,11 @@ export const instanceDefaultConfig = {
absoluteTimeFormatMinAge: '0d',
absoluteTime12h: '24h',
imageCompression: true,
alwaysUseJpeg: false
alwaysUseJpeg: false,
}
export const makeUndefined = c => Object.fromEntries(Object.keys(c).map(key => [key, undefined]))
export const makeUndefined = (c) =>
Object.fromEntries(Object.keys(c).map((key) => [key, undefined]))
/// For properties with special processing or properties that does not
/// make sense to be overriden on a instance-wide level.
@ -199,14 +200,15 @@ export const defaultState = {
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
theme3hacks: {
// Hacks, user overrides that are independent of theme used
underlay: 'none',
fonts: {
interface: undefined,
input: undefined,
post: undefined,
monospace: undefined
}
monospace: undefined,
},
},
// Special handling: These fields are not of a primitive type, and

View file

@ -1,19 +1,19 @@
import { storage } from 'src/lib/storage.js'
export const defaultState = {
drafts: {}
drafts: {},
}
export const mutations = {
addOrSaveDraft (state, { draft }) {
addOrSaveDraft(state, { draft }) {
state.drafts[draft.id] = draft
},
abandonDraft (state, { id }) {
abandonDraft(state, { id }) {
delete state.drafts[id]
},
loadDrafts (state, data) {
loadDrafts(state, data) {
state.drafts = data
}
},
}
const storageKey = 'pleroma-fe-drafts'
@ -30,7 +30,7 @@ const storageKey = 'pleroma-fe-drafts'
* different keys, which will just pollute the whole storage.
* It is indeed best to have backend support for this.
*/
const getStorageData = async () => ((await storage.getItem(storageKey)) || {})
const getStorageData = async () => (await storage.getItem(storageKey)) || {}
const saveDraftToStorage = async (draft) => {
const currentData = await getStorageData()
@ -45,42 +45,44 @@ const deleteDraftFromStorage = async (id) => {
}
export const actions = {
async addOrSaveDraft (store, { draft }) {
const id = draft.id || (new Date().getTime()).toString()
async addOrSaveDraft(store, { draft }) {
const id = draft.id || new Date().getTime().toString()
const draftWithId = { ...draft, id }
store.commit('addOrSaveDraft', { draft: draftWithId })
await saveDraftToStorage(draftWithId)
return id
},
async abandonDraft (store, { id }) {
async abandonDraft(store, { id }) {
store.commit('abandonDraft', { id })
await deleteDraftFromStorage(id)
},
async loadDrafts (store) {
async loadDrafts(store) {
const currentData = await getStorageData()
store.commit('loadDrafts', currentData)
}
},
}
export const getters = {
draftsByTypeAndRefId (state) {
draftsByTypeAndRefId(state) {
return (type, refId) => {
return Object.values(state.drafts).filter(draft => draft.type === type && draft.refId === refId)
return Object.values(state.drafts).filter(
(draft) => draft.type === type && draft.refId === refId,
)
}
},
draftsArray (state) {
draftsArray(state) {
return Object.values(state.drafts)
},
draftCount (state) {
draftCount(state) {
return Object.values(state.drafts).length
}
},
}
const drafts = {
state: defaultState,
mutations,
getters,
actions
actions,
}
export default drafts

View file

@ -19,5 +19,5 @@ export default {
profileConfig,
adminSettings,
drafts,
chats
chats,
}

View file

@ -4,7 +4,10 @@ import { ensureFinalFallback } from '../i18n/languages.js'
import { useInterfaceStore } from 'src/stores/interface.js'
// See build/emojis_plugin for more details
import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations'
import { staticOrApiConfigDefault, instanceDefaultConfig } from './default_config_state.js';
import {
staticOrApiConfigDefault,
instanceDefaultConfig,
} from './default_config_state.js'
const SORTED_EMOJI_GROUP_IDS = [
'smileys-and-emotion',
@ -15,12 +18,12 @@ const SORTED_EMOJI_GROUP_IDS = [
'activities',
'objects',
'symbols',
'flags'
'flags',
]
const REGIONAL_INDICATORS = (() => {
const start = 0x1F1E6
const end = 0x1F1FF
const start = 0x1f1e6
const end = 0x1f1ff
const A = 'A'.codePointAt(0)
const res = new Array(end - start + 1)
for (let i = start; i <= end; ++i) {
@ -31,8 +34,8 @@ const REGIONAL_INDICATORS = (() => {
displayText: 'regional_indicator_' + letter,
displayTextI18n: {
key: 'emoji.regional_indicator',
args: { letter }
}
args: { letter },
},
}
}
return res
@ -107,13 +110,12 @@ const defaultState = {
max_options: 4,
max_option_chars: 255,
min_expiration: 60,
max_expiration: 60 * 60 * 24
}
max_expiration: 60 * 60 * 24,
},
}
const loadAnnotations = (lang) => {
return annotationsLoader[lang]()
.then(k => k.default)
return annotationsLoader[lang]().then((k) => k.default)
}
const injectAnnotations = (emoji, annotations) => {
@ -124,11 +126,11 @@ const injectAnnotations = (emoji, annotations) => {
annotations: availableLangs.reduce((acc, cur) => {
acc[cur] = annotations[cur][emoji.replacement]
return acc
}, {})
}, {}),
}
}
const injectRegionalIndicators = groups => {
const injectRegionalIndicators = (groups) => {
groups.symbols.push(...REGIONAL_INDICATORS)
return groups
}
@ -136,77 +138,84 @@ const injectRegionalIndicators = groups => {
const instance = {
state: defaultState,
mutations: {
setInstanceOption (state, { name, value }) {
setInstanceOption(state, { name, value }) {
if (typeof value !== 'undefined') {
state[name] = value
}
},
setKnownDomains (state, domains) {
setKnownDomains(state, domains) {
state.knownDomains = domains
},
setUnicodeEmojiAnnotations (state, { lang, annotations }) {
setUnicodeEmojiAnnotations(state, { lang, annotations }) {
state.unicodeEmojiAnnotations[lang] = annotations
}
},
},
getters: {
instanceDefaultConfig (state) {
instanceDefaultConfig(state) {
return instanceDefaultProperties
.map(key => [key, state[key]])
.map((key) => [key, state[key]])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
},
groupedCustomEmojis (state) {
const packsOf = emoji => {
groupedCustomEmojis(state) {
const packsOf = (emoji) => {
const packs = emoji.tags
.filter(k => k.startsWith('pack:'))
.map(k => {
.filter((k) => k.startsWith('pack:'))
.map((k) => {
const packName = k.slice(5) // remove 'pack:' prefix
return {
id: `custom-${packName}`,
text: packName
text: packName,
}
})
if (!packs.length) {
return [{
id: 'unpacked'
}]
return [
{
id: 'unpacked',
},
]
} else {
return packs
}
}
return state.customEmoji
.reduce((res, emoji) => {
packsOf(emoji).forEach(({ id: packId, text: packName }) => {
if (!res[packId]) {
res[packId] = ({
id: packId,
text: packName,
image: emoji.imageUrl,
emojis: []
})
return state.customEmoji.reduce((res, emoji) => {
packsOf(emoji).forEach(({ id: packId, text: packName }) => {
if (!res[packId]) {
res[packId] = {
id: packId,
text: packName,
image: emoji.imageUrl,
emojis: [],
}
res[packId].emojis.push(emoji)
})
return res
}, {})
}
res[packId].emojis.push(emoji)
})
return res
}, {})
},
standardEmojiList (state) {
return SORTED_EMOJI_GROUP_IDS
.map(groupId => (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations)))
.reduce((a, b) => a.concat(b), [])
standardEmojiList(state) {
return SORTED_EMOJI_GROUP_IDS.map((groupId) =>
(state.emoji[groupId] || []).map((k) =>
injectAnnotations(k, state.unicodeEmojiAnnotations),
),
).reduce((a, b) => a.concat(b), [])
},
standardEmojiGroupList (state) {
return SORTED_EMOJI_GROUP_IDS.map(groupId => ({
standardEmojiGroupList(state) {
return SORTED_EMOJI_GROUP_IDS.map((groupId) => ({
id: groupId,
emojis: (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations))
emojis: (state.emoji[groupId] || []).map((k) =>
injectAnnotations(k, state.unicodeEmojiAnnotations),
),
}))
},
instanceDomain (state) {
instanceDomain(state) {
return new URL(state.server).hostname
},
remoteInteractionLink (state) {
const server = state.server.endsWith('/') ? state.server.slice(0, -1) : state.server
remoteInteractionLink(state) {
const server = state.server.endsWith('/')
? state.server.slice(0, -1)
: state.server
const link = server + REMOTE_INTERACTION_URL
return ({ statusId, nickname }) => {
@ -216,10 +225,10 @@ const instance = {
return `${link}?nickname=${nickname}`
}
}
}
},
},
actions: {
setInstanceOption ({ commit, dispatch }, { name, value }) {
setInstanceOption({ commit, dispatch }, { name, value }) {
commit('setInstanceOption', { name, value })
switch (name) {
case 'name':
@ -232,43 +241,49 @@ const instance = {
break
}
},
async getStaticEmoji ({ commit }) {
async getStaticEmoji({ commit }) {
try {
const values = (await import('/src/assets/emoji.json')).default
const emoji = Object.keys(values).reduce((res, groupId) => {
res[groupId] = values[groupId].map(e => ({
res[groupId] = values[groupId].map((e) => ({
displayText: e.slug,
imageUrl: false,
replacement: e.emoji
replacement: e.emoji,
}))
return res
}, {})
commit('setInstanceOption', { name: 'emoji', value: injectRegionalIndicators(emoji) })
commit('setInstanceOption', {
name: 'emoji',
value: injectRegionalIndicators(emoji),
})
} catch (e) {
console.warn("Can't load static emoji\n", e)
}
},
loadUnicodeEmojiData ({ commit, state }, language) {
loadUnicodeEmojiData({ commit, state }, language) {
const langList = ensureFinalFallback(language)
return Promise.all(
langList
.map(async lang => {
if (!state.unicodeEmojiAnnotations[lang]) {
try {
const annotations = await loadAnnotations(lang)
commit('setUnicodeEmojiAnnotations', { lang, annotations })
} catch (e) {
console.warn(`Error loading unicode emoji annotations for ${lang}: `, e)
// ignore
}
langList.map(async (lang) => {
if (!state.unicodeEmojiAnnotations[lang]) {
try {
const annotations = await loadAnnotations(lang)
commit('setUnicodeEmojiAnnotations', { lang, annotations })
} catch (e) {
console.warn(
`Error loading unicode emoji annotations for ${lang}: `,
e,
)
// ignore
}
}))
}
}),
)
},
async getCustomEmoji ({ commit, state }) {
async getCustomEmoji({ commit, state }) {
try {
let res = await window.fetch('/api/v1/pleroma/emoji')
if (!res.ok) {
@ -276,11 +291,13 @@ const instance = {
}
if (res.ok) {
const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result
const values = Array.isArray(result)
? Object.assign({}, ...result)
: result
const caseInsensitiveStrCmp = (a, b) => {
const la = a.toLowerCase()
const lb = b.toLowerCase()
return la > lb ? 1 : (la < lb ? -1 : 0)
return la > lb ? 1 : la < lb ? -1 : 0
}
const noPackLast = (a, b) => {
const aNull = a === ''
@ -294,32 +311,43 @@ const instance = {
}
}
const byPackThenByName = (a, b) => {
const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5)
const packOf = (emoji) =>
(emoji.tags.filter((k) => k.startsWith('pack:'))[0] || '').slice(
5,
)
const packOfA = packOf(a)
const packOfB = packOf(b)
return noPackLast(packOfA, packOfB) || caseInsensitiveStrCmp(packOfA, packOfB) || caseInsensitiveStrCmp(a.displayText, b.displayText)
return (
noPackLast(packOfA, packOfB) ||
caseInsensitiveStrCmp(packOfA, packOfB) ||
caseInsensitiveStrCmp(a.displayText, b.displayText)
)
}
const emoji = Object.entries(values).map(([key, value]) => {
const imageUrl = value.image_url
return {
displayText: key,
imageUrl: imageUrl ? state.server + imageUrl : value,
tags: imageUrl ? value.tags.sort((a, b) => a > b ? 1 : 0) : ['utf'],
replacement: `:${key}: `
}
// Technically could use tags but those are kinda useless right now,
// should have been "pack" field, that would be more useful
}).sort(byPackThenByName)
const emoji = Object.entries(values)
.map(([key, value]) => {
const imageUrl = value.image_url
return {
displayText: key,
imageUrl: imageUrl ? state.server + imageUrl : value,
tags: imageUrl
? value.tags.sort((a, b) => (a > b ? 1 : 0))
: ['utf'],
replacement: `:${key}: `,
}
// Technically could use tags but those are kinda useless right now,
// should have been "pack" field, that would be more useful
})
.sort(byPackThenByName)
commit('setInstanceOption', { name: 'customEmoji', value: emoji })
} else {
throw (res)
throw res
}
} catch (e) {
console.warn("Can't load custom emojis\n", e)
}
},
fetchEmoji ({ dispatch, state }) {
fetchEmoji({ dispatch, state }) {
if (!state.customEmojiFetched) {
state.customEmojiFetched = true
dispatch('getCustomEmoji')
@ -330,17 +358,17 @@ const instance = {
}
},
async getKnownDomains ({ commit, rootState }) {
async getKnownDomains({ commit, rootState }) {
try {
const result = await apiService.fetchKnownDomains({
credentials: rootState.users.currentUser.credentials
credentials: rootState.users.currentUser.credentials,
})
commit('setKnownDomains', result)
} catch (e) {
console.warn("Can't load known domains\n", e)
}
}
}
},
},
}
export default instance

View file

@ -4,12 +4,12 @@ import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import {
isStatusNotification,
isValidNotification,
maybeShowNotification
maybeShowNotification,
} from '../services/notification_utils/notification_utils.js'
import {
closeDesktopNotification,
closeAllDesktopNotifications
closeAllDesktopNotifications,
} from '../services/desktop_notification_utils/desktop_notification_utils.js'
import { useReportsStore } from 'src/stores/reports.js'
@ -20,58 +20,58 @@ const emptyNotifications = () => ({
minId: Number.POSITIVE_INFINITY,
data: [],
idStore: {},
loading: false
loading: false,
})
export const defaultState = () => ({
...emptyNotifications()
...emptyNotifications(),
})
export const notifications = {
state: defaultState(),
mutations: {
addNewNotifications (state, { notifications }) {
notifications.forEach(notification => {
addNewNotifications(state, { notifications }) {
notifications.forEach((notification) => {
state.data.push(notification)
state.idStore[notification.id] = notification
})
},
clearNotifications (state) {
clearNotifications(state) {
const blankState = defaultState()
Object.keys(state).forEach(k => {
Object.keys(state).forEach((k) => {
state[k] = blankState[k]
})
},
updateNotificationsMinMaxId (state, id) {
updateNotificationsMinMaxId(state, id) {
state.maxId = id > state.maxId ? id : state.maxId
state.minId = id < state.minId ? id : state.minId
},
setNotificationsLoading (state, { value }) {
setNotificationsLoading(state, { value }) {
state.loading = value
},
setNotificationsSilence (state, { value }) {
setNotificationsSilence(state, { value }) {
state.desktopNotificationSilence = value
},
markNotificationsAsSeen (state) {
markNotificationsAsSeen(state) {
state.data.forEach((notification) => {
notification.seen = true
})
},
markSingleNotificationAsSeen (state, { id }) {
markSingleNotificationAsSeen(state, { id }) {
const notification = state.idStore[id]
if (notification) notification.seen = true
},
dismissNotification (state, { id }) {
state.data = state.data.filter(n => n.id !== id)
dismissNotification(state, { id }) {
state.data = state.data.filter((n) => n.id !== id)
delete state.idStore[id]
},
updateNotification (state, { id, updater }) {
updateNotification(state, { id, updater }) {
const notification = state.idStore[id]
notification && updater(notification)
}
},
},
actions: {
addNewNotifications (store, { notifications }) {
addNewNotifications(store, { notifications }) {
const { commit, dispatch, state, rootState } = store
const validNotifications = notifications.filter((notification) => {
// If invalid notification, update ids but don't add it to store
@ -83,13 +83,20 @@ export const notifications = {
return true
})
const statusNotifications = validNotifications.filter(notification => isStatusNotification(notification.type) && notification.status)
const statusNotifications = validNotifications.filter(
(notification) =>
isStatusNotification(notification.type) && notification.status,
)
// Synchronous commit to add all the statuses
commit('addNewStatuses', { statuses: statusNotifications.map(notification => notification.status) })
commit('addNewStatuses', {
statuses: statusNotifications.map(
(notification) => notification.status,
),
})
// Update references to statuses in notifications to ones in the store
statusNotifications.forEach(notification => {
statusNotifications.forEach((notification) => {
const id = notification.status.id
const referenceStatus = rootState.statuses.allStatusesObject[id]
@ -98,7 +105,7 @@ export const notifications = {
}
})
validNotifications.forEach(notification => {
validNotifications.forEach((notification) => {
if (notification.type === 'pleroma:report') {
useReportsStore().addReport(notification.report)
}
@ -115,15 +122,17 @@ export const notifications = {
maybeShowNotification(
store,
Object.values(useServerSideStorageStore().prefsStorage.simple.muteFilters),
notification
Object.values(
useServerSideStorageStore().prefsStorage.simple.muteFilters,
),
notification,
)
} else if (notification.seen) {
state.idStore[notification.id].seen = true
}
})
},
notificationClicked ({ state, dispatch }, { id }) {
notificationClicked({ state, dispatch }, { id }) {
const notification = state.idStore[id]
const { type, seen } = notification
@ -138,42 +147,46 @@ export const notifications = {
}
}
},
setNotificationsLoading ({ commit }, { value }) {
setNotificationsLoading({ commit }, { value }) {
commit('setNotificationsLoading', { value })
},
setNotificationsSilence ({ commit }, { value }) {
setNotificationsSilence({ commit }, { value }) {
commit('setNotificationsSilence', { value })
},
markNotificationsAsSeen ({ rootState, state, commit }) {
markNotificationsAsSeen({ rootState, state, commit }) {
commit('markNotificationsAsSeen')
apiService.markNotificationsAsSeen({
id: state.maxId,
credentials: rootState.users.currentUser.credentials
}).then(() => {
closeAllDesktopNotifications(rootState)
})
apiService
.markNotificationsAsSeen({
id: state.maxId,
credentials: rootState.users.currentUser.credentials,
})
.then(() => {
closeAllDesktopNotifications(rootState)
})
},
markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
markSingleNotificationAsSeen({ rootState, commit }, { id }) {
commit('markSingleNotificationAsSeen', { id })
apiService.markNotificationsAsSeen({
single: true,
id,
credentials: rootState.users.currentUser.credentials
}).then(() => {
closeDesktopNotification(rootState, { id })
})
apiService
.markNotificationsAsSeen({
single: true,
id,
credentials: rootState.users.currentUser.credentials,
})
.then(() => {
closeDesktopNotification(rootState, { id })
})
},
dismissNotificationLocal ({ commit }, { id }) {
dismissNotificationLocal({ commit }, { id }) {
commit('dismissNotification', { id })
},
dismissNotification ({ rootState, commit }, { id }) {
dismissNotification({ rootState, commit }, { id }) {
commit('dismissNotification', { id })
rootState.api.backendInteractor.dismissNotification({ id })
},
updateNotification ({ commit }, { id, updater }) {
updateNotification({ commit }, { id, updater }) {
commit('updateNotification', { id, updater })
}
}
},
},
}
export default notifications

View file

@ -3,11 +3,9 @@ import { get, set } from 'lodash'
const defaultApi = ({ rootState, commit }, { path, value }) => {
const params = {}
set(params, path, value)
return rootState
.api
.backendInteractor
return rootState.api.backendInteractor
.updateProfile({ params })
.then(result => {
.then((result) => {
commit('addNewUsers', [result])
commit('setCurrentUser', result)
})
@ -16,11 +14,9 @@ const defaultApi = ({ rootState, commit }, { path, value }) => {
const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => {
const settings = {}
set(settings, path, value)
return rootState
.api
.backendInteractor
return rootState.api.backendInteractor
.updateNotificationSettings({ settings })
.then(result => {
.then((result) => {
if (result.status === 'success') {
commit('confirmProfileOption', { name, value })
} else {
@ -43,98 +39,99 @@ export const settingsMap = {
defaultNSFW: 'source.sensitive', // BROKEN: pleroma/pleroma#2837
stripRichContent: {
get: 'source.pleroma.no_rich_text',
set: 'no_rich_text'
set: 'no_rich_text',
},
// Privacy
locked: 'locked',
acceptChatMessages: {
get: 'pleroma.accepts_chat_messages',
set: 'accepts_chat_messages'
set: 'accepts_chat_messages',
},
allowFollowingMove: {
get: 'pleroma.allow_following_move',
set: 'allow_following_move'
set: 'allow_following_move',
},
discoverable: {
get: 'source.pleroma.discoverable',
set: 'discoverable'
set: 'discoverable',
},
hideFavorites: {
get: 'pleroma.hide_favorites',
set: 'hide_favorites'
set: 'hide_favorites',
},
hideFollowers: {
get: 'pleroma.hide_followers',
set: 'hide_followers'
set: 'hide_followers',
},
hideFollows: {
get: 'pleroma.hide_follows',
set: 'hide_follows'
set: 'hide_follows',
},
hideFollowersCount: {
get: 'pleroma.hide_followers_count',
set: 'hide_followers_count'
set: 'hide_followers_count',
},
hideFollowsCount: {
get: 'pleroma.hide_follows_count',
set: 'hide_follows_count'
set: 'hide_follows_count',
},
// NotificationSettingsAPIs
webPushHideContents: {
get: 'pleroma.notification_settings.hide_notification_contents',
set: 'hide_notification_contents',
api: notificationsApi
api: notificationsApi,
},
blockNotificationsFromStrangers: {
get: 'pleroma.notification_settings.block_from_strangers',
set: 'block_from_strangers',
api: notificationsApi
}
api: notificationsApi,
},
}
export const defaultState = Object.fromEntries(Object.keys(settingsMap).map(key => [key, null]))
export const defaultState = Object.fromEntries(
Object.keys(settingsMap).map((key) => [key, null]),
)
const profileConfig = {
state: { ...defaultState },
mutations: {
confirmProfileOption (state, { name, value }) {
confirmProfileOption(state, { name, value }) {
set(state, name, value)
},
wipeProfileOption (state, { name }) {
wipeProfileOption(state, { name }) {
set(state, name, null)
},
wipeAllProfileOptions (state) {
Object.keys(settingsMap).forEach(key => {
wipeAllProfileOptions(state) {
Object.keys(settingsMap).forEach((key) => {
set(state, key, null)
})
},
// Set the settings based on their path location
setCurrentUser (state, user) {
setCurrentUser(state, user) {
Object.entries(settingsMap).forEach((map) => {
const [name, value] = map
const { get: path = value } = value
set(state, name, get(user._original, path))
})
}
},
},
actions: {
setProfileOption ({ rootState, state, commit }, { name, value }) {
setProfileOption({ rootState, state, commit }, { name, value }) {
const oldValue = get(state, name)
const map = settingsMap[name]
if (!map) throw new Error('Invalid server-side setting')
const { set: path = map, api = defaultApi } = map
commit('wipeProfileOption', { name })
api({ rootState, commit }, { path, value, oldValue })
.catch((e) => {
console.warn('Error setting server-side option:', e)
commit('confirmProfileOption', { name, value: oldValue })
})
api({ rootState, commit }, { path, value, oldValue }).catch((e) => {
console.warn('Error setting server-side option:', e)
commit('confirmProfileOption', { name, value: oldValue })
})
},
logout ({ commit }) {
logout({ commit }) {
commit('wipeAllProfileOptions')
}
}
},
},
}
export default profileConfig

View file

@ -10,7 +10,7 @@ import {
first,
last,
isArray,
omitBy
omitBy,
} from 'lodash'
import apiService from '../services/api/api.service.js'
import { useInterfaceStore } from 'src/stores/interface'
@ -29,7 +29,7 @@ const emptyTl = (userId = 0) => ({
followers: [],
friends: [],
userId,
flushMarker: 0
flushMarker: 0,
})
export const defaultState = () => ({
@ -52,8 +52,8 @@ export const defaultState = () => ({
dms: emptyTl(),
bookmarks: emptyTl(),
list: emptyTl(),
bubble: emptyTl()
}
bubble: emptyTl(),
},
})
export const prepareStatus = (status) => {
@ -73,7 +73,10 @@ const mergeOrAdd = (arr, obj, item) => {
// We already have this, so only merge the new info.
// We ignore null values to avoid overwriting existing properties with missing data
// we also skip 'user' because that is handled by users module
merge(oldItem, omitBy(item, (v, k) => v === null || k === 'user'))
merge(
oldItem,
omitBy(item, (v, k) => v === null || k === 'user'),
)
// Reactivity fix.
oldItem.attachments.splice(oldItem.attachments.length)
return { item: oldItem, new: false }
@ -116,26 +119,32 @@ const getLatestScrobble = (state, user) => {
return
}
if (state.scrobblesNextFetch[user.id] && state.scrobblesNextFetch[user.id] > Date.now()) {
if (
state.scrobblesNextFetch[user.id] &&
state.scrobblesNextFetch[user.id] > Date.now()
) {
return
}
state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
if (!scrobblesSupport) return
apiService.fetchScrobbles({ accountId: user.id }).then((scrobbles) => {
if (scrobbles?.error) {
state.pleromaScrobblesAvailable = false
return
}
apiService
.fetchScrobbles({ accountId: user.id })
.then((scrobbles) => {
if (scrobbles?.error) {
state.pleromaScrobblesAvailable = false
return
}
if (scrobbles.length > 0) {
user.latestScrobble = scrobbles[0]
if (scrobbles.length > 0) {
user.latestScrobble = scrobbles[0]
state.scrobblesNextFetch[user.id] = Date.now() + 60 * 1000
}
}).catch(e => {
console.warn('cannot fetch scrobbles', e)
})
state.scrobblesNextFetch[user.id] = Date.now() + 60 * 1000
}
})
.catch((e) => {
console.warn('cannot fetch scrobbles', e)
})
}
// Add status to the global storages (arrays and objects maintaining statuses) except timelines
@ -156,7 +165,18 @@ const addStatusToGlobalStorage = (state, data) => {
return result
}
const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {}, noIdUpdate = false, userId, pagination = {} }) => {
const addNewStatuses = (
state,
{
statuses,
showImmediately = false,
timeline,
user = {},
noIdUpdate = false,
userId,
pagination = {},
},
) => {
// Sanity check
if (!isArray(statuses)) {
return false
@ -169,11 +189,19 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
// pagination.maxId is the oldest of the returned statuses when fetching older,
// and pagination.minId is the newest when fetching newer. The names come directly
// from the arguments they're supposed to be passed as for the next fetch.
const minNew = pagination.maxId || (statuses.length > 0 ? minBy(statuses, 'id').id : 0)
const maxNew = pagination.minId || (statuses.length > 0 ? maxBy(statuses, 'id').id : 0)
const minNew =
pagination.maxId || (statuses.length > 0 ? minBy(statuses, 'id').id : 0)
const maxNew =
pagination.minId || (statuses.length > 0 ? maxBy(statuses, 'id').id : 0)
const newer = timeline && (maxNew > timelineObject.maxId || timelineObject.maxId === 0) && statuses.length > 0
const older = timeline && (minNew < timelineObject.minId || timelineObject.minId === 0) && statuses.length > 0
const newer =
timeline &&
(maxNew > timelineObject.maxId || timelineObject.maxId === 0) &&
statuses.length > 0
const older =
timeline &&
(minNew < timelineObject.minId || timelineObject.minId === 0) &&
statuses.length > 0
if (!noIdUpdate && newer) {
timelineObject.maxId = maxNew
@ -185,7 +213,10 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
// This makes sure that user timeline won't get data meant for other
// user. I.e. opening different user profiles makes request which could
// return data late after user already viewing different user profile
if ((timeline === 'user' || timeline === 'media') && timelineObject.userId !== userId) {
if (
(timeline === 'user' || timeline === 'media') &&
timelineObject.userId !== userId
) {
return
}
@ -195,7 +226,10 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
if (result.new) {
// We are mentioned in a post
if (status.type === 'status' && find(status.attentions, { id: user.id })) {
if (
status.type === 'status' &&
find(status.attentions, { id: user.id })
) {
const mentions = state.timelines.mentions
// Add the mention to the mentions timeline
@ -220,20 +254,32 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
let resultForCurrentTimeline
// Some statuses should only be added to the global status repository.
if (timeline && addToTimeline) {
resultForCurrentTimeline = mergeOrAdd(timelineObject.statuses, timelineObject.statusesObject, status)
resultForCurrentTimeline = mergeOrAdd(
timelineObject.statuses,
timelineObject.statusesObject,
status,
)
}
if (timeline && showImmediately) {
// Add it directly to the visibleStatuses, don't change
// newStatusCount
mergeOrAdd(timelineObject.visibleStatuses, timelineObject.visibleStatusesObject, status)
mergeOrAdd(
timelineObject.visibleStatuses,
timelineObject.visibleStatusesObject,
status,
)
} else if (timeline && addToTimeline && resultForCurrentTimeline.new) {
// Just change newStatuscount
timelineObject.newStatusCount += 1
}
if (status.quote) {
addStatus(status.quote, /* showImmediately = */ false, /* addToTimeline = */ false)
addStatus(
status.quote,
/* showImmediately = */ false,
/* addToTimeline = */ false,
)
}
return status
@ -266,13 +312,19 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
let retweet
// If the retweeted status is already there, don't add the retweet
// to the timeline.
if (timeline && find(timelineObject.statuses, (s) => {
if (s.retweeted_status) {
return s.id === retweetedStatus.id || s.retweeted_status.id === retweetedStatus.id
} else {
return s.id === retweetedStatus.id
}
})) {
if (
timeline &&
find(timelineObject.statuses, (s) => {
if (s.retweeted_status) {
return (
s.id === retweetedStatus.id ||
s.retweeted_status.id === retweetedStatus.id
)
} else {
return s.id === retweetedStatus.id
}
})
) {
// Already have it visible (either as the original or another RT), don't add to timeline, don't show.
retweet = addStatus(status, false, false)
} else {
@ -295,7 +347,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
default: (unknown) => {
console.warn('unknown status type')
console.warn(unknown)
}
},
}
each(statuses, (status) => {
@ -315,35 +367,41 @@ const removeStatus = (state, { timeline, userId }) => {
if (userId) {
remove(timelineObject.statuses, { user: { id: userId } })
remove(timelineObject.visibleStatuses, { user: { id: userId } })
timelineObject.minVisibleId = timelineObject.visibleStatuses.length > 0 ? last(timelineObject.visibleStatuses).id : 0
timelineObject.maxId = timelineObject.statuses.length > 0 ? first(timelineObject.statuses).id : 0
timelineObject.minVisibleId =
timelineObject.visibleStatuses.length > 0
? last(timelineObject.visibleStatuses).id
: 0
timelineObject.maxId =
timelineObject.statuses.length > 0 ? first(timelineObject.statuses).id : 0
}
}
export const mutations = {
addNewStatuses,
removeStatus,
showNewStatuses (state, { timeline }) {
const oldTimeline = (state.timelines[timeline])
showNewStatuses(state, { timeline }) {
const oldTimeline = state.timelines[timeline]
oldTimeline.newStatusCount = 0
oldTimeline.visibleStatuses = slice(oldTimeline.statuses, 0, 50)
oldTimeline.minVisibleId = last(oldTimeline.visibleStatuses).id
oldTimeline.minId = oldTimeline.minVisibleId
oldTimeline.visibleStatusesObject = {}
each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status })
each(oldTimeline.visibleStatuses, (status) => {
oldTimeline.visibleStatusesObject[status.id] = status
})
},
resetStatuses (state) {
resetStatuses(state) {
const emptyState = defaultState()
Object.entries(emptyState).forEach(([key, value]) => {
state[key] = value
})
},
clearTimeline (state, { timeline, excludeUserId = false }) {
clearTimeline(state, { timeline, excludeUserId = false }) {
const userId = excludeUserId ? state.timelines[timeline].userId : undefined
state.timelines[timeline] = emptyTl(userId)
},
setFavorited (state, { status, value }) {
setFavorited(state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
if (newStatus.favorited !== value) {
@ -356,7 +414,7 @@ export const mutations = {
newStatus.favorited = value
},
setFavoritedConfirm (state, { status, user }) {
setFavoritedConfirm(state, { status, user }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.favorited = status.favorited
newStatus.fave_num = status.fave_num
@ -367,15 +425,19 @@ export const mutations = {
newStatus.favoritedBy.push(user)
}
},
setMutedStatus (state, status) {
setMutedStatus(state, status) {
const newStatus = state.allStatusesObject[status.id]
newStatus.thread_muted = status.thread_muted
if (newStatus.thread_muted !== undefined) {
state.conversationsObject[newStatus.statusnet_conversation_id].forEach(status => { status.thread_muted = newStatus.thread_muted })
state.conversationsObject[newStatus.statusnet_conversation_id].forEach(
(status) => {
status.thread_muted = newStatus.thread_muted
},
)
}
},
setRetweeted (state, { status, value }) {
setRetweeted(state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
if (newStatus.repeated !== value) {
@ -388,7 +450,7 @@ export const mutations = {
newStatus.repeated = value
},
setRetweetedConfirm (state, { status, user }) {
setRetweetedConfirm(state, { status, user }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.repeated = status.repeated
newStatus.repeat_num = status.repeat_num
@ -399,73 +461,79 @@ export const mutations = {
newStatus.rebloggedBy.push(user)
}
},
setBookmarked (state, { status, value }) {
setBookmarked(state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.bookmarked = value
newStatus.bookmark_folder_id = status.bookmark_folder_id
},
setBookmarkedConfirm (state, { status }) {
setBookmarkedConfirm(state, { status }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.bookmarked = status.bookmarked
if (status.pleroma) newStatus.bookmark_folder_id = status.pleroma.bookmark_folder
if (status.pleroma)
newStatus.bookmark_folder_id = status.pleroma.bookmark_folder
},
setDeleted (state, { status }) {
setDeleted(state, { status }) {
const newStatus = state.allStatusesObject[status.id]
if (newStatus) newStatus.deleted = true
},
setManyDeleted (state, condition) {
Object.values(state.allStatusesObject).forEach(status => {
setManyDeleted(state, condition) {
Object.values(state.allStatusesObject).forEach((status) => {
if (condition(status)) {
status.deleted = true
}
})
},
setLoading (state, { timeline, value }) {
setLoading(state, { timeline, value }) {
state.timelines[timeline].loading = value
},
setNsfw (state, { id, nsfw }) {
setNsfw(state, { id, nsfw }) {
const newStatus = state.allStatusesObject[id]
newStatus.nsfw = nsfw
},
queueFlush (state, { timeline, id }) {
queueFlush(state, { timeline, id }) {
state.timelines[timeline].flushMarker = id
},
queueFlushAll (state) {
queueFlushAll(state) {
Object.keys(state.timelines).forEach((timeline) => {
state.timelines[timeline].flushMarker = state.timelines[timeline].maxId
})
},
addRepeats (state, { id, rebloggedByUsers, currentUser }) {
addRepeats(state, { id, rebloggedByUsers, currentUser }) {
const newStatus = state.allStatusesObject[id]
newStatus.rebloggedBy = rebloggedByUsers.filter(_ => _)
newStatus.rebloggedBy = rebloggedByUsers.filter((_) => _)
// repeats stats can be incorrect based on polling condition, let's update them using the most recent data
newStatus.repeat_num = newStatus.rebloggedBy.length
newStatus.repeated = !!newStatus.rebloggedBy.find(({ id }) => currentUser.id === id)
newStatus.repeated = !!newStatus.rebloggedBy.find(
({ id }) => currentUser.id === id,
)
},
addFavs (state, { id, favoritedByUsers, currentUser }) {
addFavs(state, { id, favoritedByUsers, currentUser }) {
const newStatus = state.allStatusesObject[id]
newStatus.favoritedBy = favoritedByUsers.filter(_ => _)
newStatus.favoritedBy = favoritedByUsers.filter((_) => _)
// favorites stats can be incorrect based on polling condition, let's update them using the most recent data
newStatus.fave_num = newStatus.favoritedBy.length
newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id)
newStatus.favorited = !!newStatus.favoritedBy.find(
({ id }) => currentUser.id === id,
)
},
addEmojiReactionsBy (state, { id, emojiReactions }) {
addEmojiReactionsBy(state, { id, emojiReactions }) {
const status = state.allStatusesObject[id]
status.emoji_reactions = emojiReactions
},
addOwnReaction (state, { id, emoji, currentUser }) {
addOwnReaction(state, { id, emoji, currentUser }) {
const status = state.allStatusesObject[id]
const reactionIndex = findIndex(status.emoji_reactions, { name: emoji })
const reaction = status.emoji_reactions[reactionIndex] || { name: emoji, count: 0, accounts: [] }
const reaction = status.emoji_reactions[reactionIndex] || {
name: emoji,
count: 0,
accounts: [],
}
const newReaction = {
...reaction,
count: reaction.count + 1,
me: true,
accounts: [
...reaction.accounts,
currentUser
]
accounts: [...reaction.accounts, currentUser],
}
// Update count of existing reaction if it exists, otherwise append at the end
@ -475,7 +543,7 @@ export const mutations = {
status.emoji_reactions = [...status.emoji_reactions, newReaction]
}
},
removeOwnReaction (state, { id, emoji, currentUser }) {
removeOwnReaction(state, { id, emoji, currentUser }) {
const status = state.allStatusesObject[id]
const reactionIndex = findIndex(status.emoji_reactions, { name: emoji })
if (reactionIndex < 0) return
@ -487,42 +555,70 @@ export const mutations = {
...reaction,
count: reaction.count - 1,
me: false,
accounts: accounts.filter(acc => acc.id !== currentUser.id)
accounts: accounts.filter((acc) => acc.id !== currentUser.id),
}
if (newReaction.count > 0) {
status.emoji_reactions[reactionIndex] = newReaction
} else {
status.emoji_reactions = status.emoji_reactions.filter(r => r.name !== emoji)
status.emoji_reactions = status.emoji_reactions.filter(
(r) => r.name !== emoji,
)
}
},
updateStatusWithPoll (state, { id, poll }) {
updateStatusWithPoll(state, { id, poll }) {
const status = state.allStatusesObject[id]
status.poll = poll
},
setVirtualHeight (state, { statusId, height }) {
setVirtualHeight(state, { statusId, height }) {
state.allStatusesObject[statusId].virtualHeight = height
}
},
}
const statuses = {
state: defaultState(),
actions: {
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId, pagination })
addNewStatuses(
{ rootState, commit },
{
statuses,
showImmediately = false,
timeline = false,
noIdUpdate = false,
userId,
pagination,
},
) {
commit('addNewStatuses', {
statuses,
showImmediately,
timeline,
noIdUpdate,
user: rootState.users.currentUser,
userId,
pagination,
})
},
fetchStatus ({ rootState, dispatch }, id) {
return rootState.api.backendInteractor.fetchStatus({ id })
fetchStatus({ rootState, dispatch }, id) {
return rootState.api.backendInteractor
.fetchStatus({ id })
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
fetchStatusSource ({ rootState }, status) {
return apiService.fetchStatusSource({ id: status.id, credentials: rootState.users.currentUser.credentials })
fetchStatusSource({ rootState }, status) {
return apiService.fetchStatusSource({
id: status.id,
credentials: rootState.users.currentUser.credentials,
})
},
fetchStatusHistory (_, status) {
fetchStatusHistory(_, status) {
return apiService.fetchStatusHistory({ status })
},
deleteStatus ({ rootState, commit }, status) {
apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials })
deleteStatus({ rootState, commit }, status) {
apiService
.deleteStatus({
id: status.id,
credentials: rootState.users.currentUser.credentials,
})
.then(() => {
commit('setDeleted', { status })
})
@ -531,141 +627,208 @@ const statuses = {
level: 'error',
messageKey: 'status.delete_error',
messageArgs: [e.message],
timeout: 5000
timeout: 5000,
})
})
},
deleteStatusById ({ rootState, commit }, id) {
deleteStatusById({ rootState, commit }, id) {
const status = rootState.statuses.allStatusesObject[id]
commit('setDeleted', { status })
},
markStatusesAsDeleted ({ commit }, condition) {
markStatusesAsDeleted({ commit }, condition) {
commit('setManyDeleted', condition)
},
favorite ({ rootState, commit }, status) {
favorite({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: true })
rootState.api.backendInteractor.favorite({ id: status.id })
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
rootState.api.backendInteractor
.favorite({ id: status.id })
.then((status) =>
commit('setFavoritedConfirm', {
status,
user: rootState.users.currentUser,
}),
)
},
unfavorite ({ rootState, commit }, status) {
unfavorite({ rootState, commit }, status) {
// Optimistic unfavoriting...
commit('setFavorited', { status, value: false })
rootState.api.backendInteractor.unfavorite({ id: status.id })
.then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
rootState.api.backendInteractor
.unfavorite({ id: status.id })
.then((status) =>
commit('setFavoritedConfirm', {
status,
user: rootState.users.currentUser,
}),
)
},
fetchPinnedStatuses ({ rootState, dispatch }, userId) {
rootState.api.backendInteractor.fetchPinnedStatuses({ id: userId })
.then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
fetchPinnedStatuses({ rootState, dispatch }, userId) {
rootState.api.backendInteractor
.fetchPinnedStatuses({ id: userId })
.then((statuses) =>
dispatch('addNewStatuses', {
statuses,
timeline: 'user',
userId,
showImmediately: true,
noIdUpdate: true,
}),
)
},
pinStatus ({ rootState, dispatch }, statusId) {
return rootState.api.backendInteractor.pinOwnStatus({ id: statusId })
pinStatus({ rootState, dispatch }, statusId) {
return rootState.api.backendInteractor
.pinOwnStatus({ id: statusId })
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
unpinStatus ({ rootState, dispatch }, statusId) {
rootState.api.backendInteractor.unpinOwnStatus({ id: statusId })
unpinStatus({ rootState, dispatch }, statusId) {
rootState.api.backendInteractor
.unpinOwnStatus({ id: statusId })
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
muteConversation ({ rootState, commit }, { id: statusId }) {
return rootState.api.backendInteractor.muteConversation({ id: statusId })
muteConversation({ rootState, commit }, { id: statusId }) {
return rootState.api.backendInteractor
.muteConversation({ id: statusId })
.then((status) => commit('setMutedStatus', status))
},
unmuteConversation ({ rootState, commit }, { id: statusId }) {
return rootState.api.backendInteractor.unmuteConversation({ id: statusId })
unmuteConversation({ rootState, commit }, { id: statusId }) {
return rootState.api.backendInteractor
.unmuteConversation({ id: statusId })
.then((status) => commit('setMutedStatus', status))
},
retweet ({ rootState, commit }, status) {
retweet({ rootState, commit }, status) {
// Optimistic retweeting...
commit('setRetweeted', { status, value: true })
rootState.api.backendInteractor.retweet({ id: status.id })
.then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
rootState.api.backendInteractor
.retweet({ id: status.id })
.then((status) =>
commit('setRetweetedConfirm', {
status: status.retweeted_status,
user: rootState.users.currentUser,
}),
)
},
unretweet ({ rootState, commit }, status) {
unretweet({ rootState, commit }, status) {
// Optimistic unretweeting...
commit('setRetweeted', { status, value: false })
rootState.api.backendInteractor.unretweet({ id: status.id })
.then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser }))
rootState.api.backendInteractor
.unretweet({ id: status.id })
.then((status) =>
commit('setRetweetedConfirm', {
status,
user: rootState.users.currentUser,
}),
)
},
bookmark ({ rootState, commit }, status) {
bookmark({ rootState, commit }, status) {
commit('setBookmarked', { status, value: true })
rootState.api.backendInteractor.bookmarkStatus({ id: status.id, folder_id: status.bookmark_folder_id })
.then(status => {
rootState.api.backendInteractor
.bookmarkStatus({ id: status.id, folder_id: status.bookmark_folder_id })
.then((status) => {
commit('setBookmarkedConfirm', { status })
})
},
unbookmark ({ rootState, commit }, status) {
unbookmark({ rootState, commit }, status) {
commit('setBookmarked', { status, value: false })
rootState.api.backendInteractor.unbookmarkStatus({ id: status.id })
.then(status => {
rootState.api.backendInteractor
.unbookmarkStatus({ id: status.id })
.then((status) => {
commit('setBookmarkedConfirm', { status })
})
},
queueFlush ({ commit }, { timeline, id }) {
queueFlush({ commit }, { timeline, id }) {
commit('queueFlush', { timeline, id })
},
queueFlushAll ({ commit }) {
queueFlushAll({ commit }) {
commit('queueFlushAll')
},
fetchFavsAndRepeats ({ rootState, commit }, id) {
fetchFavsAndRepeats({ rootState, commit }, id) {
Promise.all([
rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
rootState.api.backendInteractor.fetchRebloggedByUsers({ id }),
]).then(([favoritedByUsers, rebloggedByUsers]) => {
commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })
commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
commit('addFavs', {
id,
favoritedByUsers,
currentUser: rootState.users.currentUser,
})
commit('addRepeats', {
id,
rebloggedByUsers,
currentUser: rootState.users.currentUser,
})
})
},
reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
reactWithEmoji({ rootState, dispatch, commit }, { id, emoji }) {
const currentUser = rootState.users.currentUser
if (!currentUser) return
commit('addOwnReaction', { id, emoji, currentUser })
rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(
() => {
dispatch('fetchEmojiReactionsBy', id)
}
)
rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(() => {
dispatch('fetchEmojiReactionsBy', id)
})
},
unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
unreactWithEmoji({ rootState, dispatch, commit }, { id, emoji }) {
const currentUser = rootState.users.currentUser
if (!currentUser) return
commit('removeOwnReaction', { id, emoji, currentUser })
rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then(
() => {
rootState.api.backendInteractor
.unreactWithEmoji({ id, emoji })
.then(() => {
dispatch('fetchEmojiReactionsBy', id)
}
)
})
},
fetchEmojiReactionsBy ({ rootState, commit }, id) {
return rootState.api.backendInteractor.fetchEmojiReactions({ id }).then(
emojiReactions => {
commit('addEmojiReactionsBy', { id, emojiReactions, currentUser: rootState.users.currentUser })
}
)
fetchEmojiReactionsBy({ rootState, commit }, id) {
return rootState.api.backendInteractor
.fetchEmojiReactions({ id })
.then((emojiReactions) => {
commit('addEmojiReactionsBy', {
id,
emojiReactions,
currentUser: rootState.users.currentUser,
})
})
},
fetchFavs ({ rootState, commit }, id) {
rootState.api.backendInteractor.fetchFavoritedByUsers({ id })
.then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
fetchFavs({ rootState, commit }, id) {
rootState.api.backendInteractor
.fetchFavoritedByUsers({ id })
.then((favoritedByUsers) =>
commit('addFavs', {
id,
favoritedByUsers,
currentUser: rootState.users.currentUser,
}),
)
},
fetchRepeats ({ rootState, commit }, id) {
rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
.then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
fetchRepeats({ rootState, commit }, id) {
rootState.api.backendInteractor
.fetchRebloggedByUsers({ id })
.then((rebloggedByUsers) =>
commit('addRepeats', {
id,
rebloggedByUsers,
currentUser: rootState.users.currentUser,
}),
)
},
search (store, { q, resolve, limit, offset, following, type }) {
return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following, type })
search(store, { q, resolve, limit, offset, following, type }) {
return store.rootState.api.backendInteractor
.search2({ q, resolve, limit, offset, following, type })
.then((data) => {
store.commit('addNewUsers', data.accounts)
store.commit('addNewUsers', data.statuses.map(s => s.user).filter(u => u))
store.commit(
'addNewUsers',
data.statuses.map((s) => s.user).filter((u) => u),
)
store.commit('addNewStatuses', { statuses: data.statuses })
return data
})
},
setVirtualHeight ({ commit }, { statusId, height }) {
setVirtualHeight({ commit }, { statusId, height }) {
commit('setVirtualHeight', { statusId, height })
}
},
},
mutations
mutations,
}
export default statuses

View file

@ -1,10 +1,25 @@
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
import {
compact,
map,
each,
mergeWith,
last,
concat,
uniq,
isArray,
} from 'lodash'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import {
windowWidth,
windowHeight,
} from '../services/window_utils/window_utils'
import apiService from '../services/api/api.service.js'
import oauthApi from '../services/new_api/oauth.js'
import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js'
import {
registerPushNotifications,
unregisterPushNotifications,
} from '../services/sw/sw.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useOAuthStore } from 'src/stores/oauth.js'
@ -14,7 +29,9 @@ import { declarations } from 'src/modules/config_declaration'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
if (!item) { return false }
if (!item) {
return false
}
const oldItem = obj[item.id]
if (oldItem) {
// We already have this, so only merge the new info.
@ -39,7 +56,8 @@ const getNotificationPermission = () => {
const Notification = window.Notification
if (!Notification) return Promise.resolve(null)
if (Notification.permission === 'default') return Notification.requestPermission()
if (Notification.permission === 'default')
return Notification.requestPermission()
return Promise.resolve(Notification.permission)
}
@ -51,30 +69,43 @@ const blockUser = (store, args) => {
store.commit('updateUserRelationship', [predictedRelationship])
store.commit('addBlockId', id)
return store.rootState.api.backendInteractor.blockUser({ id, expiresIn })
return store.rootState.api.backendInteractor
.blockUser({ id, expiresIn })
.then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('addBlockId', id)
store.commit('removeStatus', { timeline: 'friends', userId: id })
store.commit('removeStatus', { timeline: 'public', userId: id })
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: id })
store.commit('removeStatus', {
timeline: 'publicAndExternal',
userId: id,
})
})
}
const unblockUser = (store, id) => {
return store.rootState.api.backendInteractor.unblockUser({ id })
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
return store.rootState.api.backendInteractor
.unblockUser({ id })
.then((relationship) =>
store.commit('updateUserRelationship', [relationship]),
)
}
const removeUserFromFollowers = (store, id) => {
return store.rootState.api.backendInteractor.removeUserFromFollowers({ id })
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
return store.rootState.api.backendInteractor
.removeUserFromFollowers({ id })
.then((relationship) =>
store.commit('updateUserRelationship', [relationship]),
)
}
const editUserNote = (store, { id, comment }) => {
return store.rootState.api.backendInteractor.editUserNote({ id, comment })
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
return store.rootState.api.backendInteractor
.editUserNote({ id, comment })
.then((relationship) =>
store.commit('updateUserRelationship', [relationship]),
)
}
const muteUser = (store, args) => {
@ -85,7 +116,8 @@ const muteUser = (store, args) => {
store.commit('updateUserRelationship', [predictedRelationship])
store.commit('addMuteId', id)
return store.rootState.api.backendInteractor.muteUser({ id, expiresIn })
return store.rootState.api.backendInteractor
.muteUser({ id, expiresIn })
.then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('addMuteId', id)
@ -97,92 +129,105 @@ const unmuteUser = (store, id) => {
predictedRelationship.muting = false
store.commit('updateUserRelationship', [predictedRelationship])
return store.rootState.api.backendInteractor.unmuteUser({ id })
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
return store.rootState.api.backendInteractor
.unmuteUser({ id })
.then((relationship) =>
store.commit('updateUserRelationship', [relationship]),
)
}
const hideReblogs = (store, userId) => {
return store.rootState.api.backendInteractor.followUser({ id: userId, reblogs: false })
return store.rootState.api.backendInteractor
.followUser({ id: userId, reblogs: false })
.then((relationship) => {
store.commit('updateUserRelationship', [relationship])
})
}
const showReblogs = (store, userId) => {
return store.rootState.api.backendInteractor.followUser({ id: userId, reblogs: true })
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
return store.rootState.api.backendInteractor
.followUser({ id: userId, reblogs: true })
.then((relationship) =>
store.commit('updateUserRelationship', [relationship]),
)
}
const muteDomain = (store, domain) => {
return store.rootState.api.backendInteractor.muteDomain({ domain })
return store.rootState.api.backendInteractor
.muteDomain({ domain })
.then(() => store.commit('addDomainMute', domain))
}
const unmuteDomain = (store, domain) => {
return store.rootState.api.backendInteractor.unmuteDomain({ domain })
return store.rootState.api.backendInteractor
.unmuteDomain({ domain })
.then(() => store.commit('removeDomainMute', domain))
}
export const mutations = {
tagUser (state, { user: { id }, tag }) {
tagUser(state, { user: { id }, tag }) {
const user = state.usersObject[id]
const tags = user.tags || []
const newTags = tags.concat([tag])
user.tags = newTags
},
untagUser (state, { user: { id }, tag }) {
untagUser(state, { user: { id }, tag }) {
const user = state.usersObject[id]
const tags = user.tags || []
const newTags = tags.filter(t => t !== tag)
const newTags = tags.filter((t) => t !== tag)
user.tags = newTags
},
updateRight (state, { user: { id }, right, value }) {
updateRight(state, { user: { id }, right, value }) {
const user = state.usersObject[id]
const newRights = user.rights
newRights[right] = value
user.rights = newRights
},
updateActivationStatus (state, { user: { id }, deactivated }) {
updateActivationStatus(state, { user: { id }, deactivated }) {
const user = state.usersObject[id]
user.deactivated = deactivated
},
setCurrentUser (state, user) {
setCurrentUser(state, user) {
state.lastLoginName = user.screen_name
state.currentUser = mergeWith(state.currentUser || {}, user, mergeArrayLength)
state.currentUser = mergeWith(
state.currentUser || {},
user,
mergeArrayLength,
)
},
clearCurrentUser (state) {
clearCurrentUser(state) {
state.currentUser = false
state.lastLoginName = false
},
beginLogin (state) {
beginLogin(state) {
state.loggingIn = true
},
endLogin (state) {
endLogin(state) {
state.loggingIn = false
},
saveFriendIds (state, { id, friendIds }) {
saveFriendIds(state, { id, friendIds }) {
const user = state.usersObject[id]
user.friendIds = uniq(concat(user.friendIds || [], friendIds))
},
saveFollowerIds (state, { id, followerIds }) {
saveFollowerIds(state, { id, followerIds }) {
const user = state.usersObject[id]
user.followerIds = uniq(concat(user.followerIds || [], followerIds))
},
// Because frontend doesn't have a reason to keep these stuff in memory
// outside of viewing someones user profile.
clearFriends (state, userId) {
clearFriends(state, userId) {
const user = state.usersObject[userId]
if (user) {
user.friendIds = []
}
},
clearFollowers (state, userId) {
clearFollowers(state, userId) {
const user = state.usersObject[userId]
if (user) {
user.followerIds = []
}
},
addNewUsers (state, users) {
addNewUsers(state, users) {
each(users, (user) => {
if (user.relationship) {
state.relationships[user.relationship.id] = user.relationship
@ -194,51 +239,51 @@ export const mutations = {
}
})
},
updateUserRelationship (state, relationships) {
updateUserRelationship(state, relationships) {
relationships.forEach((relationship) => {
state.relationships[relationship.id] = relationship
})
},
updateUserInLists (state, { id, inLists }) {
updateUserInLists(state, { id, inLists }) {
state.usersObject[id].inLists = inLists
},
saveBlockIds (state, blockIds) {
saveBlockIds(state, blockIds) {
state.currentUser.blockIds = blockIds
},
addBlockId (state, blockId) {
addBlockId(state, blockId) {
if (state.currentUser.blockIds.indexOf(blockId) === -1) {
state.currentUser.blockIds.push(blockId)
}
},
setBlockIdsMaxId (state, blockIdsMaxId) {
setBlockIdsMaxId(state, blockIdsMaxId) {
state.currentUser.blockIdsMaxId = blockIdsMaxId
},
saveMuteIds (state, muteIds) {
saveMuteIds(state, muteIds) {
state.currentUser.muteIds = muteIds
},
setMuteIdsMaxId (state, muteIdsMaxId) {
setMuteIdsMaxId(state, muteIdsMaxId) {
state.currentUser.muteIdsMaxId = muteIdsMaxId
},
addMuteId (state, muteId) {
addMuteId(state, muteId) {
if (state.currentUser.muteIds.indexOf(muteId) === -1) {
state.currentUser.muteIds.push(muteId)
}
},
saveDomainMutes (state, domainMutes) {
saveDomainMutes(state, domainMutes) {
state.currentUser.domainMutes = domainMutes
},
addDomainMute (state, domain) {
addDomainMute(state, domain) {
if (state.currentUser.domainMutes.indexOf(domain) === -1) {
state.currentUser.domainMutes.push(domain)
}
},
removeDomainMute (state, domain) {
removeDomainMute(state, domain) {
const index = state.currentUser.domainMutes.indexOf(domain)
if (index !== -1) {
state.currentUser.domainMutes.splice(index, 1)
}
},
setPinnedToUser (state, status) {
setPinnedToUser(state, status) {
const user = state.usersObject[status.user.id]
user.pinnedStatusIds = user.pinnedStatusIds || []
const index = user.pinnedStatusIds.indexOf(status.id)
@ -249,55 +294,57 @@ export const mutations = {
user.pinnedStatusIds.splice(index, 1)
}
},
setUserForStatus (state, status) {
setUserForStatus(state, status) {
status.user = state.usersObject[status.user.id]
},
setUserForNotification (state, notification) {
setUserForNotification(state, notification) {
if (notification.type !== 'follow') {
notification.action.user = state.usersObject[notification.action.user.id]
}
notification.from_profile = state.usersObject[notification.from_profile.id]
},
setColor (state, { user: { id }, highlighted }) {
setColor(state, { user: { id }, highlighted }) {
const user = state.usersObject[id]
user.highlight = highlighted
},
signUpPending (state) {
signUpPending(state) {
state.signUpPending = true
state.signUpErrors = []
state.signUpNotice = {}
},
signUpSuccess (state) {
signUpSuccess(state) {
state.signUpPending = false
},
signUpFailure (state, errors) {
signUpFailure(state, errors) {
state.signUpPending = false
state.signUpErrors = errors
state.signUpNotice = {}
},
signUpNotice (state, notice) {
signUpNotice(state, notice) {
state.signUpPending = false
state.signUpErrors = []
state.signUpNotice = notice
}
},
}
export const getters = {
findUser: state => query => {
findUser: (state) => (query) => {
return state.usersObject[query]
},
findUserByName: state => query => {
findUserByName: (state) => (query) => {
return state.usersByNameObject[query.toLowerCase()]
},
findUserByUrl: state => query => {
return state.users
.find(u => u.statusnet_profile_url &&
u.statusnet_profile_url.toLowerCase() === query.toLowerCase())
findUserByUrl: (state) => (query) => {
return state.users.find(
(u) =>
u.statusnet_profile_url &&
u.statusnet_profile_url.toLowerCase() === query.toLowerCase(),
)
},
relationship: state => id => {
relationship: (state) => (id) => {
const rel = id && state.relationships[id]
return rel || { id, loading: true }
}
},
}
export const defaultState = {
@ -310,7 +357,7 @@ export const defaultState = {
signUpPending: false,
signUpErrors: [],
signUpNotice: {},
relationships: {}
relationships: {},
}
const users = {
@ -318,47 +365,54 @@ const users = {
mutations,
getters,
actions: {
fetchUserIfMissing (store, id) {
fetchUserIfMissing(store, id) {
if (!store.getters.findUser(id)) {
store.dispatch('fetchUser', id)
}
},
fetchUser (store, id) {
return store.rootState.api.backendInteractor.fetchUser({ id })
fetchUser(store, id) {
return store.rootState.api.backendInteractor
.fetchUser({ id })
.then((user) => {
store.commit('addNewUsers', [user])
return user
})
},
fetchUserByName (store, name) {
return store.rootState.api.backendInteractor.fetchUserByName({ name })
fetchUserByName(store, name) {
return store.rootState.api.backendInteractor
.fetchUserByName({ name })
.then((user) => {
store.commit('addNewUsers', [user])
return user
})
},
fetchUserRelationship (store, id) {
fetchUserRelationship(store, id) {
if (store.state.currentUser) {
store.rootState.api.backendInteractor.fetchUserRelationship({ id })
.then((relationships) => store.commit('updateUserRelationship', relationships))
store.rootState.api.backendInteractor
.fetchUserRelationship({ id })
.then((relationships) =>
store.commit('updateUserRelationship', relationships),
)
}
},
fetchUserInLists (store, id) {
fetchUserInLists(store, id) {
if (store.state.currentUser) {
store.rootState.api.backendInteractor.fetchUserInLists({ id })
store.rootState.api.backendInteractor
.fetchUserInLists({ id })
.then((inLists) => store.commit('updateUserInLists', { id, inLists }))
}
},
fetchBlocks (store, args) {
fetchBlocks(store, args) {
const { reset } = args || {}
const maxId = store.state.currentUser.blockIdsMaxId
return store.rootState.api.backendInteractor.fetchBlocks({ maxId })
return store.rootState.api.backendInteractor
.fetchBlocks({ maxId })
.then((blocks) => {
if (reset) {
store.commit('saveBlockIds', map(blocks, 'id'))
} else {
map(blocks, 'id').map(id => store.commit('addBlockId', id))
map(blocks, 'id').map((id) => store.commit('addBlockId', id))
}
if (blocks.length) {
store.commit('setBlockIdsMaxId', last(blocks).id)
@ -367,34 +421,35 @@ const users = {
return blocks
})
},
blockUser (store, data) {
blockUser(store, data) {
return blockUser(store, data)
},
unblockUser (store, data) {
unblockUser(store, data) {
return unblockUser(store, data)
},
removeUserFromFollowers (store, id) {
removeUserFromFollowers(store, id) {
return removeUserFromFollowers(store, id)
},
blockUsers (store, data = []) {
return Promise.all(data.map(d => blockUser(store, d)))
blockUsers(store, data = []) {
return Promise.all(data.map((d) => blockUser(store, d)))
},
unblockUsers (store, data = []) {
return Promise.all(data.map(d => unblockUser(store, d)))
unblockUsers(store, data = []) {
return Promise.all(data.map((d) => unblockUser(store, d)))
},
editUserNote (store, args) {
editUserNote(store, args) {
return editUserNote(store, args)
},
fetchMutes (store, args) {
fetchMutes(store, args) {
const { reset } = args || {}
const maxId = store.state.currentUser.muteIdsMaxId
return store.rootState.api.backendInteractor.fetchMutes({ maxId })
return store.rootState.api.backendInteractor
.fetchMutes({ maxId })
.then((mutes) => {
if (reset) {
store.commit('saveMuteIds', map(mutes, 'id'))
} else {
map(mutes, 'id').map(id => store.commit('addMuteId', id))
map(mutes, 'id').map((id) => store.commit('addMuteId', id))
}
if (mutes.length) {
store.commit('setMuteIdsMaxId', last(mutes).id)
@ -403,99 +458,118 @@ const users = {
return mutes
})
},
muteUser (store, data) {
muteUser(store, data) {
return muteUser(store, data)
},
unmuteUser (store, id) {
unmuteUser(store, id) {
return unmuteUser(store, id)
},
hideReblogs (store, id) {
hideReblogs(store, id) {
return hideReblogs(store, id)
},
showReblogs (store, id) {
showReblogs(store, id) {
return showReblogs(store, id)
},
muteUsers (store, data = []) {
return Promise.all(data.map(d => muteUser(store, d)))
muteUsers(store, data = []) {
return Promise.all(data.map((d) => muteUser(store, d)))
},
unmuteUsers (store, ids = []) {
return Promise.all(ids.map(d => unmuteUser(store, d)))
unmuteUsers(store, ids = []) {
return Promise.all(ids.map((d) => unmuteUser(store, d)))
},
fetchDomainMutes (store) {
return store.rootState.api.backendInteractor.fetchDomainMutes()
fetchDomainMutes(store) {
return store.rootState.api.backendInteractor
.fetchDomainMutes()
.then((domainMutes) => {
store.commit('saveDomainMutes', domainMutes)
return domainMutes
})
},
muteDomain (store, domain) {
muteDomain(store, domain) {
return muteDomain(store, domain)
},
unmuteDomain (store, domain) {
unmuteDomain(store, domain) {
return unmuteDomain(store, domain)
},
muteDomains (store, domains = []) {
return Promise.all(domains.map(domain => muteDomain(store, domain)))
muteDomains(store, domains = []) {
return Promise.all(domains.map((domain) => muteDomain(store, domain)))
},
unmuteDomains (store, domain = []) {
return Promise.all(domain.map(domain => unmuteDomain(store, domain)))
unmuteDomains(store, domain = []) {
return Promise.all(domain.map((domain) => unmuteDomain(store, domain)))
},
fetchFriends ({ rootState, commit }, id) {
fetchFriends({ rootState, commit }, id) {
const user = rootState.users.usersObject[id]
const maxId = last(user.friendIds)
return rootState.api.backendInteractor.fetchFriends({ id, maxId })
return rootState.api.backendInteractor
.fetchFriends({ id, maxId })
.then((friends) => {
commit('addNewUsers', friends)
commit('saveFriendIds', { id, friendIds: map(friends, 'id') })
return friends
})
},
fetchFollowers ({ rootState, commit }, id) {
fetchFollowers({ rootState, commit }, id) {
const user = rootState.users.usersObject[id]
const maxId = last(user.followerIds)
return rootState.api.backendInteractor.fetchFollowers({ id, maxId })
return rootState.api.backendInteractor
.fetchFollowers({ id, maxId })
.then((followers) => {
commit('addNewUsers', followers)
commit('saveFollowerIds', { id, followerIds: map(followers, 'id') })
return followers
})
},
clearFriends ({ commit }, userId) {
clearFriends({ commit }, userId) {
commit('clearFriends', userId)
},
clearFollowers ({ commit }, userId) {
clearFollowers({ commit }, userId) {
commit('clearFollowers', userId)
},
subscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.followUser({ id, notify: true })
.then((relationship) => commit('updateUserRelationship', [relationship]))
subscribeUser({ rootState, commit }, id) {
return rootState.api.backendInteractor
.followUser({ id, notify: true })
.then((relationship) =>
commit('updateUserRelationship', [relationship]),
)
},
unsubscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.followUser({ id, notify: false })
.then((relationship) => commit('updateUserRelationship', [relationship]))
unsubscribeUser({ rootState, commit }, id) {
return rootState.api.backendInteractor
.followUser({ id, notify: false })
.then((relationship) =>
commit('updateUserRelationship', [relationship]),
)
},
toggleActivationStatus ({ rootState, commit }, { user }) {
const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser
api({ user })
.then((user) => { const deactivated = !user.is_active; commit('updateActivationStatus', { user, deactivated }) })
toggleActivationStatus({ rootState, commit }, { user }) {
const api = user.deactivated
? rootState.api.backendInteractor.activateUser
: rootState.api.backendInteractor.deactivateUser
api({ user }).then((user) => {
const deactivated = !user.is_active
commit('updateActivationStatus', { user, deactivated })
})
},
registerPushNotifications (store) {
registerPushNotifications(store) {
const token = store.state.currentUser.credentials
const vapidPublicKey = store.rootState.instance.vapidPublicKey
const isEnabled = store.rootState.config.webPushNotifications
const notificationVisibility = store.rootState.config.notificationVisibility
const notificationVisibility =
store.rootState.config.notificationVisibility
registerPushNotifications(isEnabled, vapidPublicKey, token, notificationVisibility)
registerPushNotifications(
isEnabled,
vapidPublicKey,
token,
notificationVisibility,
)
},
unregisterPushNotifications (store) {
unregisterPushNotifications(store) {
const token = store.state.currentUser.credentials
unregisterPushNotifications(token)
},
addNewUsers ({ commit }, users) {
addNewUsers({ commit }, users) {
commit('addNewUsers', users)
},
addNewStatuses (store, { statuses }) {
addNewStatuses(store, { statuses }) {
const users = map(statuses, 'user')
const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))
store.commit('addNewUsers', users)
@ -514,10 +588,10 @@ const users = {
store.commit('setPinnedToUser', status)
})
},
addNewNotifications (store, { notifications }) {
addNewNotifications(store, { notifications }) {
const users = map(notifications, 'from_profile')
const targetUsers = map(notifications, 'target').filter(_ => _)
const notificationIds = notifications.map(_ => _.id)
const targetUsers = map(notifications, 'target').filter((_) => _)
const notificationIds = notifications.map((_) => _.id)
store.commit('addNewUsers', users)
store.commit('addNewUsers', targetUsers)
@ -531,29 +605,32 @@ const users = {
store.commit('setUserForNotification', notification)
})
},
searchUsers ({ rootState, commit }, { query }) {
return rootState.api.backendInteractor.searchUsers({ query })
searchUsers({ rootState, commit }, { query }) {
return rootState.api.backendInteractor
.searchUsers({ query })
.then((users) => {
commit('addNewUsers', users)
return users
})
},
async signUp (store, userInfo) {
async signUp(store, userInfo) {
const oauthStore = useOAuthStore()
store.commit('signUpPending')
try {
const token = await oauthStore.ensureAppToken()
const data = await apiService.register(
{ credentials: token, params: { ...userInfo } }
)
const data = await apiService.register({
credentials: token,
params: { ...userInfo },
})
if (data.access_token) {
store.commit('signUpSuccess')
oauthStore.setToken(data.access_token)
store.dispatch('loginUser', data.access_token)
return 'ok'
} else { // Request succeeded, but user cannot login yet.
} else {
// Request succeeded, but user cannot login yet.
store.commit('signUpNotice', data)
return 'request_sent'
}
@ -563,22 +640,23 @@ const users = {
throw e
}
},
async getCaptcha (store) {
async getCaptcha(store) {
return store.rootState.api.backendInteractor.getCaptcha()
},
logout (store) {
logout(store) {
const oauth = useOAuthStore()
const { instance } = store.rootState
// NOTE: No need to verify the app still exists, because if it doesn't,
// the token will be invalid too
return oauth.ensureApp()
return oauth
.ensureApp()
.then((app) => {
const params = {
app,
instance: instance.server,
token: oauth.userToken
token: oauth.userToken,
}
return oauthApi.revokeToken(params)
@ -588,7 +666,10 @@ const users = {
store.dispatch('disconnectFromSocket')
oauth.clearToken()
store.dispatch('stopFetchingTimeline', 'friends')
store.commit('setBackendInteractor', backendInteractorService(oauth.getToken))
store.commit(
'setBackendInteractor',
backendInteractorService(oauth.getToken),
)
store.dispatch('stopFetchingNotifications')
store.dispatch('stopFetchingLists')
store.dispatch('stopFetchingBookmarkFolders')
@ -602,13 +683,14 @@ const users = {
store.commit('clearServerSideStorage')
})
},
loginUser (store, accessToken) {
loginUser(store, accessToken) {
return new Promise((resolve, reject) => {
const commit = store.commit
const dispatch = store.dispatch
const rootState = store.rootState
commit('beginLogin')
store.rootState.api.backendInteractor.verifyCredentials(accessToken)
store.rootState.api.backendInteractor
.verifyCredentials(accessToken)
.then((data) => {
if (!data.error) {
const user = data
@ -624,11 +706,15 @@ const users = {
dispatch('fetchEmoji')
getNotificationPermission()
.then(permission => useInterfaceStore().setNotificationPermission(permission))
getNotificationPermission().then((permission) =>
useInterfaceStore().setNotificationPermission(permission),
)
// Set our new backend interactor
commit('setBackendInteractor', backendInteractorService(accessToken))
commit(
'setBackendInteractor',
backendInteractorService(accessToken),
)
// Do server-side storage migrations
@ -645,17 +731,23 @@ const users = {
useServerSideStorageStore().setFlag({ flag: 'configMigration', value: 0 })
/**/
const { configMigration } = useServerSideStorageStore().flagStorage
const { configMigration } =
useServerSideStorageStore().flagStorage
declarations
.filter(x => {
return x.store === 'server-side' &&
.filter((x) => {
return (
x.store === 'server-side' &&
x.migrationNum > 0 &&
x.migrationNum > configMigration
)
})
.toSorted((a, b) => a.configMigration - b.configMigration)
.forEach(value => {
.forEach((value) => {
value.migration(useServerSideStorageStore(), store.rootState)
useServerSideStorageStore().setFlag({ flag: 'configMigration', value: value.migrationNum })
useServerSideStorageStore().setFlag({
flag: 'configMigration',
value: value.migrationNum,
})
useServerSideStorageStore().pushServerSideStorage()
})
@ -689,12 +781,20 @@ const users = {
if (store.getters.mergedConfig.useStreamingApi) {
dispatch('fetchTimeline', { timeline: 'friends', since: null })
dispatch('fetchNotifications', { since: null })
dispatch('enableMastoSockets', true).catch((error) => {
console.error('Failed initializing MastoAPI Streaming socket', error)
}).then(() => {
dispatch('fetchChats', { latest: true })
setTimeout(() => dispatch('setNotificationsSilence', false), 10000)
})
dispatch('enableMastoSockets', true)
.catch((error) => {
console.error(
'Failed initializing MastoAPI Streaming socket',
error,
)
})
.then(() => {
dispatch('fetchChats', { latest: true })
setTimeout(
() => dispatch('setNotificationsSilence', false),
10000,
)
})
} else {
startPolling()
}
@ -706,7 +806,8 @@ const users = {
useInterfaceStore().setLayoutHeight(windowHeight())
// Fetch our friends
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
store.rootState.api.backendInteractor
.fetchFriends({ id: user.id })
.then((friends) => commit('addNewUsers', friends))
} else {
const response = data.error
@ -733,8 +834,8 @@ const users = {
reject(new Error('Failed to connect to server, try again'))
})
})
}
}
},
},
}
export default users