import generator , { Entity , FilterContext , NotificationType } from 'megalodon' ; import { Module , MutationTree , ActionTree , GetterTree } from 'vuex' ; import { RootState } from '@/store' ; import { LocalMarker } from '~/src/types/localMarker' ; import { MyWindow } from '~/src/types/global' ; import { LoadingCard } from '@/types/loading-card' ; const win = ( window as any ) as MyWindow ; export type NotificationsState = { lazyLoading : boolean ; heading : boolean ; notifications : Array < Entity . Notification | LoadingCard > ; scrolling : boolean ; } ; const state = ( ) : NotificationsState => ( { lazyLoading : false , heading : true , notifications : [ ] , scrolling : false } ) ; export const MUTATION_TYPES = { CHANGE_LAZY_LOADING : 'changeLazyLoading' , CHANGE_HEADING : 'changeHeading' , APPEND_NOTIFICATIONS : 'appendNotifications' , UPDATE_NOTIFICATIONS : 'updateNotifications' , INSERT_NOTIFICATIONS : 'insertNotifications' , UPDATE_TOOT : 'updateToot' , DELETE_TOOT : 'deleteToot' , CLEAR_NOTIFICATIONS : 'clearNotifications' , ARCHIVE_NOTIFICATIONS : 'archiveNotifications' , CHANGE_SCROLLING : 'changeScrolling' , APPEND_NOTIFICATIONS_AFTER_LOADING_CARD : 'appendNotificationsAfterLoadingCard' } ; const mutations = { [ MUTATION_TYPES . CHANGE_LAZY_LOADING ] : ( state , value ) => { state . lazyLoading = value ; } , [ MUTATION_TYPES . CHANGE_HEADING ] : ( state , value ) => { state . heading = value ; } , [ MUTATION_TYPES . APPEND_NOTIFICATIONS ] : ( state , notification ) => { if ( ! state . notifications . find ( item => item . id === notification . id ) ) { state . notifications = ( [ notification ] as Array < Entity . Notification | LoadingCard > ) . concat ( state . notifications ) ; } } , [ MUTATION_TYPES . UPDATE_NOTIFICATIONS ] : ( state , notifications ) => { state . notifications = notifications ; } , [ MUTATION_TYPES . INSERT_NOTIFICATIONS ] : ( state , notifications ) => { state . notifications = state . notifications . concat ( notifications ) ; } , [ MUTATION_TYPES . UPDATE_TOOT ] : ( state , message ) => { state . notifications = state . notifications . map ( notification => { if ( notification . type === 'mention' && notification . status && notification . status . id === message . id ) { const status = { status : message } ; return Object . assign ( notification , status ) ; } else { return notification ; } } ) ; } , [ MUTATION_TYPES . DELETE_TOOT ] : ( state , id ) => { state . notifications = state . notifications . filter ( notify => { if ( notify . id === 'loading-card' ) { return true ; } const notification = notify as Entity . Notification ; if ( notification . status ) { if ( notification . status . reblog && notification . status . reblog . id === id ) { return false ; } else { return notification . status . id !== id ; } } else { return true ; } } ) ; } , [ MUTATION_TYPES . CLEAR_NOTIFICATIONS ] : state => { state . notifications = [ ] ; } , [ MUTATION_TYPES . ARCHIVE_NOTIFICATIONS ] : state => { state . notifications = state . notifications . slice ( 0 , 30 ) ; } , [ MUTATION_TYPES . CHANGE_SCROLLING ] : ( state , value ) => { state . scrolling = value ; } , [ MUTATION_TYPES . APPEND_NOTIFICATIONS_AFTER_LOADING_CARD ] : ( state , notifications ) => { const n = state . notifications . flatMap ( notify => { if ( notify . id !== 'loading-card' ) { return notify ; } else { return notifications ; } } ) ; state . notifications = Array . from ( new Set ( n ) ) ; } } ; const actions = { fetchNotifications : async ( { dispatch , commit , rootState } ) : Promise < Array < Entity . Notification > > => { const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; const localMarker = await dispatch ( 'getMarker' ) . catch ( err => { console . error ( err ) ; } ) ; if ( rootState . TimelineSpace . timelineSetting . useMarker . notifications && localMarker !== null ) { const last = await client . getNotification ( localMarker . last_read_id ) ; const lastReadNotification = last . data ; let notifications = [ lastReadNotification ] ; const card = { type : 'middle-load' , since_id : lastReadNotification . id , max_id : null , id : 'loading-card' } ; const res = await client . getNotifications ( { limit : 30 , max_id : localMarker . last_read_id } ) ; const nextResponse = await client . getNotifications ( { limit : 1 , min_id : lastReadNotification . id } ) ; if ( nextResponse . data . length > 0 ) { notifications = ( [ card ] as Array < Entity . Notification | LoadingCard > ) . concat ( notifications ) . concat ( res . data ) ; } else { notifications = notifications . concat ( res . data ) ; } commit ( MUTATION_TYPES . UPDATE_NOTIFICATIONS , notifications ) ; return res . data ; } else { const res = await client . getNotifications ( { limit : 30 } ) ; commit ( MUTATION_TYPES . UPDATE_NOTIFICATIONS , res . data ) ; return res . data ; } } , lazyFetchNotifications : async ( { state , commit , rootState } , lastNotification ) : Promise < Array < Entity . Notification > | null > => { if ( state . lazyLoading ) { return Promise . resolve ( null ) ; } commit ( MUTATION_TYPES . CHANGE_LAZY_LOADING , true ) ; const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; return client . getNotifications ( { max_id : lastNotification . id , limit : 30 } ) . then ( res => { commit ( MUTATION_TYPES . INSERT_NOTIFICATIONS , res . data ) ; return res . data ; } ) . finally ( ( ) => { commit ( MUTATION_TYPES . CHANGE_LAZY_LOADING , false ) ; } ) ; } , fetchNotificationsSince : async ( { state , rootState , commit } , since_id ) : Promise < Array < Entity . Notification > | null > => { const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; const cardIndex = state . notifications . findIndex ( s => { if ( s . id === 'loading-card' ) { return true ; } return false ; } ) ; let maxID = null ; if ( cardIndex > 0 ) { maxID = state . notifications [ cardIndex - 1 ] . id ; } let params = { min_id : since_id , limit : 30 } ; if ( maxID !== null ) { params = Object . assign ( { } , params , { max_id : maxID } ) ; } const res = await client . getNotifications ( params ) ; if ( res . data . length >= 30 ) { const card = { type : 'middle-load' , since_id : res . data [ 0 ] . id , max_id : maxID , id : 'loading-card' } ; let notifications = [ card ] ; notifications = notifications . concat ( res . data ) ; commit ( MUTATION_TYPES . APPEND_NOTIFICATIONS_AFTER_LOADING_CARD , notifications ) ; } else { commit ( MUTATION_TYPES . APPEND_NOTIFICATIONS_AFTER_LOADING_CARD , res . data ) ; } return res . data ; } , resetBadge : ( ) => { win . ipcRenderer . send ( 'reset-badge' ) ; } , getMarker : async ( { rootState } ) : Promise < LocalMarker | null > => { if ( ! rootState . TimelineSpace . timelineSetting . useMarker . notifications ) { return null ; } const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; let serverMarker = { } ; try { const res = await client . getMarkers ( [ 'notifications' ] ) ; serverMarker = res . data ; } catch ( err ) { console . warn ( err ) ; } if ( ( serverMarker as Entity . Marker ) . notifications !== undefined ) { return { timeline : 'notifications' , last_read_id : ( serverMarker as Entity . Marker ) . notifications . last_read_id } as LocalMarker ; } const localMarker = await win . ipcRenderer . invoke ( 'get-notifications-marker' , rootState . TimelineSpace . account . _id ) ; return localMarker ; } , saveMarker : async ( { state , rootState } ) => { const notifications = state . notifications ; if ( notifications . length === 0 || notifications [ 0 ] . id === 'loading-card' ) { return ; } win . ipcRenderer . send ( 'save-marker' , { owner_id : rootState . TimelineSpace . account . _id , timeline : 'notifications' , last_read_id : notifications [ 0 ] . id } as LocalMarker ) ; if ( rootState . TimelineSpace . sns === 'misskey' ) { return ; } const client = generator ( rootState . TimelineSpace . sns , rootState . TimelineSpace . account . baseURL , rootState . TimelineSpace . account . accessToken , rootState . App . userAgent ) ; const res = await client . saveMarkers ( { notifications : { last_read_id : notifications [ 0 ] . id } } ) ; if ( rootState . TimelineSpace . sns === 'pleroma' ) { await client . readNotifications ( { max_id : notifications [ 0 ] . id } ) ; } return res . data ; } } ; const getters = { handledNotifications : state => { return state . notifications . filter ( n => { switch ( n . type ) { case 'middle-load' : case NotificationType . Follow : case NotificationType . Favourite : case NotificationType . Reblog : case NotificationType . Mention : case NotificationType . EmojiReaction : case NotificationType . FollowRequest : case NotificationType . Status : case NotificationType . PollVote : case NotificationType . PollExpired : return true ; default : return false ; } } ) ; } , filters : ( _state , _getters , rootState ) => { return rootState . TimelineSpace . filters . filter ( f => f . context . includes ( FilterContext . Notifications ) && ! f . irreversible ) ; } } ; const Notifications = { namespaced : true , state : state , mutations : mutations , actions : actions , getters : getters } ; export default Notifications ;