diff options
| -rw-r--r-- | src/components/status/status.vue | 24 | ||||
| -rw-r--r-- | src/components/user_card/user_card.vue | 1 | ||||
| -rw-r--r-- | src/modules/api.js | 2 | ||||
| -rw-r--r-- | src/modules/chats.js | 15 | ||||
| -rw-r--r-- | src/modules/statuses.js | 47 | ||||
| -rw-r--r-- | src/services/chat_utils/chat_utils.js | 19 | ||||
| -rw-r--r-- | src/services/desktop_notification_utils/desktop_notification_utils.js | 9 | ||||
| -rw-r--r-- | src/services/entity_normalizer/entity_normalizer.service.js | 1 | ||||
| -rw-r--r-- | src/services/notification_utils/notification_utils.js | 40 | ||||
| -rw-r--r-- | src/services/notifications_fetcher/notifications_fetcher.service.js | 2 | ||||
| -rw-r--r-- | src/services/timeline_fetcher/timeline_fetcher.service.js | 4 |
11 files changed, 111 insertions, 53 deletions
diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 4764f467..e1e56ec9 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -72,7 +72,10 @@ :user="statusoid.user" /> <div class="media-body faint"> - <span class="user-name"> + <span + class="user-name" + :title="retweeter" + > <router-link v-if="retweeterHtml" :to="retweeterProfileLink" @@ -129,20 +132,28 @@ <h4 v-if="status.user.name_html" class="user-name" + :title="status.user.name" v-html="status.user.name_html" /> <h4 v-else class="user-name" + :title="status.user.name" > {{ status.user.name }} </h4> <router-link class="account-name" + :title="status.user.screen_name" :to="userProfileLink" > {{ status.user.screen_name }} </router-link> + <img + class="status-favicon" + v-if="!!(status.user && status.user.favicon)" + :src="status.user.favicon" + > </div> <span class="heading-right"> @@ -222,7 +233,10 @@ > <span class="reply-to-text">{{ $t('status.reply_to') }}</span> </span> - <router-link :to="replyProfileLink"> + <router-link + :title="replyToName" + :to="replyProfileLink" + > {{ replyToName }} </router-link> <span @@ -434,6 +448,12 @@ $status-margin: 0.75em; } } + .status-favicon { + height: 18px; + width: 18px; + margin-right: 0.4em; + } + .media-heading { padding: 0; vertical-align: bottom; diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 9529d7f6..75db5db1 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -66,6 +66,7 @@ <div class="bottom-line"> <router-link class="user-screen-name" + :title="user.screen_name" :to="userProfileLink(user)" > @{{ user.screen_name }} diff --git a/src/modules/api.js b/src/modules/api.js index 68402602..5e213f0d 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -1,5 +1,6 @@ import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import { WSConnectionStatus } from '../services/api/api.service.js' +import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { Socket } from 'phoenix' const api = { @@ -77,6 +78,7 @@ const api = { messages: [message.chatUpdate.lastMessage] }) dispatch('updateChat', { chat: message.chatUpdate }) + maybeShowChatNotification(store, message.chatUpdate) } } ) diff --git a/src/modules/chats.js b/src/modules/chats.js index 228d6256..c7609018 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -2,6 +2,7 @@ import Vue from 'vue' import { find, omitBy, orderBy, sumBy } from 'lodash' import chatService from '../services/chat_service/chat_service.js' import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js' +import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' const emptyChatList = () => ({ data: [], @@ -59,8 +60,12 @@ const chats = { return chats }) }, - addNewChats ({ rootState, commit, dispatch, rootGetters }, { chats }) { - commit('addNewChats', { dispatch, chats, rootGetters }) + addNewChats (store, { chats }) { + const { commit, dispatch, rootGetters } = store + const newChatMessageSideEffects = (chat) => { + maybeShowChatNotification(store, chat) + } + commit('addNewChats', { dispatch, chats, rootGetters, newChatMessageSideEffects }) }, updateChat ({ commit }, { chat }) { commit('updateChat', { chat }) @@ -130,13 +135,17 @@ const chats = { setCurrentChatId (state, { chatId }) { state.currentChatId = chatId }, - addNewChats (state, { _dispatch, chats, _rootGetters }) { + addNewChats (state, { chats, newChatMessageSideEffects }) { chats.forEach((updatedChat) => { const chat = getChatById(state, updatedChat.id) if (chat) { + const isNewMessage = (chat.lastMessage && chat.lastMessage.id) !== (updatedChat.lastMessage && updatedChat.lastMessage.id) chat.lastMessage = updatedChat.lastMessage chat.unread = updatedChat.unread + if (isNewMessage && chat.unread) { + newChatMessageSideEffects(updatedChat) + } } else { state.chatList.data.push(updatedChat) Vue.set(state.chatList.idStore, updatedChat.id, updatedChat) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 64f5b587..e108b2a7 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,9 +13,8 @@ import { omitBy } from 'lodash' import { set } from 'vue' -import { isStatusNotification, prepareNotificationObject } from '../services/notification_utils/notification_utils.js' +import { isStatusNotification, maybeShowNotification } from '../services/notification_utils/notification_utils.js' import apiService from '../services/api/api.service.js' -import { muteWordHits } from '../services/status_parser/status_parser.js' const emptyTl = (userId = 0) => ({ statuses: [], @@ -77,17 +76,6 @@ export const prepareStatus = (status) => { return status } -const visibleNotificationTypes = (rootState) => { - return [ - rootState.config.notificationVisibility.likes && 'like', - rootState.config.notificationVisibility.mentions && 'mention', - rootState.config.notificationVisibility.repeats && 'repeat', - rootState.config.notificationVisibility.follows && 'follow', - rootState.config.notificationVisibility.moves && 'move', - rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reactions' - ].filter(_ => _) -} - const mergeOrAdd = (arr, obj, item) => { const oldItem = obj[item.id] @@ -325,7 +313,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us } } -const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => { +const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => { each(notifications, (notification) => { if (isStatusNotification(notification.type)) { notification.action = addStatusToGlobalStorage(state, notification.action).item @@ -348,27 +336,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot state.notifications.data.push(notification) state.notifications.idStore[notification.id] = notification - if ('Notification' in window && window.Notification.permission === 'granted') { - const notifObj = prepareNotificationObject(notification, rootGetters.i18n) - - const reasonsToMuteNotif = ( - notification.seen || - state.notifications.desktopNotificationSilence || - !visibleNotificationTypes.includes(notification.type) || - ( - notification.type === 'mention' && status && ( - status.muted || - muteWordHits(status, rootGetters.mergedConfig.muteWords).length === 0 - ) - ) - ) - if (!reasonsToMuteNotif) { - let desktopNotification = new window.Notification(notifObj.title, notifObj) - // Chrome is known for not closing notifications automatically - // according to MDN, anyway. - setTimeout(desktopNotification.close.bind(desktopNotification), 5000) - } - } + newNotificationSideEffects(notification) } else if (notification.seen) { state.notifications.idStore[notification.id].seen = true } @@ -609,8 +577,13 @@ const statuses = { addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) { commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId, pagination }) }, - addNewNotifications ({ rootState, commit, dispatch, rootGetters }, { notifications, older }) { - commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older, rootGetters }) + addNewNotifications (store, { notifications, older }) { + const { commit, dispatch, rootGetters } = store + + const newNotificationSideEffects = (notification) => { + maybeShowNotification(store, notification) + } + commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects }) }, setError ({ rootState, commit }, { value }) { commit('setError', { value }) diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js new file mode 100644 index 00000000..ab898ced --- /dev/null +++ b/src/services/chat_utils/chat_utils.js @@ -0,0 +1,19 @@ +import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js' + +export const maybeShowChatNotification = (store, chat) => { + if (!chat.lastMessage) return + if (store.rootState.chats.currentChatId === chat.id && !document.hidden) return + + const opts = { + tag: chat.lastMessage.id, + title: chat.account.name, + icon: chat.account.profile_image_url, + body: chat.lastMessage.content + } + + if (chat.lastMessage.attachment && chat.lastMessage.attachment.type === 'image') { + opts.image = chat.lastMessage.attachment.preview_url + } + + showDesktopNotification(store.rootState, opts) +} diff --git a/src/services/desktop_notification_utils/desktop_notification_utils.js b/src/services/desktop_notification_utils/desktop_notification_utils.js new file mode 100644 index 00000000..b84a1f75 --- /dev/null +++ b/src/services/desktop_notification_utils/desktop_notification_utils.js @@ -0,0 +1,9 @@ +export const showDesktopNotification = (rootState, desktopNotificationOpts) => { + if (!('Notification' in window && window.Notification.permission === 'granted')) return + if (rootState.statuses.notifications.desktopNotificationSilence) { return } + + const desktopNotification = new window.Notification(desktopNotificationOpts.title, desktopNotificationOpts) + // Chrome is known for not closing notifications automatically + // according to MDN, anyway. + setTimeout(desktopNotification.close.bind(desktopNotification), 5000) +} diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 7ea8a16c..c1bf8535 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -79,6 +79,7 @@ export const parseUser = (data) => { const relationship = data.pleroma.relationship output.background_image = data.pleroma.background_image + output.favicon = data.pleroma.favicon output.token = data.pleroma.chat_token if (relationship) { diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 5cc19215..d912d19f 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -1,16 +1,22 @@ import { filter, sortBy, includes } from 'lodash' +import { muteWordHits } from '../status_parser/status_parser.js' +import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js' export const notificationsFromStore = store => store.state.statuses.notifications.data -export const visibleTypes = store => ([ - store.state.config.notificationVisibility.likes && 'like', - store.state.config.notificationVisibility.mentions && 'mention', - store.state.config.notificationVisibility.repeats && 'repeat', - store.state.config.notificationVisibility.follows && 'follow', - store.state.config.notificationVisibility.followRequest && 'follow_request', - store.state.config.notificationVisibility.moves && 'move', - store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' -].filter(_ => _)) +export const visibleTypes = store => { + const rootState = store.rootState || store.state + + return ([ + rootState.config.notificationVisibility.likes && 'like', + rootState.config.notificationVisibility.mentions && 'mention', + rootState.config.notificationVisibility.repeats && 'repeat', + rootState.config.notificationVisibility.follows && 'follow', + rootState.config.notificationVisibility.followRequest && 'follow_request', + rootState.config.notificationVisibility.moves && 'move', + rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' + ].filter(_ => _)) +} const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction'] @@ -32,6 +38,22 @@ const sortById = (a, b) => { } } +const isMutedNotification = (store, notification) => { + if (!notification.status) return + return notification.status.muted || muteWordHits(notification.status, store.rootGetters.mergedConfig.muteWords).length > 0 +} + +export const maybeShowNotification = (store, notification) => { + const rootState = store.rootState || store.state + + if (notification.seen) return + if (!visibleTypes(store).includes(notification.type)) return + if (notification.type === 'mention' && isMutedNotification(store, notification)) return + + const notificationObject = prepareNotificationObject(notification, store.rootGetters.i18n) + showDesktopNotification(rootState, notificationObject) +} + export const filteredNotificationsFromStore = (store, types) => { // map is just to clone the array since sort mutates it and it causes some issues let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index d282074a..80be02ca 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -35,7 +35,7 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { const notifications = timelineData.data const readNotifsIds = notifications.filter(n => n.seen).map(n => n.id) const numUnseenNotifs = notifications.length - readNotifsIds.length - if (numUnseenNotifs > 0) { + if (numUnseenNotifs > 0 && readNotifsIds.length > 0) { args['since'] = Math.max(...readNotifsIds) fetchNotifications({ store, args, older }) } diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 214294eb..d0cddf84 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -43,7 +43,9 @@ const fetchAndUpdate = ({ args['userId'] = userId args['tag'] = tag args['withMuted'] = !hideMutedPosts - if (loggedIn) args['replyVisibility'] = replyVisibility + if (loggedIn && ['friends', 'public', 'publicAndExternal'].includes(timeline)) { + args['replyVisibility'] = replyVisibility + } const numStatusesBeforeFetch = timelineData.statuses.length |
