From a52a393266744560c6c6b2cbd24da812d35562fb Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 15:22:31 +0200 Subject: [PATCH 01/10] NotificationUtils: Extract preparation of notification object. --- src/modules/statuses.js | 41 ++---------------- .../notification_utils/notification_utils.js | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 9a2e0df1e..073b15f1b 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 eb479227c..4576ea833 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -43,3 +43,45 @@ export const filteredNotificationsFromStore = (store, types) => { export const unseenNotificationsFromStore = store => filter(filteredNotificationsFromStore(store), ({ seen }) => !seen) + +export const prepareNotificationObject = (notification, i18n) => { + const notifObj = {} + 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 +} From 5b0190bef5a9756453d0220b80a18469639c8fe9 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 15:22:58 +0200 Subject: [PATCH 02/10] ServiceWorker: Grab the notification and display it with i18n. --- src/sw.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/src/sw.js b/src/sw.js index 6cecb3f38..0aab275b6 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,6 +1,48 @@ /* 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' + +const messages = { + ar: require('./i18n/ar.json'), + ca: require('./i18n/ca.json'), + cs: require('./i18n/cs.json'), + de: require('./i18n/de.json'), + eo: require('./i18n/eo.json'), + es: require('./i18n/es.json'), + et: require('./i18n/et.json'), + eu: require('./i18n/eu.json'), + fi: require('./i18n/fi.json'), + fr: require('./i18n/fr.json'), + ga: require('./i18n/ga.json'), + he: require('./i18n/he.json'), + hu: require('./i18n/hu.json'), + it: require('./i18n/it.json'), + ja: require('./i18n/ja_pedantic.json'), + ja_easy: require('./i18n/ja_easy.json'), + ko: require('./i18n/ko.json'), + nb: require('./i18n/nb.json'), + nl: require('./i18n/nl.json'), + oc: require('./i18n/oc.json'), + pl: require('./i18n/pl.json'), + pt: require('./i18n/pt.json'), + ro: require('./i18n/ro.json'), + ru: require('./i18n/ru.json'), + te: require('./i18n/te.json'), + zh: require('./i18n/zh.json'), + en: require('./i18n/en.json') +} + +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 +54,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) { + 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 } }) + let nj = await notification.json() + nj = parseNotification(nj) + + const res = prepareNotificationObject(nj, i18n) + + self.registration.showNotification(res.title, res) + } +} + +self.addEventListener('push', async (event) => { + if (event.data) { + event.waitUntil(maybeShowNotification(event)) } }) From e2ca107e01c5a0715aa1a1a68496fe514eb9e902 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 15:24:01 +0200 Subject: [PATCH 03/10] ServiceWorker: Don't show message via sw if a client is active. --- src/sw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sw.js b/src/sw.js index 0aab275b6..d0050b24e 100644 --- a/src/sw.js +++ b/src/sw.js @@ -64,7 +64,7 @@ const maybeShowNotification = async (event) => { const enabled = await isEnabled() const activeClients = await getWindowClients() await setLocale() - if (enabled && activeClients) { + if (enabled && (activeClients.length === 0)) { const data = event.data.json() const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}` From 98819ae32c6f3962df955ee280cd6cc271625852 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 15:49:39 +0200 Subject: [PATCH 04/10] NotificationUtils: Add tag to notifications. --- src/services/notification_utils/notification_utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 4576ea833..5cc19215a 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -45,7 +45,9 @@ export const unseenNotificationsFromStore = store => filter(filteredNotificationsFromStore(store), ({ seen }) => !seen) export const prepareNotificationObject = (notification, i18n) => { - const notifObj = {} + const notifObj = { + tag: notification.id + } const status = notification.status const title = notification.from_profile.name notifObj.title = title From 9bfb3754c1bc0d1033afda97f2884e721d1ab3d8 Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 13 Jun 2020 11:47:34 +0200 Subject: [PATCH 05/10] ServiceWorker: Use loader to only notification messages. This keeps the translation size very small and makes it easy to integrate all the languages, as dynamically loading them isn't easy in the service worker. --- src/lib/notification-i18n-loader.js | 9 +++++ src/sw.js | 55 +++++++++++++++-------------- 2 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 src/lib/notification-i18n-loader.js diff --git a/src/lib/notification-i18n-loader.js b/src/lib/notification-i18n-loader.js new file mode 100644 index 000000000..a61755c80 --- /dev/null +++ b/src/lib/notification-i18n-loader.js @@ -0,0 +1,9 @@ +// This somewhat mysterious module +module.exports = function(source) { + var object = JSON.parse(source) + var smol = { + notifications: object.notifications || {} + } + + return JSON.stringify(smol) +} diff --git a/src/sw.js b/src/sw.js index d0050b24e..6e31516a8 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,4 +1,5 @@ /* eslint-env serviceworker */ +/* eslint-disable import/no-webpack-loader-syntax */ import localForage from 'localforage' import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js' @@ -7,33 +8,33 @@ import Vue from 'vue' import VueI18n from 'vue-i18n' const messages = { - ar: require('./i18n/ar.json'), - ca: require('./i18n/ca.json'), - cs: require('./i18n/cs.json'), - de: require('./i18n/de.json'), - eo: require('./i18n/eo.json'), - es: require('./i18n/es.json'), - et: require('./i18n/et.json'), - eu: require('./i18n/eu.json'), - fi: require('./i18n/fi.json'), - fr: require('./i18n/fr.json'), - ga: require('./i18n/ga.json'), - he: require('./i18n/he.json'), - hu: require('./i18n/hu.json'), - it: require('./i18n/it.json'), - ja: require('./i18n/ja_pedantic.json'), - ja_easy: require('./i18n/ja_easy.json'), - ko: require('./i18n/ko.json'), - nb: require('./i18n/nb.json'), - nl: require('./i18n/nl.json'), - oc: require('./i18n/oc.json'), - pl: require('./i18n/pl.json'), - pt: require('./i18n/pt.json'), - ro: require('./i18n/ro.json'), - ru: require('./i18n/ru.json'), - te: require('./i18n/te.json'), - zh: require('./i18n/zh.json'), - en: require('./i18n/en.json') + ar: require('./lib/notification-i18n-loader.js!./i18n/ar.json'), + ca: require('./lib/notification-i18n-loader.js!./i18n/ca.json'), + cs: require('./lib/notification-i18n-loader.js!./i18n/cs.json'), + de: require('./lib/notification-i18n-loader.js!./i18n/de.json'), + eo: require('./lib/notification-i18n-loader.js!./i18n/eo.json'), + es: require('./lib/notification-i18n-loader.js!./i18n/es.json'), + et: require('./lib/notification-i18n-loader.js!./i18n/et.json'), + eu: require('./lib/notification-i18n-loader.js!./i18n/eu.json'), + fi: require('./lib/notification-i18n-loader.js!./i18n/fi.json'), + fr: require('./lib/notification-i18n-loader.js!./i18n/fr.json'), + ga: require('./lib/notification-i18n-loader.js!./i18n/ga.json'), + he: require('./lib/notification-i18n-loader.js!./i18n/he.json'), + hu: require('./lib/notification-i18n-loader.js!./i18n/hu.json'), + it: require('./lib/notification-i18n-loader.js!./i18n/it.json'), + ja: require('./lib/notification-i18n-loader.js!./i18n/ja_pedantic.json'), + ja_easy: require('./lib/notification-i18n-loader.js!./i18n/ja_easy.json'), + ko: require('./lib/notification-i18n-loader.js!./i18n/ko.json'), + nb: require('./lib/notification-i18n-loader.js!./i18n/nb.json'), + nl: require('./lib/notification-i18n-loader.js!./i18n/nl.json'), + oc: require('./lib/notification-i18n-loader.js!./i18n/oc.json'), + pl: require('./lib/notification-i18n-loader.js!./i18n/pl.json'), + pt: require('./lib/notification-i18n-loader.js!./i18n/pt.json'), + ro: require('./lib/notification-i18n-loader.js!./i18n/ro.json'), + ru: require('./lib/notification-i18n-loader.js!./i18n/ru.json'), + te: require('./lib/notification-i18n-loader.js!./i18n/te.json'), + zh: require('./lib/notification-i18n-loader.js!./i18n/zh.json'), + en: require('./lib/notification-i18n-loader.js!./i18n/en.json') } Vue.use(VueI18n) From 1e57adf6d4a46ab5be677bba7ae3c7f0ba0fc520 Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 13 Jun 2020 11:53:16 +0200 Subject: [PATCH 06/10] Linting + docs --- src/lib/notification-i18n-loader.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/notification-i18n-loader.js b/src/lib/notification-i18n-loader.js index a61755c80..71f9156a8 100644 --- a/src/lib/notification-i18n-loader.js +++ b/src/lib/notification-i18n-loader.js @@ -1,5 +1,8 @@ -// This somewhat mysterious module -module.exports = function(source) { +// 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 || {} From 14540e2a078bd59ca8f13cd5c0b894acad3d639f Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 16 Jun 2020 13:27:36 +0200 Subject: [PATCH 07/10] Service Worker: Extract messages to own module. --- src/i18n/service_worker_messages.js | 35 +++++++++++++++++++++++++++++ src/sw.js | 32 +------------------------- 2 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 src/i18n/service_worker_messages.js diff --git a/src/i18n/service_worker_messages.js b/src/i18n/service_worker_messages.js new file mode 100644 index 000000000..270ed043c --- /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/sw.js b/src/sw.js index 6e31516a8..c971222ed 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,41 +1,11 @@ /* eslint-env serviceworker */ -/* eslint-disable import/no-webpack-loader-syntax */ 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' - -const messages = { - ar: require('./lib/notification-i18n-loader.js!./i18n/ar.json'), - ca: require('./lib/notification-i18n-loader.js!./i18n/ca.json'), - cs: require('./lib/notification-i18n-loader.js!./i18n/cs.json'), - de: require('./lib/notification-i18n-loader.js!./i18n/de.json'), - eo: require('./lib/notification-i18n-loader.js!./i18n/eo.json'), - es: require('./lib/notification-i18n-loader.js!./i18n/es.json'), - et: require('./lib/notification-i18n-loader.js!./i18n/et.json'), - eu: require('./lib/notification-i18n-loader.js!./i18n/eu.json'), - fi: require('./lib/notification-i18n-loader.js!./i18n/fi.json'), - fr: require('./lib/notification-i18n-loader.js!./i18n/fr.json'), - ga: require('./lib/notification-i18n-loader.js!./i18n/ga.json'), - he: require('./lib/notification-i18n-loader.js!./i18n/he.json'), - hu: require('./lib/notification-i18n-loader.js!./i18n/hu.json'), - it: require('./lib/notification-i18n-loader.js!./i18n/it.json'), - ja: require('./lib/notification-i18n-loader.js!./i18n/ja_pedantic.json'), - ja_easy: require('./lib/notification-i18n-loader.js!./i18n/ja_easy.json'), - ko: require('./lib/notification-i18n-loader.js!./i18n/ko.json'), - nb: require('./lib/notification-i18n-loader.js!./i18n/nb.json'), - nl: require('./lib/notification-i18n-loader.js!./i18n/nl.json'), - oc: require('./lib/notification-i18n-loader.js!./i18n/oc.json'), - pl: require('./lib/notification-i18n-loader.js!./i18n/pl.json'), - pt: require('./lib/notification-i18n-loader.js!./i18n/pt.json'), - ro: require('./lib/notification-i18n-loader.js!./i18n/ro.json'), - ru: require('./lib/notification-i18n-loader.js!./i18n/ru.json'), - te: require('./lib/notification-i18n-loader.js!./i18n/te.json'), - zh: require('./lib/notification-i18n-loader.js!./i18n/zh.json'), - en: require('./lib/notification-i18n-loader.js!./i18n/en.json') -} +import messages from './i18n/service_worker_messages.js' Vue.use(VueI18n) const i18n = new VueI18n({ From 5aa65e32ef0b14d6a70257e35559360e696f878c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 19 Jun 2020 16:09:44 +0300 Subject: [PATCH 08/10] fix huge emoji in usernames --- src/components/status/status.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 336f912a7..7ec29b288 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -418,7 +418,7 @@ $status-margin: 0.75em; max-width: 85%; font-weight: bold; - img { + img.emoji { width: 14px; height: 14px; vertical-align: middle; From aa725010c5a3eeeb2dae3e6618b057e08fc5fd49 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 19 Jun 2020 15:24:06 +0200 Subject: [PATCH 09/10] ServiceWorker: Use clearer variable names --- src/sw.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sw.js b/src/sw.js index c971222ed..f5e34dd60 100644 --- a/src/sw.js +++ b/src/sw.js @@ -40,10 +40,10 @@ const maybeShowNotification = async (event) => { const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}` const notification = await fetch(url, { headers: { Authorization: 'Bearer ' + data.access_token } }) - let nj = await notification.json() - nj = parseNotification(nj) + const notificationJson = await notification.json() + const parsedNotification = parseNotification(notificationJson) - const res = prepareNotificationObject(nj, i18n) + const res = prepareNotificationObject(parsedNotification, i18n) self.registration.showNotification(res.title, res) } From ed908e0195db26ec9c51d124315bc084c5576a29 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 19 Jun 2020 15:25:57 +0200 Subject: [PATCH 10/10] Changelog: Add info about push notifications. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad03c7602..19ac4ba3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Changed - 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)