aboutsummaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/adminSettings.js2
-rw-r--r--src/modules/api.js5
-rw-r--r--src/modules/config.js133
-rw-r--r--src/modules/instance.js43
-rw-r--r--src/modules/interface.js235
-rw-r--r--src/modules/notifications.js169
-rw-r--r--src/modules/serverSideStorage.js1
-rw-r--r--src/modules/statuses.js178
-rw-r--r--src/modules/users.js27
9 files changed, 579 insertions, 214 deletions
diff --git a/src/modules/adminSettings.js b/src/modules/adminSettings.js
index 3df3647f..ef28f6e0 100644
--- a/src/modules/adminSettings.js
+++ b/src/modules/adminSettings.js
@@ -105,7 +105,6 @@ const adminSettingsStorage = {
}
set(config, path, convert(c.value))
})
- console.log(config[':pleroma'])
commit('updateAdminSettings', { config, modifiedPaths })
commit('resetAdminDraft')
},
@@ -123,7 +122,6 @@ const adminSettingsStorage = {
const descriptions = {}
backendDescriptions.forEach(d => convert(d, '', descriptions))
- console.log(descriptions[':pleroma']['Pleroma.Captcha'])
commit('updateAdminDescriptions', { descriptions })
},
diff --git a/src/modules/api.js b/src/modules/api.js
index fee584e8..3dbead5b 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -202,12 +202,13 @@ const api = {
timeline = 'friends',
tag = false,
userId = false,
- listId = false
+ listId = false,
+ statusId = false
}) {
if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({
- timeline, store, userId, listId, tag
+ timeline, store, userId, listId, statusId, tag
})
store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
diff --git a/src/modules/config.js b/src/modules/config.js
index 56f8cba5..cf84234a 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -1,10 +1,21 @@
import Cookies from 'js-cookie'
-import { setPreset, applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
+import { applyConfig } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages'
import { set } from 'lodash'
import localeService from '../services/locale/locale.service.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
+const APPEARANCE_SETTINGS_KEYS = new Set([
+ 'sidebarColumnWidth',
+ 'contentColumnWidth',
+ 'notifsColumnWidth',
+ 'textSize',
+ 'navbarSize',
+ 'panelHeaderSize',
+ 'forcedRoundness',
+ 'emojiSize',
+ 'emojiReactionsScale'
+])
const browserLocale = (window.navigator.language || 'en').split('-')[0]
@@ -24,10 +35,30 @@ export const multiChoiceProperties = [
export const defaultState = {
expertLevel: 0, // used to track which settings to show and hide
- colors: {},
- theme: undefined,
- customTheme: undefined,
- customThemeSource: undefined,
+
+ // Theme stuff
+ theme: undefined, // Very old theme store, stores preset name, still in use
+
+ // V1
+ colors: {}, // VERY old theme store, just colors of V1, probably not even used anymore
+
+ // V2
+ customTheme: undefined, // "snapshot", previously was used as actual theme store for V2 so it's still used in case of PleromaFE downgrade event.
+ customThemeSource: undefined, // "source", stores original theme data
+
+ // V3
+ themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions
+ forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists
+ theme3hacks: { // Hacks, user overrides that are independent of theme used
+ underlay: 'none',
+ fonts: {
+ interface: undefined,
+ input: undefined,
+ post: undefined,
+ monospace: undefined
+ }
+ },
+
hideISP: false,
hideInstanceWallpaper: false,
hideShoutbox: false,
@@ -36,10 +67,13 @@ export const defaultState = {
hideMutedThreads: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default
+ muteSensitiveStatuses: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
padEmoji: true,
hideAttachments: false,
hideAttachmentsInConv: false,
+ hideScrobbles: false,
+ hideScrobblesAfter: '2d',
maxThumbnails: 16,
hideNsfw: true,
preloadImage: true,
@@ -56,6 +90,7 @@ export const defaultState = {
notificationVisibility: {
follows: true,
mentions: true,
+ statuses: true,
likes: true,
repeats: true,
moves: true,
@@ -65,7 +100,21 @@ export const defaultState = {
chatMention: true,
polls: true
},
+ notificationNative: {
+ follows: true,
+ mentions: true,
+ statuses: true,
+ likes: false,
+ repeats: false,
+ moves: false,
+ emojiReactions: false,
+ followRequest: true,
+ reports: true,
+ chatMention: true,
+ polls: true
+ },
webPushNotifications: false,
+ webPushAlwaysShowNotifications: false,
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
@@ -98,7 +147,12 @@ export const defaultState = {
sidebarColumnWidth: '25rem',
contentColumnWidth: '45rem',
notifsColumnWidth: '25rem',
- emojiReactionsScale: 1.0,
+ emojiReactionsScale: undefined,
+ textSize: undefined, // instance default
+ emojiSize: undefined, // instance default
+ navbarSize: undefined, // instance default
+ panelHeaderSize: undefined, // instance default
+ forcedRoundness: undefined, // instance default
navbarColumnStretch: false,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
@@ -117,8 +171,16 @@ export const defaultState = {
conversationTreeAdvanced: undefined, // instance default
conversationOtherRepliesButton: undefined, // instance default
conversationTreeFadeAncestors: undefined, // instance default
+ showExtraNotifications: undefined, // instance default
+ showExtraNotificationsTip: undefined, // instance default
+ showChatsInExtraNotifications: undefined, // instance default
+ showAnnouncementsInExtraNotifications: undefined, // instance default
+ showFollowRequestsInExtraNotifications: undefined, // instance default
maxDepthInThread: undefined, // instance default
- autocompleteSelect: undefined // instance default
+ autocompleteSelect: undefined, // instance default
+ closingDrawerMarksAsSeen: undefined, // instance default
+ unseenAtTop: undefined, // instance default
+ ignoreInactionableSeen: undefined // instance default
}
// caching the instance default properties
@@ -148,6 +210,10 @@ const config = {
}
},
mutations: {
+ setOptionTemporarily (state, { name, value }) {
+ set(state, name, value)
+ applyConfig(state)
+ },
setOption (state, { name, value }) {
set(state, name, value)
},
@@ -178,6 +244,37 @@ const config = {
setHighlight ({ commit, dispatch }, { user, color, type }) {
commit('setHighlight', { user, color, type })
},
+ setOptionTemporarily ({ commit, dispatch, state, rootState }, { name, value }) {
+ if (rootState.interface.temporaryChangesTimeoutId !== null) {
+ console.warn('Can\'t track more than one temporary change')
+ return
+ }
+ const oldValue = state[name]
+
+ commit('setOptionTemporarily', { name, value })
+
+ const confirm = () => {
+ dispatch('setOption', { name, value })
+ commit('clearTemporaryChanges')
+ }
+
+ const revert = () => {
+ commit('setOptionTemporarily', { name, value: oldValue })
+ commit('clearTemporaryChanges')
+ }
+
+ commit('setTemporaryChanges', {
+ timeoutId: setTimeout(revert, 10000),
+ confirm,
+ revert
+ })
+ },
+ setThemeV2 ({ commit, dispatch }, { customTheme, customThemeSource }) {
+ commit('setOption', { name: 'theme', value: 'custom' })
+ commit('setOption', { name: 'customTheme', value: customTheme })
+ commit('setOption', { name: 'customThemeSource', value: customThemeSource })
+ dispatch('setTheme', { themeData: customThemeSource, recompile: true })
+ },
setOption ({ commit, dispatch, state }, { name, value }) {
const exceptions = new Set([
'useStreamingApi'
@@ -195,24 +292,26 @@ const config = {
dispatch('disableMastoSockets')
dispatch('setOption', { name: 'useStreamingApi', value: false })
})
+ break
}
}
} else {
commit('setOption', { name, value })
+ if (APPEARANCE_SETTINGS_KEYS.has(name)) {
+ applyConfig(state)
+ }
+ if (name.startsWith('theme3hacks')) {
+ dispatch('setTheme', { recompile: true })
+ }
switch (name) {
case 'theme':
- setPreset(value)
- break
- case 'sidebarColumnWidth':
- case 'contentColumnWidth':
- case 'notifsColumnWidth':
- case 'emojiReactionsScale':
- applyConfig(state)
+ if (value === 'custom') break
+ dispatch('setTheme', { themeName: value, recompile: true, saveData: true })
break
- case 'customTheme':
- case 'customThemeSource':
- applyTheme(value)
+ case 'themeDebug': {
+ dispatch('setTheme', { recompile: true })
break
+ }
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
dispatch('loadUnicodeEmojiData', value)
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 1ee64552..99b8b5d5 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -1,5 +1,3 @@
-import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
-import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import apiService from '../services/api/api.service.js'
import { instanceDefaultProperties } from './config.js'
import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
@@ -44,7 +42,7 @@ const defaultState = {
registrationOpen: true,
server: 'http://localhost:4040/',
textlimit: 5000,
- themeData: undefined,
+ themeData: undefined, // used for theme editor v2
vapidPublicKey: undefined,
// Stuff from static/config.json
@@ -71,6 +69,7 @@ const defaultState = {
hideSitename: false,
hideUserStats: false,
muteBotStatuses: false,
+ muteSensitiveStatuses: false,
modalOnRepeat: false,
modalOnUnfollow: false,
modalOnBlock: true,
@@ -97,14 +96,29 @@ const defaultState = {
sidebarRight: false,
subjectLineBehavior: 'email',
theme: 'pleroma-dark',
+ emojiReactionsScale: 0.5,
+ textSize: '14px',
+ emojiSize: '2.2rem',
+ navbarSize: '3.5rem',
+ panelHeaderSize: '3.2rem',
+ forcedRoundness: -1,
+ fontsOverride: {},
virtualScrolling: true,
sensitiveByDefault: false,
conversationDisplay: 'linear',
conversationTreeAdvanced: false,
conversationOtherRepliesButton: 'below',
conversationTreeFadeAncestors: false,
+ showExtraNotifications: true,
+ showExtraNotificationsTip: true,
+ showChatsInExtraNotifications: true,
+ showAnnouncementsInExtraNotifications: true,
+ showFollowRequestsInExtraNotifications: true,
maxDepthInThread: 6,
autocompleteSelect: false,
+ closingDrawerMarksAsSeen: true,
+ unseenAtTop: false,
+ ignoreInactionableSeen: false,
// Nasty stuff
customEmoji: [],
@@ -129,6 +143,7 @@ const defaultState = {
suggestionsEnabled: false,
suggestionsWeb: '',
quotingAvailable: false,
+ groupActorAvailable: false,
// Html stuff
instanceSpecificPanelContent: '',
@@ -269,9 +284,6 @@ const instance = {
dispatch('initializeSocket')
}
break
- case 'theme':
- dispatch('setTheme', value)
- break
}
},
async getStaticEmoji ({ commit }) {
@@ -360,25 +372,6 @@ const instance = {
console.warn(e)
}
},
-
- setTheme ({ commit, rootState }, themeName) {
- commit('setInstanceOption', { name: 'theme', value: themeName })
- getPreset(themeName)
- .then(themeData => {
- commit('setInstanceOption', { name: 'themeData', value: themeData })
- // No need to apply theme if there's user theme already
- const { customTheme } = rootState.config
- if (customTheme) return
-
- // New theme presets don't have 'theme' property, they use 'source'
- const themeSource = themeData.source
- if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) {
- applyTheme(themeSource)
- } else {
- applyTheme(themeData.theme)
- }
- })
- },
fetchEmoji ({ dispatch, state }) {
if (!state.customEmojiFetched) {
state.customEmojiFetched = true
diff --git a/src/modules/interface.js b/src/modules/interface.js
index f8d62d87..57bfe0c6 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -1,4 +1,13 @@
+import { getPreset, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
+import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
+import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
+
const defaultState = {
+ localFonts: null,
+ themeApplied: false,
+ temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout
+ temporaryChangesConfirm: () => {}, // used for applying temporary options
+ temporaryChangesRevert: () => {}, // used for reverting temporary options
settingsModalState: 'hidden',
settingsModalLoadedUser: false,
settingsModalLoadedAdmin: false,
@@ -13,7 +22,8 @@ const defaultState = {
cssFilter: window.CSS && window.CSS.supports && (
window.CSS.supports('filter', 'drop-shadow(0 0)') ||
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
- )
+ ),
+ localFonts: typeof window.queryLocalFonts === 'function'
},
layoutType: 'normal',
globalNotices: [],
@@ -35,6 +45,20 @@ const interfaceMod = {
state.settings.currentSaveStateNotice = { error: true, errorData: error }
}
},
+ setTemporaryChanges (state, { timeoutId, confirm, revert }) {
+ state.temporaryChangesTimeoutId = timeoutId
+ state.temporaryChangesConfirm = confirm
+ state.temporaryChangesRevert = revert
+ },
+ clearTemporaryChanges (state) {
+ clearTimeout(state.temporaryChangesTimeoutId)
+ state.temporaryChangesTimeoutId = null
+ state.temporaryChangesConfirm = () => {}
+ state.temporaryChangesRevert = () => {}
+ },
+ setThemeApplied (state) {
+ state.themeApplied = true
+ },
setNotificationPermission (state, permission) {
state.notificationPermission = permission
},
@@ -86,6 +110,10 @@ const interfaceMod = {
},
setLastTimeline (state, value) {
state.lastTimeline = value
+ },
+ setFontsList (state, value) {
+ // Set is used here so that we filter out duplicate fonts (possibly same font but with different weight)
+ state.localFonts = [...(new Set(value.map(font => font.family))).values()]
}
},
actions: {
@@ -160,10 +188,215 @@ const interfaceMod = {
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
}
},
+ queryLocalFonts ({ commit, dispatch, state }) {
+ if (state.localFonts !== null) return
+ commit('setFontsList', [])
+ if (!state.browserSupport.localFonts) {
+ return
+ }
+ window
+ .queryLocalFonts()
+ .then((fonts) => {
+ commit('setFontsList', fonts)
+ })
+ .catch((e) => {
+ dispatch('pushGlobalNotice', {
+ messageKey: 'settings.style.themes3.font.font_list_unavailable',
+ messageArgs: {
+ error: e
+ },
+ level: 'error'
+ })
+ })
+ },
setLastTimeline ({ commit }, value) {
commit('setLastTimeline', value)
+ },
+ setTheme ({ commit, rootState }, { themeName, themeData, recompile, saveData } = {}) {
+ const {
+ theme: instanceThemeName
+ } = rootState.instance
+
+ const {
+ theme: userThemeName,
+ customTheme: userThemeSnapshot,
+ customThemeSource: userThemeSource,
+ forceThemeRecompilation,
+ themeDebug,
+ theme3hacks
+ } = rootState.config
+
+ const actualThemeName = userThemeName || instanceThemeName
+
+ const forceRecompile = forceThemeRecompilation || recompile
+
+ let promise = null
+
+ if (themeData) {
+ promise = Promise.resolve(normalizeThemeData(themeData))
+ } else if (themeName) {
+ promise = getPreset(themeName).then(themeData => normalizeThemeData(themeData))
+ } else if (userThemeSource || userThemeSnapshot) {
+ promise = Promise.resolve(normalizeThemeData({
+ _pleroma_theme_version: 2,
+ theme: userThemeSnapshot,
+ source: userThemeSource
+ }))
+ } else if (actualThemeName && actualThemeName !== 'custom') {
+ promise = getPreset(actualThemeName).then(themeData => {
+ const realThemeData = normalizeThemeData(themeData)
+ if (actualThemeName === instanceThemeName) {
+ // This sole line is the reason why this whole block is above the recompilation check
+ commit('setInstanceOption', { name: 'themeData', value: { theme: realThemeData } })
+ }
+ return realThemeData
+ })
+ } else {
+ throw new Error('Cannot load any theme!')
+ }
+
+ // If we're not not forced to recompile try using
+ // cache (tryLoadCache return true if load successful)
+ if (!forceRecompile && !themeDebug && tryLoadCache()) {
+ commit('setThemeApplied')
+ return
+ }
+
+ promise
+ .then(realThemeData => {
+ const theme2ruleset = convertTheme2To3(realThemeData)
+
+ if (saveData) {
+ commit('setOption', { name: 'theme', value: themeName || actualThemeName })
+ commit('setOption', { name: 'customTheme', value: realThemeData })
+ commit('setOption', { name: 'customThemeSource', value: realThemeData })
+ }
+ const hacks = []
+
+ Object.entries(theme3hacks).forEach(([key, value]) => {
+ switch (key) {
+ case 'fonts': {
+ Object.entries(theme3hacks.fonts).forEach(([fontKey, font]) => {
+ if (!font?.family) return
+ switch (fontKey) {
+ case 'interface':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'input':
+ hacks.push({
+ component: 'Input',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'post':
+ hacks.push({
+ component: 'RichContent',
+ directives: {
+ '--font': 'generic | ' + font.family
+ }
+ })
+ break
+ case 'monospace':
+ hacks.push({
+ component: 'Root',
+ directives: {
+ '--monoFont': 'generic | ' + font.family
+ }
+ })
+ break
+ }
+ })
+ break
+ }
+ case 'underlay': {
+ if (value !== 'none') {
+ const newRule = {
+ component: 'Underlay',
+ directives: {}
+ }
+ if (value === 'opaque') {
+ newRule.directives.opacity = 1
+ newRule.directives.background = '--wallpaper'
+ }
+ if (value === 'transparent') {
+ newRule.directives.opacity = 0
+ }
+ hacks.push(newRule)
+ }
+ break
+ }
+ }
+ })
+
+ const ruleset = [
+ ...theme2ruleset,
+ ...hacks
+ ]
+
+ applyTheme(
+ ruleset,
+ () => commit('setThemeApplied'),
+ themeDebug
+ )
+ })
+
+ return promise
}
}
}
export default interfaceMod
+
+export const normalizeThemeData = (input) => {
+ if (Array.isArray(input)) {
+ const themeData = { colors: {} }
+ themeData.colors.bg = input[1]
+ themeData.colors.fg = input[2]
+ themeData.colors.text = input[3]
+ themeData.colors.link = input[4]
+ themeData.colors.cRed = input[5]
+ themeData.colors.cGreen = input[6]
+ themeData.colors.cBlue = input[7]
+ themeData.colors.cOrange = input[8]
+ return generatePreset(themeData).theme
+ }
+
+ let themeData, themeSource
+
+ if (input.themeFileVerison === 1) {
+ // this might not be even used at all, some leftover of unimplemented code in V2 editor
+ return generatePreset(input).theme
+ } else if (
+ Object.prototype.hasOwnProperty.call(input, '_pleroma_theme_version') ||
+ Object.prototype.hasOwnProperty.call(input, 'source') ||
+ Object.prototype.hasOwnProperty.call(input, 'theme')
+ ) {
+ // We got passed a full theme file
+ themeData = input.theme
+ themeSource = input.source
+ } else if (Object.prototype.hasOwnProperty.call(input, 'themeEngineVersion')) {
+ // We got passed a source/snapshot
+ themeData = input
+ themeSource = input
+ }
+ // New theme presets don't have 'theme' property, they use 'source'
+
+ let out // shout, shout let it all out
+ if (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION) {
+ // There are some themes in wild that have completely broken source
+ out = { ...(themeData || {}), ...themeSource }
+ } else {
+ out = themeData
+ }
+
+ // generatePreset here basically creates/updates "snapshot",
+ // while also fixing the 2.2 -> 2.3 colors/shadows/etc
+ return generatePreset(out).theme
+}
diff --git a/src/modules/notifications.js b/src/modules/notifications.js
new file mode 100644
index 00000000..c1f5a63e
--- /dev/null
+++ b/src/modules/notifications.js
@@ -0,0 +1,169 @@
+import apiService from '../services/api/api.service.js'
+
+import {
+ isStatusNotification,
+ isValidNotification,
+ maybeShowNotification
+} from '../services/notification_utils/notification_utils.js'
+
+import {
+ closeDesktopNotification,
+ closeAllDesktopNotifications
+} from '../services/desktop_notification_utils/desktop_notification_utils.js'
+
+const emptyNotifications = () => ({
+ desktopNotificationSilence: true,
+ maxId: 0,
+ minId: Number.POSITIVE_INFINITY,
+ data: [],
+ idStore: {},
+ loading: false
+})
+
+export const defaultState = () => ({
+ ...emptyNotifications()
+})
+
+export const notifications = {
+ state: defaultState(),
+ mutations: {
+ addNewNotifications (state, { notifications }) {
+ notifications.forEach(notification => {
+ state.data.push(notification)
+ state.idStore[notification.id] = notification
+ })
+ },
+ clearNotifications (state) {
+ state = emptyNotifications()
+ },
+ updateNotificationsMinMaxId (state, id) {
+ state.maxId = id > state.maxId ? id : state.maxId
+ state.minId = id < state.minId ? id : state.minId
+ },
+ setNotificationsLoading (state, { value }) {
+ state.loading = value
+ },
+ setNotificationsSilence (state, { value }) {
+ state.desktopNotificationSilence = value
+ },
+ markNotificationsAsSeen (state) {
+ state.data.forEach((notification) => {
+ notification.seen = true
+ })
+ },
+ 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)
+ delete state.idStore[id]
+ },
+ updateNotification (state, { id, updater }) {
+ const notification = state.idStore[id]
+ notification && updater(notification)
+ }
+ },
+ actions: {
+ addNewNotifications (store, { notifications, older }) {
+ const { commit, dispatch, state, rootState } = store
+ const validNotifications = notifications.filter((notification) => {
+ // If invalid notification, update ids but don't add it to store
+ if (!isValidNotification(notification)) {
+ console.error('Invalid notification:', notification)
+ commit('updateNotificationsMinMaxId', notification.id)
+ return false
+ }
+ return true
+ })
+
+ 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) })
+
+ // Update references to statuses in notifications to ones in the store
+ statusNotifications.forEach(notification => {
+ const id = notification.status.id
+ const referenceStatus = rootState.statuses.allStatusesObject[id]
+
+ if (referenceStatus) {
+ notification.status = referenceStatus
+ }
+ })
+
+ validNotifications.forEach(notification => {
+ if (notification.type === 'pleroma:report') {
+ dispatch('addReport', notification.report)
+ }
+
+ if (notification.type === 'pleroma:emoji_reaction') {
+ dispatch('fetchEmojiReactionsBy', notification.status.id)
+ }
+
+ // Only add a new notification if we don't have one for the same action
+ // eslint-disable-next-line no-prototype-builtins
+ if (!state.idStore.hasOwnProperty(notification.id)) {
+ commit('updateNotificationsMinMaxId', notification.id)
+ commit('addNewNotifications', { notifications: [notification] })
+
+ maybeShowNotification(store, notification)
+ } else if (notification.seen) {
+ state.idStore[notification.id].seen = true
+ }
+ })
+ },
+ notificationClicked ({ state, dispatch }, { id }) {
+ const notification = state.idStore[id]
+ const { type, seen } = notification
+
+ if (!seen) {
+ switch (type) {
+ case 'mention':
+ case 'pleroma:report':
+ case 'follow_request':
+ break
+ default:
+ dispatch('markSingleNotificationAsSeen', { id })
+ }
+ }
+ },
+ setNotificationsLoading ({ rootState, commit }, { value }) {
+ commit('setNotificationsLoading', { value })
+ },
+ setNotificationsSilence ({ rootState, commit }, { value }) {
+ commit('setNotificationsSilence', { value })
+ },
+ markNotificationsAsSeen ({ rootState, state, commit }) {
+ commit('markNotificationsAsSeen')
+ apiService.markNotificationsAsSeen({
+ id: state.maxId,
+ credentials: rootState.users.currentUser.credentials
+ }).then(() => {
+ closeAllDesktopNotifications(rootState)
+ })
+ },
+ markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
+ commit('markSingleNotificationAsSeen', { id })
+ apiService.markNotificationsAsSeen({
+ single: true,
+ id,
+ credentials: rootState.users.currentUser.credentials
+ }).then(() => {
+ closeDesktopNotification(rootState, { id })
+ })
+ },
+ dismissNotificationLocal ({ rootState, commit }, { id }) {
+ commit('dismissNotification', { id })
+ },
+ dismissNotification ({ rootState, commit }, { id }) {
+ commit('dismissNotification', { id })
+ rootState.api.backendInteractor.dismissNotification({ id })
+ },
+ updateNotification ({ rootState, commit }, { id, updater }) {
+ commit('updateNotification', { id, updater })
+ }
+ }
+}
+
+export default notifications
diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js
index c933ce8d..c55f54fd 100644
--- a/src/modules/serverSideStorage.js
+++ b/src/modules/serverSideStorage.js
@@ -419,7 +419,6 @@ const serverSideStorage = {
actions: {
pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) {
const needPush = state.dirty || force
- console.log(needPush)
if (!needPush) return
commit('updateCache', { username: rootState.users.currentUser.fqn })
const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } }
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 186bba3c..462def22 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -12,11 +12,6 @@ import {
isArray,
omitBy
} from 'lodash'
-import {
- isStatusNotification,
- isValidNotification,
- maybeShowNotification
-} from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js'
const emptyTl = (userId = 0) => ({
@@ -36,21 +31,12 @@ const emptyTl = (userId = 0) => ({
flushMarker: 0
})
-const emptyNotifications = () => ({
- desktopNotificationSilence: true,
- maxId: 0,
- minId: Number.POSITIVE_INFINITY,
- data: [],
- idStore: {},
- loading: false
-})
-
export const defaultState = () => ({
allStatuses: [],
+ scrobblesNextFetch: {},
allStatusesObject: {},
conversationsObject: {},
maxId: 0,
- notifications: emptyNotifications(),
favorites: new Set(),
timelines: {
mentions: emptyTl(),
@@ -120,8 +106,24 @@ const sortTimeline = (timeline) => {
return timeline
}
+const getLatestScrobble = (state, user) => {
+ if (state.scrobblesNextFetch[user.id] && state.scrobblesNextFetch[user.id] > Date.now()) {
+ return
+ }
+
+ state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
+ apiService.fetchScrobbles({ accountId: user.id }).then((scrobbles) => {
+ if (scrobbles.length > 0) {
+ user.latestScrobble = scrobbles[0]
+
+ state.scrobblesNextFetch[user.id] = Date.now() + 60 * 1000
+ }
+ })
+}
+
// Add status to the global storages (arrays and objects maintaining statuses) except timelines
const addStatusToGlobalStorage = (state, data) => {
+ getLatestScrobble(state, data.user)
const result = mergeOrAdd(state.allStatuses, state.allStatusesObject, data)
if (result.new) {
// Add to conversation
@@ -137,22 +139,6 @@ const addStatusToGlobalStorage = (state, data) => {
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, pagination = {} }) => {
// Sanity check
if (!isArray(statuses)) {
@@ -286,20 +272,6 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
favoriteStatus(favorite)
}
},
- deletion: (deletion) => {
- const uri = deletion.uri
- const status = find(allStatuses, { uri })
- if (!status) {
- return
- }
-
- removeStatusFromGlobalStorage(state, status)
-
- if (timeline) {
- remove(timelineObject.statuses, { uri })
- remove(timelineObject.visibleStatuses, { uri })
- }
- },
follow: (follow) => {
// NOOP, it is known status but we don't do anything about it for now
},
@@ -321,52 +293,6 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
}
-const updateNotificationsMinMaxId = (state, notification) => {
- state.notifications.maxId = notification.id > state.notifications.maxId
- ? notification.id
- : state.notifications.maxId
- state.notifications.minId = notification.id < state.notifications.minId
- ? notification.id
- : state.notifications.minId
-}
-
-const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => {
- each(notifications, (notification) => {
- // If invalid notification, update ids but don't add it to store
- if (!isValidNotification(notification)) {
- console.error('Invalid notification:', notification)
- updateNotificationsMinMaxId(state, notification)
- return
- }
-
- if (isStatusNotification(notification.type)) {
- notification.action = addStatusToGlobalStorage(state, notification.action).item
- notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
- }
-
- if (notification.type === 'pleroma:report') {
- dispatch('addReport', notification.report)
- }
-
- if (notification.type === 'pleroma:emoji_reaction') {
- dispatch('fetchEmojiReactionsBy', notification.status.id)
- }
-
- // Only add a new notification if we don't have one for the same action
- // eslint-disable-next-line no-prototype-builtins
- if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
- updateNotificationsMinMaxId(state, notification)
-
- state.notifications.data.push(notification)
- state.notifications.idStore[notification.id] = notification
-
- newNotificationSideEffects(notification)
- } else if (notification.seen) {
- state.notifications.idStore[notification.id].seen = true
- }
- })
-}
-
const removeStatus = (state, { timeline, userId }) => {
const timelineObject = state.timelines[timeline]
if (userId) {
@@ -379,7 +305,6 @@ const removeStatus = (state, { timeline, userId }) => {
export const mutations = {
addNewStatuses,
- addNewNotifications,
removeStatus,
showNewStatuses (state, { timeline }) {
const oldTimeline = (state.timelines[timeline])
@@ -401,9 +326,6 @@ export const mutations = {
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]
@@ -486,31 +408,6 @@ export const mutations = {
const newStatus = state.allStatusesObject[id]
newStatus.nsfw = nsfw
},
- setNotificationsLoading (state, { value }) {
- state.notifications.loading = value
- },
- setNotificationsSilence (state, { value }) {
- state.notifications.desktopNotificationSilence = value
- },
- markNotificationsAsSeen (state) {
- each(state.notifications.data, (notification) => {
- notification.seen = true
- })
- },
- markSingleNotificationAsSeen (state, { id }) {
- const notification = find(state.notifications.data, n => n.id === id)
- if (notification) notification.seen = true
- },
- dismissNotification (state, { id }) {
- state.notifications.data = state.notifications.data.filter(n => n.id !== id)
- },
- dismissNotifications (state, { finder }) {
- state.notifications.data = state.notifications.data.filter(n => finder)
- },
- updateNotification (state, { id, updater }) {
- const notification = find(state.notifications.data, n => n.id === id)
- notification && updater(notification)
- },
queueFlush (state, { timeline, id }) {
state.timelines[timeline].flushMarker = id
},
@@ -592,23 +489,9 @@ export const mutations = {
const statuses = {
state: defaultState(),
actions: {
- addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) {
+ addNewStatuses ({ rootState, commit, dispatch, state }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId, pagination })
},
- addNewNotifications (store, { notifications, older }) {
- const { commit, dispatch, rootGetters } = store
-
- const newNotificationSideEffects = (notification) => {
- maybeShowNotification(store, notification)
- }
- commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects })
- },
- setNotificationsLoading ({ rootState, commit }, { value }) {
- commit('setNotificationsLoading', { value })
- },
- setNotificationsSilence ({ rootState, commit }, { value }) {
- commit('setNotificationsSilence', { value })
- },
fetchStatus ({ rootState, dispatch }, id) {
return rootState.api.backendInteractor.fetchStatus({ id })
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
@@ -704,31 +587,6 @@ const statuses = {
queueFlushAll ({ rootState, commit }) {
commit('queueFlushAll')
},
- markNotificationsAsSeen ({ rootState, commit }) {
- commit('markNotificationsAsSeen')
- apiService.markNotificationsAsSeen({
- id: rootState.statuses.notifications.maxId,
- credentials: rootState.users.currentUser.credentials
- })
- },
- markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
- commit('markSingleNotificationAsSeen', { id })
- apiService.markNotificationsAsSeen({
- single: true,
- id,
- credentials: rootState.users.currentUser.credentials
- })
- },
- dismissNotificationLocal ({ rootState, commit }, { id }) {
- commit('dismissNotification', { id })
- },
- dismissNotification ({ rootState, commit }, { id }) {
- commit('dismissNotification', { id })
- rootState.api.backendInteractor.dismissNotification({ id })
- },
- updateNotification ({ rootState, commit }, { id, updater }) {
- commit('updateNotification', { id, updater })
- },
fetchFavsAndRepeats ({ rootState, commit }, id) {
Promise.all([
rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
diff --git a/src/modules/users.js b/src/modules/users.js
index 50b4cb84..b8f49f15 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -2,7 +2,7 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
-import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
+import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@@ -250,6 +250,7 @@ export const mutations = {
signUpPending (state) {
state.signUpPending = true
state.signUpErrors = []
+ state.signUpNotice = {}
},
signUpSuccess (state) {
state.signUpPending = false
@@ -257,6 +258,12 @@ export const mutations = {
signUpFailure (state, errors) {
state.signUpPending = false
state.signUpErrors = errors
+ state.signUpNotice = {}
+ },
+ signUpNotice (state, notice) {
+ state.signUpPending = false
+ state.signUpErrors = []
+ state.signUpNotice = notice
}
}
@@ -287,6 +294,7 @@ export const defaultState = {
usersByNameObject: {},
signUpPending: false,
signUpErrors: [],
+ signUpNotice: {},
relationships: {}
}
@@ -498,7 +506,7 @@ const users = {
store.commit('addNewUsers', users)
store.commit('addNewUsers', targetUsers)
- const notificationsObject = store.rootState.statuses.notifications.idStore
+ const notificationsObject = store.rootState.notifications.idStore
const relevantNotifications = Object.entries(notificationsObject)
.filter(([k, val]) => notificationIds.includes(k))
.map(([k, val]) => val)
@@ -524,9 +532,16 @@ const users = {
const data = await rootState.api.backendInteractor.register(
{ params: { ...userInfo } }
)
- store.commit('signUpSuccess')
- store.commit('setToken', data.access_token)
- store.dispatch('loginUser', data.access_token)
+
+ if (data.access_token) {
+ store.commit('signUpSuccess')
+ store.commit('setToken', data.access_token)
+ store.dispatch('loginUser', data.access_token)
+ return 'ok'
+ } else { // Request succeeded, but user cannot login yet.
+ store.commit('signUpNotice', data)
+ return 'request_sent'
+ }
} catch (e) {
const errors = e.message
store.commit('signUpFailure', errors)
@@ -667,7 +682,7 @@ const users = {
resolve()
})
.catch((error) => {
- console.log(error)
+ console.error(error)
commit('endLogin')
reject(new Error('Failed to connect to server, try again'))
})