Browse Source

Web Push notifications supported

Closes #981
master
Igor Zhukov 8 years ago
parent
commit
11ccf1aa16
  1. 18
      app/js/controllers.js
  2. 7
      app/js/lib/mtproto.js
  3. 73
      app/js/lib/ng_utils.js
  4. 312
      app/js/lib/push_worker.js
  5. 2
      app/js/locales/en-us.json
  6. 2
      app/js/messages_manager.js
  7. 70
      app/js/services.js
  8. 7
      app/partials/desktop/settings_modal.html
  9. 7
      app/partials/mobile/settings_modal.html
  10. 4
      app/service_worker.js

18
app/js/controllers.js

@ -3989,7 +3989,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} }
}) })
.controller('SettingsModalController', function ($rootScope, $scope, $timeout, $modal, AppUsersManager, AppChatsManager, AppPhotosManager, MtpApiManager, Storage, NotificationsManager, MtpApiFileManager, PasswordManager, ApiUpdatesManager, ChangelogNotifyService, LayoutSwitchService, AppRuntimeManager, ErrorService, _) { .controller('SettingsModalController', function ($rootScope, $scope, $timeout, $modal, AppUsersManager, AppChatsManager, AppPhotosManager, MtpApiManager, Storage, NotificationsManager, MtpApiFileManager, PasswordManager, ApiUpdatesManager, ChangelogNotifyService, LayoutSwitchService, WebPushApiManager, AppRuntimeManager, ErrorService, _) {
$scope.profile = {} $scope.profile = {}
$scope.photo = {} $scope.photo = {}
$scope.version = Config.App.version $scope.version = Config.App.version
@ -4142,10 +4142,13 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}) })
} }
Storage.get('notify_nodesktop', 'send_ctrlenter', 'notify_volume', 'notify_novibrate', 'notify_nopreview').then(function (settings) { Storage.get('notify_nodesktop', 'send_ctrlenter', 'notify_volume', 'notify_novibrate', 'notify_nopreview', 'notify_nopush').then(function (settings) {
$scope.notify.desktop = !settings[0] $scope.notify.desktop = !settings[0]
$scope.send.enter = settings[1] ? '' : '1' $scope.send.enter = settings[1] ? '' : '1'
$scope.notify.pushAvailable = WebPushApiManager.isAvailable
$scope.notify.push = !settings[5]
if (settings[2] !== false) { if (settings[2] !== false) {
$scope.notify.volume = settings[2] > 0 && settings[2] <= 1.0 ? settings[2] : 0 $scope.notify.volume = settings[2] > 0 && settings[2] <= 1.0 ? settings[2] : 0
} else { } else {
@ -4196,6 +4199,17 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$rootScope.$broadcast('settings_changed') $rootScope.$broadcast('settings_changed')
} }
$scope.togglePush = function () {
$scope.notify.push = !$scope.notify.push
if ($scope.notify.push) {
Storage.remove('notify_nopush')
} else {
Storage.set({notify_nopush: true})
}
$rootScope.$broadcast('settings_changed')
}
$scope.togglePreview = function () { $scope.togglePreview = function () {
$scope.notify.preview = !$scope.notify.preview $scope.notify.preview = !$scope.notify.preview

7
app/js/lib/mtproto.js

@ -1468,6 +1468,11 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
} }
MtpNetworker.prototype.processMessage = function (message, messageID, sessionID) { MtpNetworker.prototype.processMessage = function (message, messageID, sessionID) {
var msgidInt = parseInt(messageID.toString(10).substr(0, -10), 10)
if (msgidInt % 2) {
console.warn('[MT] Server even message id: ', messageID, message)
return
}
// console.log('process message', message, messageID, sessionID) // console.log('process message', message, messageID, sessionID)
switch (message._) { switch (message._) {
case 'msg_container': case 'msg_container':
@ -1513,7 +1518,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
case 'message': case 'message':
if (this.lastServerMessages.indexOf(messageID) != -1) { if (this.lastServerMessages.indexOf(messageID) != -1) {
console.warn('[MT] Server same messageID: ', messageID) // console.warn('[MT] Server same messageID: ', messageID)
this.ackMessage(messageID) this.ackMessage(messageID)
return return
} }

73
app/js/lib/ng_utils.js

@ -394,7 +394,7 @@ angular.module('izhukov.utils', [])
request.onsuccess = function (event) { request.onsuccess = function (event) {
finished = true finished = true
db = request.result var db = request.result
db.onerror = function (error) { db.onerror = function (error) {
storageIsAvailable = false storageIsAvailable = false
@ -1981,7 +1981,7 @@ angular.module('izhukov.utils', [])
return timeParams return timeParams
}) })
.service('WebPushApiManager', function ($timeout, $q, $rootScope) { .service('WebPushApiManager', function ($window, $timeout, $q, $rootScope, AppRuntimeManager) {
var isAvailable = true var isAvailable = true
var isPushEnabled = false var isPushEnabled = false
@ -2002,6 +2002,7 @@ angular.module('izhukov.utils', [])
if (!started) { if (!started) {
started = true started = true
getSubscription() getSubscription()
setUpServiceWorkerChannel()
} }
} }
@ -2011,13 +2012,7 @@ angular.module('izhukov.utils', [])
} }
navigator.serviceWorker.ready.then(function(reg) { navigator.serviceWorker.ready.then(function(reg) {
reg.pushManager.getSubscription().then(function(subscription) { reg.pushManager.getSubscription().then(function(subscription) {
if (!subscription) { isPushEnabled = subscription ? true : false
console.log('Not yet subscribed to Push')
subscribe()
return
}
isPushEnabled = true
pushSubscriptionNotify('init', subscription) pushSubscriptionNotify('init', subscription)
}) })
.catch(function(err) { .catch(function(err) {
@ -2052,11 +2047,11 @@ angular.module('izhukov.utils', [])
} }
navigator.serviceWorker.ready.then(function(reg) { navigator.serviceWorker.ready.then(function(reg) {
reg.pushManager.getSubscription().then(function (subscription) { reg.pushManager.getSubscription().then(function (subscription) {
pushSubscriptionNotify('unsubscribe', subscription)
isPushEnabled = false isPushEnabled = false
if (subscription) { if (subscription) {
pushSubscriptionNotify('unsubscribe', subscription)
setTimeout(function() { setTimeout(function() {
subscription.unsubscribe().then(function(successful) { subscription.unsubscribe().then(function(successful) {
isPushEnabled = false isPushEnabled = false
@ -2073,12 +2068,55 @@ angular.module('izhukov.utils', [])
}) })
} }
function pushSubscriptionNotify(event, subscription) { function isAliveNotify() {
console.warn(dT(), 'Push', event, subscription.toJSON()) if (!isAvailable ||
$rootScope.$emit('push_' + event, { $rootScope.idle && $rootScope.idle.deactivated) {
tokenType: 10, return
tokenValue: JSON.stringify(subscription.toJSON()) }
var baseUrl = (location.href || '').replace(/#.*$/, '') + '#/im'
var eventData = {type: 'alive', baseUrl: baseUrl}
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage(eventData)
}
setTimeout(isAliveNotify, 10000)
}
function hidePushNotifications() {
if (!isAvailable) {
return
}
if (navigator.serviceWorker.controller) {
var eventData = {type: 'notifications_clear'}
navigator.serviceWorker.controller.postMessage(eventData)
}
}
function setUpServiceWorkerChannel() {
navigator.serviceWorker.addEventListener('message', function(event) {
if (event.data &&
event.data.type == 'push_click') {
if ($rootScope.idle && $rootScope.idle.deactivated) {
AppRuntimeManager.reload()
return
}
$rootScope.$emit('push_notification_click', event.data.data)
}
}) })
navigator.serviceWorker.ready.then(isAliveNotify)
}
function pushSubscriptionNotify(event, subscription) {
if (subscription) {
console.warn(dT(), 'Push', event, subscription.toJSON())
$rootScope.$emit('push_' + event, {
tokenType: 10,
tokenValue: JSON.stringify(subscription.toJSON())
})
} else {
console.warn(dT(), 'Push', event, false)
$rootScope.$emit('push_' + event, false)
}
} }
return { return {
@ -2086,7 +2124,8 @@ angular.module('izhukov.utils', [])
start: start, start: start,
isPushEnabled: isPushEnabled, isPushEnabled: isPushEnabled,
subscribe: subscribe, subscribe: subscribe,
unsubscribe: unsubscribe unsubscribe: unsubscribe,
hidePushNotifications: hidePushNotifications
} }
}) })

312
app/js/lib/push_worker.js

@ -1,47 +1,323 @@
console.log('Push worker placeholder') 'use strict';
console.log('[SW] Push worker started')
var port var port
var lastAliveTime = false
var pendingNotification = false
var muteUntil = false
var baseUrl
switch (location.hostname) {
case 'localhost':
baseUrl = 'http://localhost:8000/app/index.html#/im'
break
case 'zhukov.github.io':
baseUrl = 'https://zhukov.github.io/webogram/#/im'
break
default:
case 'web.telegram.org':
baseUrl = 'https://' + location.hostname + '/#/im'
}
self.addEventListener('push', function(event) { self.addEventListener('push', function(event) {
var obj = event.data.json() var obj = event.data.json()
console.log('push obj', obj) console.log('[SW] push', obj)
fireNotification(obj, event) if (!obj.badge) {
closeAllNotifications(obj, event)
} else {
fireNotification(obj, event)
}
}) })
self.onmessage = function(e) { self.addEventListener('activate', function(event) {
console.log(e) event.waitUntil(clients.claim())
port = e.ports[0] })
}
self.addEventListener('message', function(event) {
console.log('[SW] on message', event.data)
port = event.ports[0] || event.source
if (event.data.type == 'alive') {
lastAliveTime = +(new Date())
if (pendingNotification &&
port &&
'postMessage' in port) {
port.postMessage(pendingNotification)
pendingNotification = false
}
}
if (event.data.type == 'notifications_clear') {
closeAllNotifications(event.data, event)
}
if (event.data.baseUrl) {
baseUrl = event.data.baseUrl
}
})
function fireNotification(obj, event) { function fireNotification(obj, event) {
var nowTime = +(new Date())
if (nowTime - lastAliveTime < 60000) {
console.log('Supress notification because some instance is alive')
return false
}
if (muteUntil && nowTime < muteUntil) {
console.log('Supress notification because mute for ', (muteUntil - nowTime) / 60000, 'min')
return false
}
var title = obj.title || 'Telegram' var title = obj.title || 'Telegram'
var body = obj.description || '' var body = obj.description || ''
var icon = 'img/Telegram72.png' var icon = 'img/logo_share.png'
var peerID
event.waitUntil(self.registration.showNotification(title, {
if (obj.custom && obj.custom.channel_id) {
peerID = -obj.custom.channel_id
}
else if (obj.custom && obj.custom.chat_id) {
peerID = -obj.custom.chat_id
}
else {
peerID = obj.custom && obj.custom.from_id || 0
}
obj.custom.peerID = peerID
var notificationPromise = self.registration.showNotification(title, {
body: body, body: body,
icon: icon icon: icon,
tag: 'peer' + peerID,
data: obj,
actions: [
{
action: 'mute1d',
title: 'Mute background alerts for 1 day'
},
{
action: 'push_settings',
title: 'Background alerts settings'
}
]
})
var finalPromise = notificationPromise.then(function (event) {
if (event && event.notification) {
pushToNotifications(event.notification)
}
})
event.waitUntil(finalPromise)
return true
}
var notifications = []
function pushToNotifications(notification) {
if (notifications.indexOf(notification) == -1) {
notifications.push(notification)
notification.onclose = onCloseNotification
}
}
function onCloseNotification(event) {
muteUntil = Math.max(muteUntil || 0, +(new Date()) + 600000) // 10 min
removeFromNotifications(event.notification)
}
function removeFromNotifications(notification) {
console.warn('on close', notification)
var pos = notifications.indexOf(notification)
if (pos != -1) {
notifications.splice(pos, 1)
}
}
function closeAllNotifications(obj, event) {
for (var i = 0, len = notifications.length; i < len; i++) {
try {
notifications[i].close()
} catch (e) {}
}
event.waitUntil(self.registration.getNotifications({}).then(function(notifications) {
for (var i = 0, len = notifications.length; i < len; i++) {
try {
notifications[i].close()
} catch (e) {}
}
})) }))
notifications = []
} }
self.addEventListener('notificationclick', function(event) { self.addEventListener('notificationclick', function(event) {
console.log('On notification click: ', event.notification.tag) var notification = event.notification
event.notification.close() console.log('On notification click: ', notification.tag)
notification.close()
var action = event.action
if (action == 'mute1d') {
console.log('[SW] mute for 1d')
muteUntil = +(new Date()) + 86400000
IDBManager.setItem('mute_until', muteUntil.toString())
return
}
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(clients.matchAll({ event.waitUntil(clients.matchAll({
type: 'window' type: 'window'
}).then(function(clientList) { }).then(function(clientList) {
notification.data.action = action
pendingNotification = {type: 'push_click', data: notification.data}
for (var i = 0; i < clientList.length; i++) { for (var i = 0; i < clientList.length; i++) {
var client = clientList[i] var client = clientList[i]
if ('focus' in client) { if ('focus' in client) {
return client.focus() client.focus()
;(port || client).postMessage(pendingNotification)
pendingNotification = false
return
} }
} }
if (clients.openWindow) if (clients.openWindow) {
return clients.openWindow('') return clients.openWindow(baseUrl)
}
})) }))
})
self.addEventListener('notificationclose', onCloseNotification)
;(function () {
var dbName = 'keyvalue'
var dbStoreName = 'kvItems'
var dbVersion = 2
var openDbPromise
var idbIsAvailable = self.indexedDB !== undefined &&
self.IDBTransaction !== undefined
function isAvailable () {
return idbIsAvailable
}
function openDatabase () {
if (openDbPromise) {
return openDbPromise
}
return openDbPromise = new Promise(function (resolve, reject) {
try {
var request = indexedDB.open(dbName, dbVersion)
var createObjectStore = function (db) {
db.createObjectStore(dbStoreName)
}
if (!request) {
throw new Exception()
}
} catch (error) {
console.error('error opening db', error.message)
idbIsAvailable = false
return $q.reject(error)
}
var finished = false
setTimeout(function () {
if (!finished) {
request.onerror({type: 'IDB_CREATE_TIMEOUT'})
}
}, 3000)
request.onsuccess = function (event) {
finished = true
var db = request.result
db.onerror = function (error) {
idbIsAvailable = false
console.error('Error creating/accessing IndexedDB database', error)
reject(error)
}
resolve(db)
}
request.onerror = function (event) {
finished = true
idbIsAvailable = false
console.error('Error creating/accessing IndexedDB database', event)
reject(event)
}
request.onupgradeneeded = function (event) {
finished = true
console.warn('performing idb upgrade from', event.oldVersion, 'to', event.newVersion)
var db = event.target.result
if (event.oldVersion == 1) {
db.deleteObjectStore(dbStoreName)
}
createObjectStore(db)
}
})
}
function setItem (key, value) {
return openDatabase().then(function (db) {
try {
var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName)
var request = objectStore.put(value, key)
} catch (error) {
idbIsAvailable = false
return Promise.reject(error)
}
return new Promise(function(resolve, reject) {
request.onsuccess = function (event) {
resolve(value)
}
request.onerror = function (error) {
reject(error)
}
})
})
}
function getItem (key) {
return openDatabase().then(function (db) {
return new Promise(function(resolve, reject) {
var objectStore = db.transaction([dbStoreName], IDBTransaction.READ || 'readonly').objectStore(dbStoreName)
var request = objectStore.get(key)
request.onsuccess = function (event) {
var result = event.target.result
if (result === undefined) {
reject()
} else {
resolve(result)
}
}
request.onerror = function (error) {
reject(error)
}
})
})
}
openDatabase()
self.IDBManager = {
name: 'IndexedDB',
isAvailable: isAvailable,
setItem: setItem,
getItem: getItem
}
})()
IDBManager.getItem('mute_until').then(function (newMuteUntil) {
muteUntil = Math.max(muteUntil || 0, newMuteUntil || 0) || false
}) })

2
app/js/locales/en-us.json

@ -59,10 +59,12 @@
"settings_modal_active_sessions": "Active sessions", "settings_modal_active_sessions": "Active sessions",
"settings_modal_settings": "Settings", "settings_modal_settings": "Settings",
"settings_modal_notification_alert": "Notification alerts", "settings_modal_notification_alert": "Notification alerts",
"settings_modal_notification_push": "PUSH notifications",
"settings_modal_vibrate": "Vibrate", "settings_modal_vibrate": "Vibrate",
"settings_modal_sounds": "Sounds", "settings_modal_sounds": "Sounds",
"settings_modal_language": "Language", "settings_modal_language": "Language",
"settings_modal_notifications": "Desktop notifications", "settings_modal_notifications": "Desktop notifications",
"settings_modal_pushes": "Background notifications",
"settings_modal_message_preview": "Message preview", "settings_modal_message_preview": "Message preview",
"settings_modal_sound": "Sound", "settings_modal_sound": "Sound",
"settings_modal_enter_send_description_md": "**Enter** - send message, **Shift + Enter** - new line", "settings_modal_enter_send_description_md": "**Enter** - send message, **Shift + Enter** - new line",

2
app/js/messages_manager.js

@ -260,6 +260,8 @@ angular.module('myApp.services')
if (hasPrepend && if (hasPrepend &&
!newDialogsHandlePromise) { !newDialogsHandlePromise) {
newDialogsHandlePromise = $timeout(handleNewDialogs, 0) newDialogsHandlePromise = $timeout(handleNewDialogs, 0)
} else {
$rootScope.$broadcast('dialogs_multiupdate', {})
} }
}) })
} }

70
app/js/services.js

@ -2512,7 +2512,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
}) })
.service('AppInlineBotsManager', function (qSync, $q, $rootScope, toaster, Storage, ErrorService, MtpApiManager, AppMessagesManager, AppMessagesIDsManager, AppDocsManager, AppPhotosManager, AppGamesManager, RichTextProcessor, AppUsersManager, AppPeersManager, PeersSelectService, GeoLocationManager) { .service('AppInlineBotsManager', function (qSync, $q, $rootScope, toaster, Storage, ErrorService, MtpApiManager, AppMessagesManager, AppMessagesIDsManager, AppDocsManager, AppPhotosManager, AppGamesManager, RichTextProcessor, AppUsersManager, AppPeersManager, LocationParamsService, PeersSelectService, GeoLocationManager) {
var inlineResults = {} var inlineResults = {}
return { return {
@ -3516,7 +3516,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
}) })
.service('NotificationsManager', function ($rootScope, $window, $interval, $q, _, MtpApiManager, AppPeersManager, IdleManager, Storage, AppRuntimeManager, FileManager, WebPushApiManager) { .service('NotificationsManager', function ($rootScope, $window, $interval, $q, $modal, _, MtpApiManager, AppPeersManager, AppChatsManager, AppUsersManager, IdleManager, Storage, AppRuntimeManager, FileManager, WebPushApiManager) {
navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate
var notificationsMsSiteMode = false var notificationsMsSiteMode = false
@ -3598,9 +3598,17 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}) })
var registeredDevice = false var registeredDevice = false
var pushInited = false
$rootScope.$on('push_init', function (e, tokenData) { $rootScope.$on('push_init', function (e, tokenData) {
if (tokenData) { pushInited = true
registerDevice(tokenData) if (!settings.nodesktop && !settings.nopush) {
if (tokenData) {
registerDevice(tokenData)
} else {
WebPushApiManager.subscribe()
}
} else {
unregisterDevice(tokenData)
} }
}) })
$rootScope.$on('push_subscribe', function (e, tokenData) { $rootScope.$on('push_subscribe', function (e, tokenData) {
@ -3610,6 +3618,42 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
unregisterDevice(tokenData) unregisterDevice(tokenData)
}) })
var topMessagesDeferred = $q.defer()
var unregisterTopMsgs = $rootScope.$on('dialogs_multiupdate', function () {
unregisterTopMsgs()
topMessagesDeferred.resolve()
})
var topMessagesPromise = topMessagesDeferred.promise
$rootScope.$on('push_notification_click', function (e, notificationData) {
if (notificationData.action == 'push_settings') {
$modal.open({
templateUrl: templateUrl('settings_modal'),
controller: 'SettingsModalController',
windowClass: 'settings_modal_window mobile_modal',
backdrop: 'single'
})
return
}
var peerID = notificationData.custom && notificationData.custom.peerID
console.log('click', notificationData, peerID)
if (peerID) {
topMessagesPromise.then(function () {
if (notificationData.custom.channel_id &&
!AppChatsManager.hasChat(notificationData.custom.channel_id)) {
return
}
if (peerID > 0 && !AppUsersManager.hasUser(peerID)) {
return
}
$rootScope.$broadcast('history_focus', {
peerString: AppPeersManager.getPeerString(peerID)
})
})
}
})
return { return {
start: start, start: start,
notify: notify, notify: notify,
@ -3627,7 +3671,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
function updateNotifySettings () { function updateNotifySettings () {
Storage.get('notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview').then(function (updSettings) { Storage.get('notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview', 'notify_nopush').then(function (updSettings) {
settings.nodesktop = updSettings[0] settings.nodesktop = updSettings[0]
settings.volume = updSettings[1] === false settings.volume = updSettings[1] === false
? 0.5 ? 0.5
@ -3635,6 +3679,20 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
settings.novibrate = updSettings[2] settings.novibrate = updSettings[2]
settings.nopreview = updSettings[3] settings.nopreview = updSettings[3]
settings.nopush = updSettings[4]
if (pushInited) {
var needPush = !settings.nopush && !settings.nodesktop && WebPushApiManager.isAvailable || false
var hasPush = registeredDevice !== false
if (needPush != hasPush) {
if (needPush) {
WebPushApiManager.subscribe()
} else {
WebPushApiManager.unsubscribe()
}
}
}
}) })
} }
@ -3901,6 +3959,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
} }
notificationsShown = {} notificationsShown = {}
notificationsCount = 0 notificationsCount = 0
WebPushApiManager.hidePushNotifications()
} }
function registerDevice (tokenData) { function registerDevice (tokenData) {

7
app/partials/desktop/settings_modal.html

@ -61,6 +61,13 @@
<span class="tg_checkbox_label" my-i18n="settings_modal_notifications"></span> <span class="tg_checkbox_label" my-i18n="settings_modal_notifications"></span>
</a> </a>
<a ng-if="notify.desktop && notify.pushAvailable" class="md_modal_section_toggle_wrap tg_checkbox" ng-click="togglePush()" ng-class="notify.push ? 'tg_checkbox_on' : ''">
<span class="icon icon-checkbox-outer"><i class="icon-checkbox-inner"></i></span>
<span class="tg_checkbox_label" my-i18n="settings_modal_pushes"></span>
</a>
<a class="md_modal_section_toggle_wrap tg_checkbox" ng-click="togglePreview()" ng-class="notify.preview ? 'tg_checkbox_on' : ''"> <a class="md_modal_section_toggle_wrap tg_checkbox" ng-click="togglePreview()" ng-class="notify.preview ? 'tg_checkbox_on' : ''">
<span class="icon icon-checkbox-outer"><i class="icon-checkbox-inner"></i></span> <span class="icon icon-checkbox-outer"><i class="icon-checkbox-inner"></i></span>
<span class="tg_checkbox_label" my-i18n="settings_modal_message_preview"></span> <span class="tg_checkbox_label" my-i18n="settings_modal_message_preview"></span>

7
app/partials/mobile/settings_modal.html

@ -86,6 +86,13 @@
</a> </a>
</div> </div>
<div ng-if="notify.desktop && notify.pushAvailable" class="mobile_modal_action_wrap">
<a class="mobile_modal_action tg_checkbox clearfix" ng-click="togglePush()" ng-class="notify.push ? 'tg_checkbox_on' : ''">
<span class="icon icon-checkbox-outer"><i class="icon-checkbox-inner"></i></span>
<span class="tg_checkbox_label" my-i18n="settings_modal_notification_push"></span>
</a>
</div>
<div class="mobile_modal_action_wrap"> <div class="mobile_modal_action_wrap">
<a class="mobile_modal_action tg_checkbox clearfix" ng-click="togglePreview()" ng-class="notify.preview ? 'tg_checkbox_on' : ''"> <a class="mobile_modal_action tg_checkbox clearfix" ng-click="togglePreview()" ng-class="notify.preview ? 'tg_checkbox_on' : ''">
<span class="icon icon-checkbox-outer"><i class="icon-checkbox-inner"></i></span> <span class="icon icon-checkbox-outer"><i class="icon-checkbox-inner"></i></span>

4
app/service_worker.js

@ -1,3 +1,3 @@
// Version 7 // Version 44
importScripts('js/lib/push_worker.js?3') importScripts('js/lib/push_worker.js?44')

Loading…
Cancel
Save