diff --git a/CHANGELOG.md b/CHANGELOG.md index 0acd1863..3b41a04d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Greentext now has separate color slot for it - Removed the use of with_move parameters when fetching notifications +- Push notifications now are the same as normal notfication, and are localized. ### Fixed - Weird bug related to post being sent seemingly after pasting with keyboard (hopefully) diff --git a/src/i18n/service_worker_messages.js b/src/i18n/service_worker_messages.js new file mode 100644 index 00000000..270ed043 --- /dev/null +++ b/src/i18n/service_worker_messages.js @@ -0,0 +1,35 @@ +/* eslint-disable import/no-webpack-loader-syntax */ +// This module exports only the notification part of the i18n, +// which is useful for the service worker + +const messages = { + ar: require('../lib/notification-i18n-loader.js!./ar.json'), + ca: require('../lib/notification-i18n-loader.js!./ca.json'), + cs: require('../lib/notification-i18n-loader.js!./cs.json'), + de: require('../lib/notification-i18n-loader.js!./de.json'), + eo: require('../lib/notification-i18n-loader.js!./eo.json'), + es: require('../lib/notification-i18n-loader.js!./es.json'), + et: require('../lib/notification-i18n-loader.js!./et.json'), + eu: require('../lib/notification-i18n-loader.js!./eu.json'), + fi: require('../lib/notification-i18n-loader.js!./fi.json'), + fr: require('../lib/notification-i18n-loader.js!./fr.json'), + ga: require('../lib/notification-i18n-loader.js!./ga.json'), + he: require('../lib/notification-i18n-loader.js!./he.json'), + hu: require('../lib/notification-i18n-loader.js!./hu.json'), + it: require('../lib/notification-i18n-loader.js!./it.json'), + ja: require('../lib/notification-i18n-loader.js!./ja_pedantic.json'), + ja_easy: require('../lib/notification-i18n-loader.js!./ja_easy.json'), + ko: require('../lib/notification-i18n-loader.js!./ko.json'), + nb: require('../lib/notification-i18n-loader.js!./nb.json'), + nl: require('../lib/notification-i18n-loader.js!./nl.json'), + oc: require('../lib/notification-i18n-loader.js!./oc.json'), + pl: require('../lib/notification-i18n-loader.js!./pl.json'), + pt: require('../lib/notification-i18n-loader.js!./pt.json'), + ro: require('../lib/notification-i18n-loader.js!./ro.json'), + ru: require('../lib/notification-i18n-loader.js!./ru.json'), + te: require('../lib/notification-i18n-loader.js!./te.json'), + zh: require('../lib/notification-i18n-loader.js!./zh.json'), + en: require('../lib/notification-i18n-loader.js!./en.json') +} + +export default messages diff --git a/src/lib/notification-i18n-loader.js b/src/lib/notification-i18n-loader.js new file mode 100644 index 00000000..71f9156a --- /dev/null +++ b/src/lib/notification-i18n-loader.js @@ -0,0 +1,12 @@ +// This somewhat mysterious module will load a json string +// and then extract only the 'notifications' part. This is +// meant to be used to load the partial i18n we need for +// the service worker. +module.exports = function (source) { + var object = JSON.parse(source) + var smol = { + notifications: object.notifications || {} + } + + return JSON.stringify(smol) +} diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 9a2e0df1..073b15f1 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,7 +13,7 @@ import { omitBy } from 'lodash' import { set } from 'vue' -import { isStatusNotification } from '../services/notification_utils/notification_utils.js' +import { isStatusNotification, prepareNotificationObject } from '../services/notification_utils/notification_utils.js' import apiService from '../services/api/api.service.js' import { muteWordHits } from '../services/status_parser/status_parser.js' @@ -344,42 +344,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot state.notifications.idStore[notification.id] = notification if ('Notification' in window && window.Notification.permission === 'granted') { - const notifObj = {} - 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 - case 'move': - i18nString = 'migrated_to' - break - case 'follow_request': - i18nString = 'follow_request' - break - } - - if (notification.type === 'pleroma:emoji_reaction') { - notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji]) - } else if (i18nString) { - notifObj.body = rootGetters.i18n.t('notifications.' + i18nString) - } else if (isStatusNotification(notification.type)) { - notifObj.body = notification.status.text - } - - // Shows first attached non-nsfw image, if any. Should add configuration for this somehow... - if (status && status.attachments && status.attachments.length > 0 && !status.nsfw && - status.attachments[0].mimetype.startsWith('image/')) { - notifObj.image = status.attachments[0].url - } + const notifObj = prepareNotificationObject(notification, rootGetters.i18n) const reasonsToMuteNotif = ( notification.seen || @@ -393,7 +358,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot ) ) if (!reasonsToMuteNotif) { - let desktopNotification = new window.Notification(title, notifObj) + 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) diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index eb479227..5cc19215 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -43,3 +43,47 @@ export const filteredNotificationsFromStore = (store, types) => { export const unseenNotificationsFromStore = store => filter(filteredNotificationsFromStore(store), ({ seen }) => !seen) + +export const prepareNotificationObject = (notification, i18n) => { + const notifObj = { + tag: notification.id + } + const status = notification.status + const title = notification.from_profile.name + notifObj.title = title + 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 + case 'move': + i18nString = 'migrated_to' + break + case 'follow_request': + i18nString = 'follow_request' + break + } + + if (notification.type === 'pleroma:emoji_reaction') { + notifObj.body = i18n.t('notifications.reacted_with', [notification.emoji]) + } else if (i18nString) { + notifObj.body = i18n.t('notifications.' + i18nString) + } else if (isStatusNotification(notification.type)) { + notifObj.body = notification.status.text + } + + // Shows first attached non-nsfw image, if any. Should add configuration for this somehow... + if (status && status.attachments && status.attachments.length > 0 && !status.nsfw && + status.attachments[0].mimetype.startsWith('image/')) { + notifObj.image = status.attachments[0].url + } + + return notifObj +} diff --git a/src/sw.js b/src/sw.js index 6cecb3f3..f5e34dd6 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,6 +1,19 @@ /* eslint-env serviceworker */ import localForage from 'localforage' +import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js' +import { prepareNotificationObject } from './services/notification_utils/notification_utils.js' +import Vue from 'vue' +import VueI18n from 'vue-i18n' +import messages from './i18n/service_worker_messages.js' + +Vue.use(VueI18n) +const i18n = new VueI18n({ + // By default, use the browser locale, we will update it if neccessary + locale: 'en', + fallbackLocale: 'en', + messages +}) function isEnabled () { return localForage.getItem('vuex-lz') @@ -12,15 +25,33 @@ function getWindowClients () { .then((clientList) => clientList.filter(({ type }) => type === 'window')) } -self.addEventListener('push', (event) => { - if (event.data) { - event.waitUntil(isEnabled().then((isEnabled) => { - return isEnabled && getWindowClients().then((list) => { - const data = event.data.json() +const setLocale = async () => { + const state = await localForage.getItem('vuex-lz') + const locale = state.config.interfaceLanguage || 'en' + i18n.locale = locale +} - if (list.length === 0) return self.registration.showNotification(data.title, data) - }) - })) +const maybeShowNotification = async (event) => { + const enabled = await isEnabled() + const activeClients = await getWindowClients() + await setLocale() + if (enabled && (activeClients.length === 0)) { + const data = event.data.json() + + const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}` + const notification = await fetch(url, { headers: { Authorization: 'Bearer ' + data.access_token } }) + const notificationJson = await notification.json() + const parsedNotification = parseNotification(notificationJson) + + const res = prepareNotificationObject(parsedNotification, i18n) + + self.registration.showNotification(res.title, res) + } +} + +self.addEventListener('push', async (event) => { + if (event.data) { + event.waitUntil(maybeShowNotification(event)) } })