aboutsummaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/api.js48
-rw-r--r--src/modules/auth_flow.js90
-rw-r--r--src/modules/chat.js12
-rw-r--r--src/modules/config.js11
-rw-r--r--src/modules/errors.js1
-rw-r--r--src/modules/instance.js27
-rw-r--r--src/modules/interface.js9
-rw-r--r--src/modules/oauth.js45
-rw-r--r--src/modules/oauth_tokens.js4
-rw-r--r--src/modules/polls.js70
-rw-r--r--src/modules/postStatus.js25
-rw-r--r--src/modules/reports.js30
-rw-r--r--src/modules/statuses.js298
-rw-r--r--src/modules/users.js353
14 files changed, 783 insertions, 240 deletions
diff --git a/src/modules/api.js b/src/modules/api.js
index 31cb55c6..eb6a7980 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -6,18 +6,17 @@ const api = {
backendInteractor: backendInteractorService(),
fetchers: {},
socket: null,
- chatDisabled: false,
followRequests: []
},
mutations: {
setBackendInteractor (state, backendInteractor) {
state.backendInteractor = backendInteractor
},
- addFetcher (state, {timeline, fetcher}) {
- state.fetchers[timeline] = fetcher
+ addFetcher (state, { fetcherName, fetcher }) {
+ state.fetchers[fetcherName] = fetcher
},
- removeFetcher (state, {timeline}) {
- delete state.fetchers[timeline]
+ removeFetcher (state, { fetcherName }) {
+ delete state.fetchers[fetcherName]
},
setWsToken (state, token) {
state.wsToken = token
@@ -25,40 +24,47 @@ const api = {
setSocket (state, socket) {
state.socket = socket
},
- setChatDisabled (state, value) {
- state.chatDisabled = value
- },
setFollowRequests (state, value) {
state.followRequests = value
}
},
actions: {
- startFetching (store, {timeline = 'friends', tag = false, userId = false}) {
+ startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) {
// Don't start fetching if we already are.
if (store.state.fetchers[timeline]) return
- const fetcher = store.state.backendInteractor.startFetching({ timeline, store, userId, tag })
- store.commit('addFetcher', { timeline, fetcher })
+ const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, tag })
+ store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
- stopFetching (store, timeline) {
- const fetcher = store.state.fetchers[timeline]
+ startFetchingNotifications (store) {
+ // Don't start fetching if we already are.
+ if (store.state.fetchers['notifications']) return
+
+ const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
+ store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
+ },
+ stopFetching (store, fetcherName) {
+ const fetcher = store.state.fetchers[fetcherName]
window.clearInterval(fetcher)
- store.commit('removeFetcher', {timeline})
+ store.commit('removeFetcher', { fetcherName })
},
setWsToken (store, token) {
store.commit('setWsToken', token)
},
- initializeSocket (store) {
+ initializeSocket ({ dispatch, commit, state, rootState }) {
// Set up websocket connection
- if (!store.state.chatDisabled) {
- const token = store.state.wsToken
- const socket = new Socket('/socket', {params: {token}})
+ const token = state.wsToken
+ if (rootState.instance.chatAvailable && typeof token !== 'undefined' && state.socket === null) {
+ const socket = new Socket('/socket', { params: { token } })
socket.connect()
- store.dispatch('initializeChat', socket)
+
+ commit('setSocket', socket)
+ dispatch('initializeChat', socket)
}
},
- disableChat (store) {
- store.commit('setChatDisabled', true)
+ disconnectFromSocket ({ commit, state }) {
+ state.socket && state.socket.disconnect()
+ commit('setSocket', null)
},
removeFollowRequest (store, request) {
let requests = store.state.followRequests.filter((it) => it !== request)
diff --git a/src/modules/auth_flow.js b/src/modules/auth_flow.js
new file mode 100644
index 00000000..d0a90feb
--- /dev/null
+++ b/src/modules/auth_flow.js
@@ -0,0 +1,90 @@
+const PASSWORD_STRATEGY = 'password'
+const TOKEN_STRATEGY = 'token'
+
+// MFA strategies
+const TOTP_STRATEGY = 'totp'
+const RECOVERY_STRATEGY = 'recovery'
+
+// initial state
+const state = {
+ app: null,
+ settings: {},
+ strategy: PASSWORD_STRATEGY,
+ initStrategy: PASSWORD_STRATEGY // default strategy from config
+}
+
+const resetState = (state) => {
+ state.strategy = state.initStrategy
+ state.settings = {}
+ state.app = null
+}
+
+// getters
+const getters = {
+ app: (state, getters) => {
+ return state.app
+ },
+ settings: (state, getters) => {
+ return state.settings
+ },
+ requiredPassword: (state, getters, rootState) => {
+ return state.strategy === PASSWORD_STRATEGY
+ },
+ requiredToken: (state, getters, rootState) => {
+ return state.strategy === TOKEN_STRATEGY
+ },
+ requiredTOTP: (state, getters, rootState) => {
+ return state.strategy === TOTP_STRATEGY
+ },
+ requiredRecovery: (state, getters, rootState) => {
+ return state.strategy === RECOVERY_STRATEGY
+ }
+}
+
+// mutations
+const mutations = {
+ setInitialStrategy (state, strategy) {
+ if (strategy) {
+ state.initStrategy = strategy
+ state.strategy = strategy
+ }
+ },
+ requirePassword (state) {
+ state.strategy = PASSWORD_STRATEGY
+ },
+ requireToken (state) {
+ state.strategy = TOKEN_STRATEGY
+ },
+ requireMFA (state, { app, settings }) {
+ state.settings = settings
+ state.app = app
+ state.strategy = TOTP_STRATEGY // default strategy of MFA
+ },
+ requireRecovery (state) {
+ state.strategy = RECOVERY_STRATEGY
+ },
+ requireTOTP (state) {
+ state.strategy = TOTP_STRATEGY
+ },
+ abortMFA (state) {
+ resetState(state)
+ }
+}
+
+// actions
+const actions = {
+ // eslint-disable-next-line camelcase
+ async login ({ state, dispatch, commit }, { access_token }) {
+ commit('setToken', access_token, { root: true })
+ await dispatch('loginUser', access_token, { root: true })
+ resetState(state)
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ getters,
+ mutations,
+ actions
+}
diff --git a/src/modules/chat.js b/src/modules/chat.js
index 2804e577..c798549d 100644
--- a/src/modules/chat.js
+++ b/src/modules/chat.js
@@ -1,16 +1,12 @@
const chat = {
state: {
messages: [],
- channel: {state: ''},
- socket: null
+ channel: { state: '' }
},
mutations: {
setChannel (state, channel) {
state.channel = channel
},
- setSocket (state, socket) {
- state.socket = socket
- },
addMessage (state, message) {
state.messages.push(message)
state.messages = state.messages.slice(-19, 20)
@@ -20,16 +16,12 @@ const chat = {
}
},
actions: {
- disconnectFromChat (store) {
- store.state.socket.disconnect()
- },
initializeChat (store, socket) {
const channel = socket.channel('chat:public')
- store.commit('setSocket', socket)
channel.on('new_msg', (msg) => {
store.commit('addMessage', msg)
})
- channel.on('messages', ({messages}) => {
+ channel.on('messages', ({ messages }) => {
store.commit('setMessages', messages)
})
channel.join()
diff --git a/src/modules/config.js b/src/modules/config.js
index 1c30c203..cf04d14f 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -5,7 +5,9 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
const defaultState = {
colors: {},
+ hideMutedPosts: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
+ padEmoji: true,
hideAttachments: false,
hideAttachmentsInConv: false,
maxThumbnails: 16,
@@ -16,6 +18,7 @@ const defaultState = {
autoLoad: true,
streaming: false,
hoverPreview: true,
+ autohideFloatingPostButton: false,
pauseOnUnfocused: true,
stopGifs: false,
replyVisibility: 'all',
@@ -29,10 +32,12 @@ const defaultState = {
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
+ hideScopeNotice: false,
scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default
- postContentType: undefined // instance default
+ postContentType: undefined, // instance default
+ minimalScopesMode: undefined // instance default
}
const config = {
@@ -52,10 +57,10 @@ const config = {
},
actions: {
setHighlight ({ commit, dispatch }, { user, color, type }) {
- commit('setHighlight', {user, color, type})
+ commit('setHighlight', { user, color, type })
},
setOption ({ commit, dispatch }, { name, value }) {
- commit('setOption', {name, value})
+ commit('setOption', { name, value })
switch (name) {
case 'theme':
setPreset(value, commit)
diff --git a/src/modules/errors.js b/src/modules/errors.js
index c809e1b5..ca89dc0f 100644
--- a/src/modules/errors.js
+++ b/src/modules/errors.js
@@ -9,4 +9,3 @@ export function humanizeErrors (errors) {
return [...errs, message]
}, [])
}
-
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 24c52f9c..7d602aa1 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -5,6 +5,7 @@ const defaultState = {
// Stuff from static/config.json and apiConfig
name: 'Pleroma FE',
registrationOpen: true,
+ safeDM: true,
textlimit: 5000,
server: 'http://localhost:4040/',
theme: 'pleroma-dark',
@@ -15,9 +16,8 @@ const defaultState = {
redirectRootNoLogin: '/main/all',
redirectRootLogin: '/main/friends',
showInstanceSpecificPanel: false,
- scopeOptionsEnabled: true,
- formattingOptionsEnabled: false,
alwaysShowSubjectInput: true,
+ hideMutedPosts: false,
collapseMessageWithSubject: false,
hidePostStats: false,
hideUserStats: false,
@@ -26,11 +26,11 @@ const defaultState = {
scopeCopy: true,
subjectLineBehavior: 'email',
postContentType: 'text/plain',
- loginMethod: 'password',
nsfwCensorImage: undefined,
vapidPublicKey: undefined,
noAttachmentLinks: false,
showFeaturesPanel: true,
+ minimalScopesMode: false,
// Nasty stuff
pleromaBackend: true,
@@ -48,7 +48,19 @@ const defaultState = {
// Html stuff
instanceSpecificPanelContent: '',
- tos: ''
+ tos: '',
+
+ // Version Information
+ backendVersion: '',
+ frontendVersion: '',
+
+ pollsAvailable: false,
+ pollLimits: {
+ max_options: 4,
+ max_option_chars: 255,
+ min_expiration: 60,
+ max_expiration: 60 * 60 * 24
+ }
}
const instance = {
@@ -62,11 +74,16 @@ const instance = {
},
actions: {
setInstanceOption ({ commit, dispatch }, { name, value }) {
- commit('setInstanceOption', {name, value})
+ commit('setInstanceOption', { name, value })
switch (name) {
case 'name':
dispatch('setPageTitle')
break
+ case 'chatAvailable':
+ if (value) {
+ dispatch('initializeSocket')
+ }
+ break
}
},
setTheme ({ commit }, themeName) {
diff --git a/src/modules/interface.js b/src/modules/interface.js
index 956c9cb3..5b2762e5 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -11,7 +11,8 @@ const defaultState = {
window.CSS.supports('filter', 'drop-shadow(0 0)') ||
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
)
- }
+ },
+ mobileLayout: false
}
const interfaceMod = {
@@ -31,6 +32,9 @@ const interfaceMod = {
},
setNotificationPermission (state, permission) {
state.notificationPermission = permission
+ },
+ setMobileLayout (state, value) {
+ state.mobileLayout = value
}
},
actions: {
@@ -42,6 +46,9 @@ const interfaceMod = {
},
setNotificationPermission ({ commit }, permission) {
commit('setNotificationPermission', permission)
+ },
+ setMobileLayout ({ commit }, value) {
+ commit('setMobileLayout', value)
}
}
}
diff --git a/src/modules/oauth.js b/src/modules/oauth.js
index 144ff830..a2a83450 100644
--- a/src/modules/oauth.js
+++ b/src/modules/oauth.js
@@ -1,16 +1,47 @@
+import { delete as del } from 'vue'
+
const oauth = {
state: {
- client_id: false,
- client_secret: false,
- token: false
+ clientId: false,
+ clientSecret: false,
+ /* App token is authentication for app without any user, used mostly for
+ * MastoAPI's registration of new users, stored so that we can fall back to
+ * it on logout
+ */
+ appToken: false,
+ /* User token is authentication for app with user, this is for every calls
+ * that need authorized user to be successful (i.e. posting, liking etc)
+ */
+ userToken: false
},
mutations: {
- setClientData (state, data) {
- state.client_id = data.client_id
- state.client_secret = data.client_secret
+ setClientData (state, { clientId, clientSecret }) {
+ state.clientId = clientId
+ state.clientSecret = clientSecret
+ },
+ setAppToken (state, token) {
+ state.appToken = token
},
setToken (state, token) {
- state.token = token
+ state.userToken = token
+ },
+ clearToken (state) {
+ state.userToken = false
+ // state.token is userToken with older name, coming from persistent state
+ // let's clear it as well, since it is being used as a fallback of state.userToken
+ del(state, 'token')
+ }
+ },
+ getters: {
+ getToken: state => () => {
+ // state.token is userToken with older name, coming from persistent state
+ // added here for smoother transition, otherwise user will be logged out
+ return state.userToken || state.token || state.appToken
+ },
+ getUserToken: state => () => {
+ // state.token is userToken with older name, coming from persistent state
+ // added here for smoother transition, otherwise user will be logged out
+ return state.userToken || state.token
}
}
}
diff --git a/src/modules/oauth_tokens.js b/src/modules/oauth_tokens.js
index 00ac1431..0159a3f1 100644
--- a/src/modules/oauth_tokens.js
+++ b/src/modules/oauth_tokens.js
@@ -3,12 +3,12 @@ const oauthTokens = {
tokens: []
},
actions: {
- fetchTokens ({rootState, commit}) {
+ fetchTokens ({ rootState, commit }) {
rootState.api.backendInteractor.fetchOAuthTokens().then((tokens) => {
commit('swapTokens', tokens)
})
},
- revokeToken ({rootState, commit, state}, id) {
+ revokeToken ({ rootState, commit, state }, id) {
rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => {
if (response.status === 201) {
commit('swapTokens', state.tokens.filter(token => token.id !== id))
diff --git a/src/modules/polls.js b/src/modules/polls.js
new file mode 100644
index 00000000..e6158b63
--- /dev/null
+++ b/src/modules/polls.js
@@ -0,0 +1,70 @@
+import { merge } from 'lodash'
+import { set } from 'vue'
+
+const polls = {
+ state: {
+ // Contains key = id, value = number of trackers for this poll
+ trackedPolls: {},
+ pollsObject: {}
+ },
+ mutations: {
+ mergeOrAddPoll (state, poll) {
+ const existingPoll = state.pollsObject[poll.id]
+ // Make expired-state change trigger re-renders properly
+ poll.expired = Date.now() > Date.parse(poll.expires_at)
+ if (existingPoll) {
+ set(state.pollsObject, poll.id, merge(existingPoll, poll))
+ } else {
+ set(state.pollsObject, poll.id, poll)
+ }
+ },
+ trackPoll (state, pollId) {
+ const currentValue = state.trackedPolls[pollId]
+ if (currentValue) {
+ set(state.trackedPolls, pollId, currentValue + 1)
+ } else {
+ set(state.trackedPolls, pollId, 1)
+ }
+ },
+ untrackPoll (state, pollId) {
+ const currentValue = state.trackedPolls[pollId]
+ if (currentValue) {
+ set(state.trackedPolls, pollId, currentValue - 1)
+ } else {
+ set(state.trackedPolls, pollId, 0)
+ }
+ }
+ },
+ actions: {
+ mergeOrAddPoll ({ commit }, poll) {
+ commit('mergeOrAddPoll', poll)
+ },
+ updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
+ rootState.api.backendInteractor.fetchPoll(pollId).then(poll => {
+ setTimeout(() => {
+ if (rootState.polls.trackedPolls[pollId]) {
+ dispatch('updateTrackedPoll', pollId)
+ }
+ }, 30 * 1000)
+ commit('mergeOrAddPoll', poll)
+ })
+ },
+ trackPoll ({ rootState, commit, dispatch }, pollId) {
+ if (!rootState.polls.trackedPolls[pollId]) {
+ setTimeout(() => dispatch('updateTrackedPoll', pollId), 30 * 1000)
+ }
+ commit('trackPoll', pollId)
+ },
+ untrackPoll ({ commit }, pollId) {
+ commit('untrackPoll', pollId)
+ },
+ votePoll ({ rootState, commit }, { id, pollId, choices }) {
+ return rootState.api.backendInteractor.vote(pollId, choices).then(poll => {
+ commit('mergeOrAddPoll', poll)
+ return poll
+ })
+ }
+ }
+}
+
+export default polls
diff --git a/src/modules/postStatus.js b/src/modules/postStatus.js
new file mode 100644
index 00000000..638c1fb2
--- /dev/null
+++ b/src/modules/postStatus.js
@@ -0,0 +1,25 @@
+const postStatus = {
+ state: {
+ params: null,
+ modalActivated: false
+ },
+ mutations: {
+ openPostStatusModal (state, params) {
+ state.params = params
+ state.modalActivated = true
+ },
+ closePostStatusModal (state) {
+ state.modalActivated = false
+ }
+ },
+ actions: {
+ openPostStatusModal ({ commit }, params) {
+ commit('openPostStatusModal', params)
+ },
+ closePostStatusModal ({ commit }) {
+ commit('closePostStatusModal')
+ }
+ }
+}
+
+export default postStatus
diff --git a/src/modules/reports.js b/src/modules/reports.js
new file mode 100644
index 00000000..904022f1
--- /dev/null
+++ b/src/modules/reports.js
@@ -0,0 +1,30 @@
+import filter from 'lodash/filter'
+
+const reports = {
+ state: {
+ userId: null,
+ statuses: [],
+ modalActivated: false
+ },
+ mutations: {
+ openUserReportingModal (state, { userId, statuses }) {
+ state.userId = userId
+ state.statuses = statuses
+ state.modalActivated = true
+ },
+ closeUserReportingModal (state) {
+ state.modalActivated = false
+ }
+ },
+ actions: {
+ openUserReportingModal ({ rootState, commit }, userId) {
+ const statuses = filter(rootState.statuses.allStatuses, status => status.user.id === userId)
+ commit('openUserReportingModal', { userId, statuses })
+ },
+ closeUserReportingModal ({ commit }) {
+ commit('closeUserReportingModal')
+ }
+ }
+}
+
+export default reports
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 6b512fa3..918065d2 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -1,4 +1,5 @@
-import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray } from 'lodash'
+import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
+import { set } from 'vue'
import apiService from '../services/api/api.service.js'
// import parse from '../services/status_parser/status_parser.js'
@@ -19,20 +20,22 @@ const emptyTl = (userId = 0) => ({
flushMarker: 0
})
+const emptyNotifications = () => ({
+ desktopNotificationSilence: true,
+ maxId: 0,
+ minId: Number.POSITIVE_INFINITY,
+ data: [],
+ idStore: {},
+ loading: false,
+ error: false
+})
+
export const defaultState = () => ({
allStatuses: [],
allStatusesObject: {},
+ conversationsObject: {},
maxId: 0,
- notifications: {
- desktopNotificationSilence: true,
- maxId: 0,
- minId: Number.POSITIVE_INFINITY,
- data: [],
- idStore: {},
- loading: false,
- error: false,
- fetcherId: null
- },
+ notifications: emptyNotifications(),
favorites: new Set(),
error: false,
timelines: {
@@ -72,16 +75,18 @@ const mergeOrAdd = (arr, obj, item) => {
if (oldItem) {
// We already have this, so only merge the new info.
- merge(oldItem, item)
+ // 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'))
// Reactivity fix.
oldItem.attachments.splice(oldItem.attachments.length)
- return {item: oldItem, new: false}
+ return { item: oldItem, new: false }
} else {
// This is a new item, prepare it
prepareStatus(item)
arr.push(item)
- obj[item.id] = item
- return {item, new: true}
+ set(obj, item.id, item)
+ return { item, new: true }
}
}
@@ -108,19 +113,52 @@ const sortTimeline = (timeline) => {
return timeline
}
-const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {}, noIdUpdate = false, userId }) => {
+// Add status to the global storages (arrays and objects maintaining statuses) except timelines
+const addStatusToGlobalStorage = (state, data) => {
+ const result = mergeOrAdd(state.allStatuses, state.allStatusesObject, data)
+ if (result.new) {
+ // Add to conversation
+ const status = result.item
+ const conversationsObject = state.conversationsObject
+ const conversationId = status.statusnet_conversation_id
+ if (conversationsObject[conversationId]) {
+ conversationsObject[conversationId].push(status)
+ } else {
+ set(conversationsObject, conversationId, [status])
+ }
+ }
+ return result
+}
+
+// Remove status from the global storages (arrays and objects maintaining statuses) except timelines
+const removeStatusFromGlobalStorage = (state, status) => {
+ remove(state.allStatuses, { id: status.id })
+
+ // TODO: Need to remove from allStatusesObject?
+
+ // Remove possible notification
+ remove(state.notifications.data, ({ action: { id } }) => id === status.id)
+
+ // Remove from conversation
+ const conversationId = status.statusnet_conversation_id
+ if (state.conversationsObject[conversationId]) {
+ remove(state.conversationsObject[conversationId], { id: status.id })
+ }
+}
+
+const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {},
+ noIdUpdate = false, userId }) => {
// Sanity check
if (!isArray(statuses)) {
return false
}
const allStatuses = state.allStatuses
- const allStatusesObject = state.allStatusesObject
const timelineObject = state.timelines[timeline]
const maxNew = statuses.length > 0 ? maxBy(statuses, 'id').id : 0
const minNew = statuses.length > 0 ? minBy(statuses, 'id').id : 0
- const newer = timeline && maxNew > timelineObject.maxId && 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) {
@@ -138,7 +176,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
const addStatus = (data, showImmediately, addToTimeline = true) => {
- const result = mergeOrAdd(allStatuses, allStatusesObject, data)
+ const result = addStatusToGlobalStorage(state, data)
const status = result.item
if (result.new) {
@@ -232,16 +270,13 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
},
'deletion': (deletion) => {
const uri = deletion.uri
-
- // Remove possible notification
- const status = find(allStatuses, {uri})
+ const status = find(allStatuses, { uri })
if (!status) {
return
}
- remove(state.notifications.data, ({action: {id}}) => id === status.id)
+ removeStatusFromGlobalStorage(state, status)
- remove(allStatuses, { uri })
if (timeline) {
remove(timelineObject.statuses, { uri })
remove(timelineObject.visibleStatuses, { uri })
@@ -268,12 +303,12 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
}
-const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes }) => {
- const allStatuses = state.allStatuses
- const allStatusesObject = state.allStatusesObject
+const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
each(notifications, (notification) => {
- notification.action = mergeOrAdd(allStatuses, allStatusesObject, notification.action).item
- notification.status = notification.status && mergeOrAdd(allStatuses, allStatusesObject, notification.status).item
+ if (notification.type !== 'follow') {
+ notification.action = addStatusToGlobalStorage(state, notification.action).item
+ notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
+ }
// Only add a new notification if we don't have one for the same action
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
@@ -289,15 +324,32 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
if ('Notification' in window && window.Notification.permission === 'granted') {
const notifObj = {}
- const action = notification.action
- const title = action.user.name
- notifObj.icon = action.user.profile_image_url
- notifObj.body = action.text // there's a problem that it doesn't put a space before links tho
+ const status = notification.status
+ const title = notification.from_profile.name
+ notifObj.icon = notification.from_profile.profile_image_url
+ let i18nString
+ switch (notification.type) {
+ case 'like':
+ i18nString = 'favorited_you'
+ break
+ case 'repeat':
+ i18nString = 'repeated_you'
+ break
+ case 'follow':
+ i18nString = 'followed_you'
+ break
+ }
+
+ if (i18nString) {
+ notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
+ } else {
+ notifObj.body = notification.status.text
+ }
// Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
- if (action.attachments && action.attachments.length > 0 && !action.nsfw &&
- action.attachments[0].mimetype.startsWith('image/')) {
- notifObj.image = action.attachments[0].url
+ if (status && status.attachments && status.attachments.length > 0 && !status.nsfw &&
+ status.attachments[0].mimetype.startsWith('image/')) {
+ notifObj.image = status.attachments[0].url
}
if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
@@ -337,35 +389,86 @@ export const mutations = {
oldTimeline.visibleStatusesObject = {}
each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status })
},
- setNotificationFetcher (state, { fetcherId }) {
- state.notifications.fetcherId = fetcherId
- },
resetStatuses (state) {
const emptyState = defaultState()
Object.entries(emptyState).forEach(([key, value]) => {
state[key] = value
})
},
- clearTimeline (state, { timeline }) {
- state.timelines[timeline] = emptyTl(state.timelines[timeline].userId)
+ clearTimeline (state, { timeline, excludeUserId = false }) {
+ const userId = excludeUserId ? state.timelines[timeline].userId : undefined
+ state.timelines[timeline] = emptyTl(userId)
+ },
+ clearNotifications (state) {
+ state.notifications = emptyNotifications()
},
setFavorited (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
+
+ if (newStatus.favorited !== value) {
+ if (value) {
+ newStatus.fave_num++
+ } else {
+ newStatus.fave_num--
+ }
+ }
+
newStatus.favorited = value
},
- setFavoritedConfirm (state, { status }) {
+ setFavoritedConfirm (state, { status, user }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.favorited = status.favorited
newStatus.fave_num = status.fave_num
+ const index = findIndex(newStatus.favoritedBy, { id: user.id })
+ if (index !== -1 && !newStatus.favorited) {
+ newStatus.favoritedBy.splice(index, 1)
+ } else if (index === -1 && newStatus.favorited) {
+ newStatus.favoritedBy.push(user)
+ }
+ },
+ 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 })
+ }
},
setRetweeted (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
+
+ if (newStatus.repeated !== value) {
+ if (value) {
+ newStatus.repeat_num++
+ } else {
+ newStatus.repeat_num--
+ }
+ }
+
newStatus.repeated = value
},
+ setRetweetedConfirm (state, { status, user }) {
+ const newStatus = state.allStatusesObject[status.id]
+ newStatus.repeated = status.repeated
+ newStatus.repeat_num = status.repeat_num
+ const index = findIndex(newStatus.rebloggedBy, { id: user.id })
+ if (index !== -1 && !newStatus.repeated) {
+ newStatus.rebloggedBy.splice(index, 1)
+ } else if (index === -1 && newStatus.repeated) {
+ newStatus.rebloggedBy.push(user)
+ }
+ },
setDeleted (state, { status }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.deleted = true
},
+ setManyDeleted (state, condition) {
+ Object.values(state.allStatusesObject).forEach(status => {
+ if (condition(status)) {
+ status.deleted = true
+ }
+ })
+ },
setLoading (state, { timeline, value }) {
state.timelines[timeline].loading = value
},
@@ -392,6 +495,24 @@ export const mutations = {
},
queueFlush (state, { timeline, id }) {
state.timelines[timeline].flushMarker = id
+ },
+ addRepeats (state, { id, rebloggedByUsers, currentUser }) {
+ const newStatus = state.allStatusesObject[id]
+ 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)
+ },
+ addFavs (state, { id, favoritedByUsers, currentUser }) {
+ const newStatus = state.allStatusesObject[id]
+ 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)
+ },
+ updateStatusWithPoll (state, { id, poll }) {
+ const status = state.allStatusesObject[id]
+ status.poll = poll
}
}
@@ -401,8 +522,8 @@ const statuses = {
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId }) {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId })
},
- addNewNotifications ({ rootState, commit, dispatch }, { notifications, older }) {
- commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older })
+ addNewNotifications ({ rootState, commit, dispatch, rootGetters }, { notifications, older }) {
+ commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older, rootGetters })
},
setError ({ rootState, commit }, { value }) {
commit('setError', { value })
@@ -416,54 +537,56 @@ const statuses = {
setNotificationsSilence ({ rootState, commit }, { value }) {
commit('setNotificationsSilence', { value })
},
- stopFetchingNotifications ({ rootState, commit }) {
- if (rootState.statuses.notifications.fetcherId) {
- window.clearInterval(rootState.statuses.notifications.fetcherId)
- }
- commit('setNotificationFetcher', { fetcherId: null })
- },
deleteStatus ({ rootState, commit }, status) {
commit('setDeleted', { status })
apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials })
},
+ markStatusesAsDeleted ({ commit }, condition) {
+ commit('setManyDeleted', condition)
+ },
favorite ({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: true })
- apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
- .then(response => {
- if (response.ok) {
- return response.json()
- } else {
- return {}
- }
- })
- .then(status => {
- commit('setFavoritedConfirm', { status })
- })
+ rootState.api.backendInteractor.favorite(status.id)
+ .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
},
unfavorite ({ rootState, commit }, status) {
- // Optimistic favoriting...
+ // Optimistic unfavoriting...
commit('setFavorited', { status, value: false })
- apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
- .then(response => {
- if (response.ok) {
- return response.json()
- } else {
- return {}
- }
- })
- .then(status => {
- commit('setFavoritedConfirm', { status })
- })
+ rootState.api.backendInteractor.unfavorite(status.id)
+ .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
+ },
+ fetchPinnedStatuses ({ rootState, dispatch }, userId) {
+ rootState.api.backendInteractor.fetchPinnedStatuses(userId)
+ .then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
+ },
+ pinStatus ({ rootState, dispatch }, statusId) {
+ return rootState.api.backendInteractor.pinOwnStatus(statusId)
+ .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
+ },
+ unpinStatus ({ rootState, dispatch }, statusId) {
+ rootState.api.backendInteractor.unpinOwnStatus(statusId)
+ .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
+ },
+ muteConversation ({ rootState, commit }, statusId) {
+ return rootState.api.backendInteractor.muteConversation(statusId)
+ .then((status) => commit('setMutedStatus', status))
+ },
+ unmuteConversation ({ rootState, commit }, statusId) {
+ return rootState.api.backendInteractor.unmuteConversation(statusId)
+ .then((status) => commit('setMutedStatus', status))
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...
commit('setRetweeted', { status, value: true })
- apiService.retweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
+ rootState.api.backendInteractor.retweet(status.id)
+ .then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
},
unretweet ({ rootState, commit }, status) {
+ // Optimistic unretweeting...
commit('setRetweeted', { status, value: false })
- apiService.unretweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
+ rootState.api.backendInteractor.unretweet(status.id)
+ .then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser }))
},
queueFlush ({ rootState, commit }, { timeline, id }) {
commit('queueFlush', { timeline, id })
@@ -474,6 +597,31 @@ const statuses = {
id: rootState.statuses.notifications.maxId,
credentials: rootState.users.currentUser.credentials
})
+ },
+ fetchFavsAndRepeats ({ rootState, commit }, id) {
+ Promise.all([
+ rootState.api.backendInteractor.fetchFavoritedByUsers(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 })
+ })
+ },
+ 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 }))
+ },
+ search (store, { q, resolve, limit, offset, following }) {
+ return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following })
+ .then((data) => {
+ store.commit('addNewUsers', data.accounts)
+ store.commit('addNewStatuses', { statuses: data.statuses })
+ return data
+ })
}
},
mutations
diff --git a/src/modules/users.js b/src/modules/users.js
index 26884750..4d02f8d7 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,9 +1,8 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
-import { compact, map, each, merge, find } from 'lodash'
+import oauthApi from '../services/new_api/oauth.js'
+import { compact, map, each, merge, last, concat, uniq } from 'lodash'
import { set } from 'vue'
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
-import oauthApi from '../services/new_api/oauth'
-import { humanizeErrors } from './errors'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@@ -16,9 +15,9 @@ export const mergeOrAdd = (arr, obj, item) => {
} else {
// This is a new item, prepare it
arr.push(item)
- obj[item.id] = item
+ set(obj, item.id, item)
if (item.screen_name && !item.screen_name.includes('@')) {
- obj[item.screen_name] = item
+ set(obj, item.screen_name.toLowerCase(), item)
}
return { item, new: true }
}
@@ -32,11 +31,62 @@ const getNotificationPermission = () => {
return Promise.resolve(Notification.permission)
}
+const blockUser = (store, id) => {
+ return store.rootState.api.backendInteractor.blockUser(id)
+ .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 })
+ })
+}
+
+const unblockUser = (store, id) => {
+ return store.rootState.api.backendInteractor.unblockUser(id)
+ .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+}
+
+const muteUser = (store, id) => {
+ return store.rootState.api.backendInteractor.muteUser(id)
+ .then((relationship) => {
+ store.commit('updateUserRelationship', [relationship])
+ store.commit('addMuteId', id)
+ })
+}
+
+const unmuteUser = (store, id) => {
+ return store.rootState.api.backendInteractor.unmuteUser(id)
+ .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+}
+
export const mutations = {
setMuted (state, { user: { id }, muted }) {
const user = state.usersObject[id]
set(user, 'muted', muted)
},
+ tagUser (state, { user: { id }, tag }) {
+ const user = state.usersObject[id]
+ const tags = user.tags || []
+ const newTags = tags.concat([tag])
+ set(user, 'tags', newTags)
+ },
+ untagUser (state, { user: { id }, tag }) {
+ const user = state.usersObject[id]
+ const tags = user.tags || []
+ const newTags = tags.filter(t => t !== tag)
+ set(user, 'tags', newTags)
+ },
+ updateRight (state, { user: { id }, right, value }) {
+ const user = state.usersObject[id]
+ let newRights = user.rights
+ newRights[right] = value
+ set(user, 'rights', newRights)
+ },
+ updateActivationStatus (state, { user: { id }, status }) {
+ const user = state.usersObject[id]
+ set(user, 'deactivated', !status)
+ },
setCurrentUser (state, user) {
state.lastLoginName = user.screen_name
state.currentUser = merge(state.currentUser || {}, user)
@@ -51,58 +101,86 @@ export const mutations = {
endLogin (state) {
state.loggingIn = false
},
- // TODO Clean after ourselves?
- addFriends (state, { id, friends, page }) {
+ saveFriendIds (state, { id, friendIds }) {
const user = state.usersObject[id]
- each(friends, friend => {
- if (!find(user.friends, { id: friend.id })) {
- user.friends.push(friend)
- }
- })
- user.friendsPage = page + 1
+ user.friendIds = uniq(concat(user.friendIds, friendIds))
},
- addFollowers (state, { id, followers, page }) {
+ saveFollowerIds (state, { id, followerIds }) {
const user = state.usersObject[id]
- each(followers, follower => {
- if (!find(user.followers, { id: follower.id })) {
- user.followers.push(follower)
- }
- })
- user.followersPage = page + 1
+ 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) {
const user = state.usersObject[userId]
- if (!user) {
- return
+ if (user) {
+ set(user, 'friendIds', [])
}
- user.friends = []
- user.friendsPage = 0
},
clearFollowers (state, userId) {
const user = state.usersObject[userId]
- if (!user) {
- return
+ if (user) {
+ set(user, 'followerIds', [])
}
- user.followers = []
- user.followersPage = 0
},
addNewUsers (state, users) {
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
},
- saveBlocks (state, blockIds) {
+ updateUserRelationship (state, relationships) {
+ relationships.forEach((relationship) => {
+ const user = state.usersObject[relationship.id]
+ if (user) {
+ user.follows_you = relationship.followed_by
+ user.following = relationship.following
+ user.muted = relationship.muting
+ user.statusnet_blocking = relationship.blocking
+ user.subscribed = relationship.subscribing
+ }
+ })
+ },
+ updateBlocks (state, blockedUsers) {
+ // Reset statusnet_blocking of all fetched users
+ each(state.users, (user) => { user.statusnet_blocking = false })
+ each(blockedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
+ },
+ saveBlockIds (state, blockIds) {
state.currentUser.blockIds = blockIds
},
- saveMutes (state, muteIds) {
+ addBlockId (state, blockId) {
+ if (state.currentUser.blockIds.indexOf(blockId) === -1) {
+ state.currentUser.blockIds.push(blockId)
+ }
+ },
+ updateMutes (state, mutedUsers) {
+ // Reset muted of all fetched users
+ each(state.users, (user) => { user.muted = false })
+ each(mutedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
+ },
+ saveMuteIds (state, muteIds) {
state.currentUser.muteIds = muteIds
},
+ addMuteId (state, muteId) {
+ if (state.currentUser.muteIds.indexOf(muteId) === -1) {
+ state.currentUser.muteIds.push(muteId)
+ }
+ },
+ setPinnedToUser (state, status) {
+ const user = state.usersObject[status.user.id]
+ const index = user.pinnedStatusIds.indexOf(status.id)
+ if (status.pinned && index === -1) {
+ user.pinnedStatusIds.push(status.id)
+ } else if (!status.pinned && index !== -1) {
+ user.pinnedStatusIds.splice(index, 1)
+ }
+ },
setUserForStatus (state, status) {
status.user = state.usersObject[status.user.id]
},
setUserForNotification (state, notification) {
- notification.action.user = state.usersObject[notification.action.user.id]
- notification.from_profile = state.usersObject[notification.action.user.id]
+ 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 }) {
const user = state.usersObject[id]
@@ -122,12 +200,14 @@ export const mutations = {
}
export const getters = {
- userById: state => id =>
- state.users.find(user => user.id === id),
- userByName: state => name =>
- state.users.find(user => user.screen_name &&
- (user.screen_name.toLowerCase() === name.toLowerCase())
- )
+ findUser: state => query => {
+ const result = state.usersObject[query]
+ // In case it's a screen_name, we can try searching case-insensitive
+ if (!result && typeof query === 'string') {
+ return state.usersObject[query.toLowerCase()]
+ }
+ return result
+ }
}
export const defaultState = {
@@ -147,59 +227,74 @@ const users = {
actions: {
fetchUser (store, id) {
return store.rootState.api.backendInteractor.fetchUser({ id })
- .then((user) => store.commit('addNewUsers', [user]))
+ .then((user) => {
+ store.commit('addNewUsers', [user])
+ return user
+ })
+ },
+ fetchUserRelationship (store, id) {
+ if (store.state.currentUser) {
+ store.rootState.api.backendInteractor.fetchUserRelationship({ id })
+ .then((relationships) => store.commit('updateUserRelationship', relationships))
+ }
},
fetchBlocks (store) {
return store.rootState.api.backendInteractor.fetchBlocks()
.then((blocks) => {
- store.commit('saveBlocks', map(blocks, 'id'))
- store.commit('addNewUsers', blocks)
+ store.commit('saveBlockIds', map(blocks, 'id'))
+ store.commit('updateBlocks', blocks)
return blocks
})
},
blockUser (store, id) {
- return store.rootState.api.backendInteractor.blockUser(id)
- .then((user) => store.commit('addNewUsers', [user]))
+ return blockUser(store, id)
},
unblockUser (store, id) {
- return store.rootState.api.backendInteractor.unblockUser(id)
- .then((user) => store.commit('addNewUsers', [user]))
+ return unblockUser(store, id)
+ },
+ blockUsers (store, ids = []) {
+ return Promise.all(ids.map(id => blockUser(store, id)))
+ },
+ unblockUsers (store, ids = []) {
+ return Promise.all(ids.map(id => unblockUser(store, id)))
},
fetchMutes (store) {
return store.rootState.api.backendInteractor.fetchMutes()
- .then((mutedUsers) => {
- each(mutedUsers, (user) => { user.muted = true })
- store.commit('addNewUsers', mutedUsers)
- store.commit('saveMutes', map(mutedUsers, 'id'))
+ .then((mutes) => {
+ store.commit('updateMutes', mutes)
+ store.commit('saveMuteIds', map(mutes, 'id'))
+ return mutes
})
},
muteUser (store, id) {
- return store.state.api.backendInteractor.setUserMute({ id, muted: true })
- .then((user) => store.commit('addNewUsers', [user]))
+ return muteUser(store, id)
},
unmuteUser (store, id) {
- return store.state.api.backendInteractor.setUserMute({ id, muted: false })
- .then((user) => store.commit('addNewUsers', [user]))
+ return unmuteUser(store, id)
},
- addFriends ({ rootState, commit }, fetchBy) {
- return new Promise((resolve, reject) => {
- const user = rootState.users.usersObject[fetchBy]
- const page = user.friendsPage || 1
- rootState.api.backendInteractor.fetchFriends({ id: user.id, page })
- .then((friends) => {
- commit('addFriends', { id: user.id, friends, page })
- resolve(friends)
- }).catch(() => {
- reject()
- })
- })
+ muteUsers (store, ids = []) {
+ return Promise.all(ids.map(id => muteUser(store, id)))
},
- addFollowers ({ rootState, commit }, fetchBy) {
- const user = rootState.users.usersObject[fetchBy]
- const page = user.followersPage || 1
- return rootState.api.backendInteractor.fetchFollowers({ id: user.id, page })
+ unmuteUsers (store, ids = []) {
+ return Promise.all(ids.map(id => unmuteUser(store, id)))
+ },
+ fetchFriends ({ rootState, commit }, id) {
+ const user = rootState.users.usersObject[id]
+ const maxId = last(user.friendIds)
+ 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) {
+ const user = rootState.users.usersObject[id]
+ const maxId = last(user.followerIds)
+ return rootState.api.backendInteractor.fetchFollowers({ id, maxId })
.then((followers) => {
- commit('addFollowers', { id: user.id, followers, page })
+ commit('addNewUsers', followers)
+ commit('saveFollowerIds', { id, followerIds: map(followers, 'id') })
return followers
})
},
@@ -209,6 +304,14 @@ const users = {
clearFollowers ({ commit }, userId) {
commit('clearFollowers', userId)
},
+ subscribeUser ({ rootState, commit }, id) {
+ return rootState.api.backendInteractor.subscribeUser(id)
+ .then((relationship) => commit('updateUserRelationship', [relationship]))
+ },
+ unsubscribeUser ({ rootState, commit }, id) {
+ return rootState.api.backendInteractor.unsubscribeUser(id)
+ .then((relationship) => commit('updateUserRelationship', [relationship]))
+ },
registerPushNotifications (store) {
const token = store.state.currentUser.credentials
const vapidPublicKey = store.rootState.instance.vapidPublicKey
@@ -222,19 +325,26 @@ const users = {
unregisterPushNotifications(token)
},
+ addNewUsers ({ commit }, users) {
+ commit('addNewUsers', users)
+ },
addNewStatuses (store, { statuses }) {
const users = map(statuses, 'user')
const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))
store.commit('addNewUsers', users)
store.commit('addNewUsers', retweetedUsers)
- // Reconnect users to statuses
each(statuses, (status) => {
+ // Reconnect users to statuses
store.commit('setUserForStatus', status)
+ // Set pinned statuses to user
+ store.commit('setPinnedToUser', status)
})
- // Reconnect users to retweets
each(compact(map(statuses, 'retweeted_status')), (status) => {
+ // Reconnect users to retweets
store.commit('setUserForStatus', status)
+ // Set pinned retweets to user
+ store.commit('setPinnedToUser', status)
})
},
addNewNotifications (store, { notifications }) {
@@ -244,60 +354,70 @@ const users = {
const notificationsObject = store.rootState.statuses.notifications.idStore
const relevantNotifications = Object.entries(notificationsObject)
- .filter(([k, val]) => notificationIds.includes(k))
- .map(([k, val]) => val)
+ .filter(([k, val]) => notificationIds.includes(k))
+ .map(([k, val]) => val)
// Reconnect users to notifications
each(relevantNotifications, (notification) => {
store.commit('setUserForNotification', notification)
})
},
+ searchUsers (store, query) {
+ return store.rootState.api.backendInteractor.searchUsers(query)
+ .then((users) => {
+ store.commit('addNewUsers', users)
+ return users
+ })
+ },
async signUp (store, userInfo) {
store.commit('signUpPending')
let rootState = store.rootState
- let response = await rootState.api.backendInteractor.register(userInfo)
- if (response.ok) {
- const data = {
- oauth: rootState.oauth,
- instance: rootState.instance.server
- }
- let app = await oauthApi.getOrCreateApp(data)
- let result = await oauthApi.getTokenWithCredentials({
- app,
- instance: data.instance,
- username: userInfo.username,
- password: userInfo.password
- })
+ try {
+ let data = await rootState.api.backendInteractor.register(userInfo)
store.commit('signUpSuccess')
- store.commit('setToken', result.access_token)
- store.dispatch('loginUser', result.access_token)
- } else {
- const data = await response.json()
- let errors = JSON.parse(data.error)
- // replace ap_id with username
- if (errors.ap_id) {
- errors.username = errors.ap_id
- delete errors.ap_id
- }
- errors = humanizeErrors(errors)
+ store.commit('setToken', data.access_token)
+ store.dispatch('loginUser', data.access_token)
+ } catch (e) {
+ let errors = e.message
store.commit('signUpFailure', errors)
- throw Error(errors)
+ throw e
}
},
async getCaptcha (store) {
- return await store.rootState.api.backendInteractor.getCaptcha()
+ return store.rootState.api.backendInteractor.getCaptcha()
},
logout (store) {
- store.commit('clearCurrentUser')
- store.dispatch('disconnectFromChat')
- store.commit('setToken', false)
- store.dispatch('stopFetching', 'friends')
- store.commit('setBackendInteractor', backendInteractorService())
- store.dispatch('stopFetchingNotifications')
- store.commit('resetStatuses')
+ const { oauth, instance } = store.rootState
+
+ const data = {
+ ...oauth,
+ commit: store.commit,
+ instance: instance.server
+ }
+
+ return oauthApi.getOrCreateApp(data)
+ .then((app) => {
+ const params = {
+ app,
+ instance: data.instance,
+ token: oauth.userToken
+ }
+
+ return oauthApi.revokeToken(params)
+ })
+ .then(() => {
+ store.commit('clearCurrentUser')
+ store.dispatch('disconnectFromSocket')
+ store.commit('clearToken')
+ store.dispatch('stopFetching', 'friends')
+ store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
+ store.dispatch('stopFetching', 'notifications')
+ store.commit('clearNotifications')
+ store.commit('resetStatuses')
+ })
},
loginUser (store, accessToken) {
return new Promise((resolve, reject) => {
@@ -328,7 +448,10 @@ const users = {
}
// Start getting fresh posts.
- store.dispatch('startFetching', { timeline: 'friends' })
+ store.dispatch('startFetchingTimeline', { timeline: 'friends' })
+
+ // Start fetching notifications
+ store.dispatch('startFetchingNotifications')
// Get user mutes
store.dispatch('fetchMutes')
@@ -341,19 +464,19 @@ const users = {
// Authentication failed
commit('endLogin')
if (response.status === 401) {
- reject('Wrong username or password')
+ reject(new Error('Wrong username or password'))
} else {
- reject('An error occurred, please try again')
+ reject(new Error('An error occurred, please try again'))
}
}
commit('endLogin')
resolve()
})
- .catch((error) => {
- console.log(error)
- commit('endLogin')
- reject('Failed to connect to server, try again')
- })
+ .catch((error) => {
+ console.log(error)
+ commit('endLogin')
+ reject(new Error('Failed to connect to server, try again'))
+ })
})
}
}