diff --git a/app/Application.js b/app/Application.js index dbe77934..fddd9859 100644 --- a/app/Application.js +++ b/app/Application.js @@ -1,334 +1,444 @@ -Ext.define('Rambox.Application', { - extend: 'Ext.app.Application' +Ext.define("Rambox.Application", { + extend: "Ext.app.Application", - ,name: 'Rambox' + name: "Rambox", - ,requires: [ - 'Rambox.ux.Auth0' - ,'Rambox.util.MD5' - ,'Ext.window.Toast' - ,'Ext.util.Cookies' - ] + requires: [ + "Rambox.ux.Auth0", + "Rambox.util.MD5", + "Ext.window.Toast", + "Ext.util.Cookies", + ], - ,stores: [ - 'ServicesList' - ,'Services' - ] + stores: ["ServicesList", "Services"], - ,profiles: [ - 'Offline' - ,'Online' - ] + profiles: ["Offline", "Online"], - ,config: { - totalServicesLoaded: 0 - ,totalNotifications: 0 - ,googleURLs: [] - } + config: { + totalServicesLoaded: 0, + totalNotifications: 0, + googleURLs: [], + }, - ,launch: function () { + launch: function () { + const isOnline = require("is-online"); + const Mousetrap = require("mousetrap"); + (async () => { + await isOnline().then((res) => { + var hideNoConnection = ipc.sendSync("getConfig").hideNoConnectionDialog; + if (!res && !hideNoConnection) { + Ext.get("spinner") ? Ext.get("spinner").destroy() : null; + Ext.get("background") ? Ext.get("background").destroy() : null; + Ext.Msg.show({ + title: "No Internet Connection", + msg: + "Please, check your internet connection. If you use a Proxy, please go to Preferences to configure it. Rambox will try to re-connect in 10 seconds", + width: 300, + closable: false, + buttons: Ext.Msg.YESNO, + buttonText: { + yes: "Ok", + no: "Never show this again", + }, + multiline: false, + fn: function (buttonValue, inputText, showConfig) { + if (buttonValue === "no") { + ipc.send("sConfig", { hideNoConnectionDialog: true }); + hideNoConnection = true; + } + }, + icon: Ext.Msg.QUESTION, + }); + setTimeout(function () { + if (!hideNoConnection) ipc.send("reloadApp"); + }, 10000); + } + }); + })(); - const isOnline = require('is-online'); - const Mousetrap = require('mousetrap'); - (async () => { - await isOnline().then(res => { - var hideNoConnection = ipc.sendSync('getConfig').hideNoConnectionDialog - if ( !res && !hideNoConnection ) { - Ext.get('spinner') ? Ext.get('spinner').destroy() : null; - Ext.get('background') ? Ext.get('background').destroy() : null; - Ext.Msg.show({ - title: 'No Internet Connection' - ,msg: 'Please, check your internet connection. If you use a Proxy, please go to Preferences to configure it. Rambox will try to re-connect in 10 seconds' - ,width: 300 - ,closable: false - ,buttons: Ext.Msg.YESNO - ,buttonText: { - yes: 'Ok' - ,no: 'Never show this again' - } - ,multiline: false - ,fn: function(buttonValue, inputText, showConfig) { - if ( buttonValue === 'no' ) { - ipc.send('sConfig', { hideNoConnectionDialog: true }); - hideNoConnection = true; - } - } - ,icon: Ext.Msg.QUESTION - }); - setTimeout(function() { - if ( !hideNoConnection ) ipc.send('reloadApp') - }, 10000) - } - }) - })(); + if ( + !localStorage.getItem("hideMacPermissions") && + process.platform === "darwin" && + (require("electron").remote.systemPreferences.getMediaAccessStatus( + "microphone" + ) !== "granted" || + require("electron").remote.systemPreferences.getMediaAccessStatus( + "camera" + ) !== "granted") + ) { + console.info("Checking mac permissions..."); + Ext.cq1("app-main").addDocked({ + xtype: "toolbar", + dock: "top", + style: { background: "#30BBF3" }, + items: [ + "->", + { + xtype: "label", + html: + "Rambox CE needs permissions to use Microphone and Camera for the apps.", + }, + { + xtype: "button", + text: "Grant permissions", + ui: "decline", + handler: async function (btn) { + await require("electron").remote.systemPreferences.askForMediaAccess( + "microphone" + ); + await require("electron").remote.systemPreferences.askForMediaAccess( + "camera" + ); + Ext.cq1("app-main").removeDocked(btn.up("toolbar"), true); + }, + }, + { + xtype: "button", + text: "Never ask again", + ui: "decline", + handler: function (btn) { + Ext.cq1("app-main").removeDocked(btn.up("toolbar"), true); + localStorage.setItem("hideMacPermissions", true); + }, + }, + "->", + { + glyph: "xf00d@FontAwesome", + baseCls: "", + style: "cursor:pointer;", + handler: function (btn) { + Ext.cq1("app-main").removeDocked(btn.up("toolbar"), true); + }, + }, + ], + }); + } - if ( !localStorage.getItem('hideMacPermissions') && process.platform === 'darwin' && (require('electron').remote.systemPreferences.getMediaAccessStatus('microphone') !== 'granted' || require('electron').remote.systemPreferences.getMediaAccessStatus('camera') !== 'granted') ) { - console.info('Checking mac permissions...'); - Ext.cq1('app-main').addDocked({ - xtype: 'toolbar' - ,dock: 'top' - ,style: {background: '#30BBF3'} - ,items: [ - '->' - ,{ - xtype: 'label' - ,html: 'Rambox CE needs permissions to use Microphone and Camera for the apps.' - } - ,{ - xtype: 'button' - ,text: 'Grant permissions' - ,ui: 'decline' - ,handler: async function(btn) { - await require('electron').remote.systemPreferences.askForMediaAccess('microphone'); - await require('electron').remote.systemPreferences.askForMediaAccess('camera'); - Ext.cq1('app-main').removeDocked(btn.up('toolbar'), true); - } - } - ,{ - xtype: 'button' - ,text: 'Never ask again' - ,ui: 'decline' - ,handler: function(btn) { - Ext.cq1('app-main').removeDocked(btn.up('toolbar'), true); - localStorage.setItem('hideMacPermissions', true); - } - } - ,'->' - ,{ - glyph: 'xf00d@FontAwesome' - ,baseCls: '' - ,style: 'cursor:pointer;' - ,handler: function(btn) { Ext.cq1('app-main').removeDocked(btn.up('toolbar'), true); } - } - ] - }); - } + Ext.getStore("ServicesList").load(function (records, operations, success) { + if (!success) { + Ext.cq1("app-main").addDocked({ + xtype: "toolbar", + dock: "top", + ui: "servicesnotloaded", + style: { background: "#efef6d" }, + items: [ + "->", + { + xtype: "label", + html: + "Services couldn't be loaded, some Rambox features will not be available.", + }, + { + xtype: "button", + text: "Reload", + handler: function () { + ipc.send("reloadApp"); + }, + }, + "->", + { + glyph: "xf00d@FontAwesome", + baseCls: "", + style: "cursor:pointer;", + handler: function (btn) { + Ext.cq1("app-main").removeDocked(btn.up("toolbar"), true); + }, + }, + ], + }); + } + // Prevent track if the user have disabled this option (default: false) + if (!ipc.sendSync("sendStatistics")) { + ga_storage = { + _enableSSL: Ext.emptyFn, + _disableSSL: Ext.emptyFn, + _setAccount: Ext.emptyFn, + _setDomain: Ext.emptyFn, + _setLocale: Ext.emptyFn, + _setCustomVar: Ext.emptyFn, + _deleteCustomVar: Ext.emptyFn, + _trackPageview: Ext.emptyFn, + _trackEvent: Ext.emptyFn, + }; + } + // Set Google Analytics events + ga_storage._setAccount("UA-80680424-1"); + ga_storage._trackPageview("/index.html", "main"); + ga_storage._trackEvent( + "Versions", + require("electron").remote.app.getVersion() + ); - Ext.getStore('ServicesList').load(function (records, operations, success) { + // Load language for Ext JS library + Ext.Loader.loadScript({ + url: Ext.util.Format.format( + "ext/packages/ext-locale/build/ext-locale-{0}.js", + localStorage.getItem("locale-auth0") || "en" + ), + }); - if (!success) { - Ext.cq1('app-main').addDocked({ - xtype: 'toolbar' - ,dock: 'top' - ,ui: 'servicesnotloaded' - ,style: { background: '#efef6d' } - ,items: [ - '->' - ,{ - xtype: 'label' - ,html: 'Services couldn\'t be loaded, some Rambox features will not be available.' - } - ,{ - xtype: 'button' - ,text: 'Reload' - ,handler: function() { ipc.send('reloadApp'); } - } - ,'->' - ,{ - glyph: 'xf00d@FontAwesome' - ,baseCls: '' - ,style: 'cursor:pointer;' - ,handler: function(btn) { Ext.cq1('app-main').removeDocked(btn.up('toolbar'), true); } - } - ] - }); - } - // Prevent track if the user have disabled this option (default: false) - if ( !ipc.sendSync('sendStatistics') ) { - ga_storage = { - _enableSSL: Ext.emptyFn - ,_disableSSL: Ext.emptyFn - ,_setAccount: Ext.emptyFn - ,_setDomain: Ext.emptyFn - ,_setLocale: Ext.emptyFn - ,_setCustomVar: Ext.emptyFn - ,_deleteCustomVar: Ext.emptyFn - ,_trackPageview: Ext.emptyFn - ,_trackEvent: Ext.emptyFn - } - } + // Initialize Auth0 + if (auth0Cfg.clientID !== "" && auth0Cfg.domain !== "") + Rambox.ux.Auth0.init(); - // Set Google Analytics events - ga_storage._setAccount('UA-80680424-1'); - ga_storage._trackPageview('/index.html', 'main'); - ga_storage._trackEvent('Versions', require('electron').remote.app.getVersion()); + // Set cookies to help Tooltip.io messages segmentation + Ext.util.Cookies.set( + "version", + require("electron").remote.app.getVersion() + ); + if (Ext.util.Cookies.get("auth0") === null) + Ext.util.Cookies.set("auth0", false); - // Load language for Ext JS library - Ext.Loader.loadScript({url: Ext.util.Format.format("ext/packages/ext-locale/build/ext-locale-{0}.js", localStorage.getItem('locale-auth0') || 'en')}); + // Check for updates + if ( + require("electron").remote.process.argv.indexOf("--without-update") === + -1 + ) + Rambox.app.checkUpdate(true); - // Initialize Auth0 - if ( auth0Cfg.clientID !== '' && auth0Cfg.domain !== '' ) Rambox.ux.Auth0.init(); + // Get Google URLs + Ext.Ajax.request({ + url: + "https://raw.githubusercontent.com/ramboxapp/community-edition/gh-pages/api/google.json", + method: "GET", + success: function (response) { + Rambox.app.config.googleURLs = Ext.decode(response.responseText); + }, + }); - // Set cookies to help Tooltip.io messages segmentation - Ext.util.Cookies.set('version', require('electron').remote.app.getVersion()); - if ( Ext.util.Cookies.get('auth0') === null ) Ext.util.Cookies.set('auth0', false); + // Shortcuts + const platform = require("electron").remote.process.platform; + // Prevents default behaviour of Mousetrap, that prevents shortcuts in textareas + Mousetrap.prototype.stopCallback = function (e, element, combo) { + return false; + }; + // Add shortcuts to switch services using CTRL + Number + Mousetrap.bind( + platform === "darwin" + ? [ + "command+1", + "command+2", + "command+3", + "command+4", + "command+5", + "command+6", + "command+7", + "command+8", + "command+9", + ] + : [ + "ctrl+1", + "ctrl+2", + "ctrl+3", + "ctrl+4", + "ctrl+5", + "ctrl+6", + "ctrl+7", + "ctrl+8", + "ctrl+9", + ], + function (e, combo) { + // GROUPS + var tabPanel = Ext.cq1("app-main"); + var arg = parseInt(e.key); + if (arg >= tabPanel.items.indexOf(Ext.getCmp("tbfill"))) arg++; + tabPanel.setActiveTab(arg); + } + ); + // Add shortcut to main tab (ctrl+,) + Mousetrap.bind( + platform === "darwin" ? "command+," : "ctrl+,", + (e, combo) => { + Ext.cq1("app-main").setActiveTab(0); + } + ); + // Add shortcuts to navigate through services + Mousetrap.bind(["ctrl+tab", "ctrl+pagedown"], (e, combo) => { + var tabPanel = Ext.cq1("app-main"); + var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab()); + var i = activeIndex + 1; + // "cycle" (go to the start) when the end is reached or the end is the spacer "tbfill" + if ( + i === tabPanel.items.items.length || + (i === tabPanel.items.items.length - 1 && + tabPanel.items.items[i].id === "tbfill") + ) + i = 0; + // skip spacer + while (tabPanel.items.items[i].id === "tbfill") i++; + tabPanel.setActiveTab(i); + }); + Mousetrap.bind(["ctrl+shift+tab", "ctrl+pageup"], (e, combo) => { + var tabPanel = Ext.cq1("app-main"); + var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab()); + var i = activeIndex - 1; + if (i < 0) i = tabPanel.items.items.length - 1; + while (tabPanel.items.items[i].id === "tbfill" || i < 0) i--; + tabPanel.setActiveTab(i); + }); + // Add shortcut to search inside a service + Mousetrap.bind( + process.platform === "darwin" ? ["command+alt+f"] : ["shift+alt+f"], + (e, combo) => { + var currentTab = Ext.cq1("app-main").getActiveTab(); + if (currentTab.getWebView) currentTab.showSearchBox(true); + } + ); + // Add shortcut to Do Not Disturb + Mousetrap.bind( + platform === "darwin" ? ["command+alt+d"] : ["shift+alt+d"], + function (e, combo) { + var btn = Ext.getCmp("disturbBtn"); + btn.toggle(); + Ext.cq1("app-main").getController().dontDisturb(btn, true); + } + ); + // Add shortcut to Lock Rambox + Mousetrap.bind( + platform === "darwin" ? ["command+alt+l"] : ["shift+alt+l"], + (e, combo) => { + var btn = Ext.getCmp("lockRamboxBtn"); + Ext.cq1("app-main").getController().lockRambox(btn); + } + ); - // Check for updates - if ( require('electron').remote.process.argv.indexOf('--without-update') === -1 ) Rambox.app.checkUpdate(true); + // Mouse Wheel zooming + document.addEventListener("mousewheel", function (e) { + if (e.ctrlKey) { + var delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail)); - // Get Google URLs - Ext.Ajax.request({ - url: 'https://raw.githubusercontent.com/ramboxapp/community-edition/gh-pages/api/google.json' - ,method: 'GET' - ,success: function(response) { - Rambox.app.config.googleURLs = Ext.decode(response.responseText); - } - }); + var tabPanel = Ext.cq1("app-main"); + if (tabPanel.items.indexOf(tabPanel.getActiveTab()) === 0) + return false; - // Shortcuts - const platform = require('electron').remote.process.platform; - // Prevents default behaviour of Mousetrap, that prevents shortcuts in textareas - Mousetrap.prototype.stopCallback = function(e, element, combo) { - return false; - }; - // Add shortcuts to switch services using CTRL + Number - Mousetrap.bind(platform === 'darwin' ? ["command+1","command+2","command+3","command+4","command+5","command+6","command+7","command+8","command+9"] : ["ctrl+1","ctrl+2","ctrl+3","ctrl+4","ctrl+5","ctrl+6","ctrl+7","ctrl+8","ctrl+9"], function(e, combo) { // GROUPS - var tabPanel = Ext.cq1('app-main'); - var arg = parseInt(e.key); - if ( arg >= tabPanel.items.indexOf(Ext.getCmp('tbfill')) ) arg++; - tabPanel.setActiveTab(arg); - }); - // Add shortcut to main tab (ctrl+,) - Mousetrap.bind(platform === 'darwin' ? 'command+,' : 'ctrl+,', (e, combo) => { - Ext.cq1('app-main').setActiveTab(0); - }); - // Add shortcuts to navigate through services - Mousetrap.bind(['ctrl+tab', 'ctrl+pagedown'], (e, combo) => { - var tabPanel = Ext.cq1('app-main'); - var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab()); - var i = activeIndex + 1; - // "cycle" (go to the start) when the end is reached or the end is the spacer "tbfill" - if (i === tabPanel.items.items.length || i === tabPanel.items.items.length - 1 && tabPanel.items.items[i].id === 'tbfill') i = 0; - // skip spacer - while (tabPanel.items.items[i].id === 'tbfill') i++; - tabPanel.setActiveTab(i); - }); - Mousetrap.bind(['ctrl+shift+tab', 'ctrl+pageup'], (e, combo) => { - var tabPanel = Ext.cq1('app-main'); - var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab()); - var i = activeIndex - 1; - if ( i < 0 ) i = tabPanel.items.items.length - 1; - while ( tabPanel.items.items[i].id === 'tbfill' || i < 0 ) i--; - tabPanel.setActiveTab(i); - }); - // Add shortcut to search inside a service - Mousetrap.bind(process.platform === 'darwin' ? ['command+alt+f'] : ['shift+alt+f'], (e, combo) => { - var currentTab = Ext.cq1('app-main').getActiveTab(); - if ( currentTab.getWebView ) currentTab.showSearchBox(true); - }); - // Add shortcut to Do Not Disturb - Mousetrap.bind(platform === 'darwin' ? ["command+alt+d"] : ["shift+alt+d"], function(e, combo) { - var btn = Ext.getCmp('disturbBtn'); - btn.toggle(); - Ext.cq1('app-main').getController().dontDisturb(btn, true); - }); - // Add shortcut to Lock Rambox - Mousetrap.bind(platform === 'darwin' ? ['command+alt+l'] : ['shift+alt+l'], (e, combo) => { - var btn = Ext.getCmp('lockRamboxBtn'); - Ext.cq1('app-main').getController().lockRambox(btn); - }); + if (delta === 1) { + // Zoom In + tabPanel.getActiveTab().zoomIn(); + } else { + // Zoom Out + tabPanel.getActiveTab().zoomOut(); + } + } + }); - // Mouse Wheel zooming - document.addEventListener('mousewheel', function(e) { - if( e.ctrlKey ) { - var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))); + // Define default value + if (localStorage.getItem("dontDisturb") === null) + localStorage.setItem("dontDisturb", false); + ipc.send("setDontDisturb", localStorage.getItem("dontDisturb")); // We store it in config - var tabPanel = Ext.cq1('app-main'); - if ( tabPanel.items.indexOf(tabPanel.getActiveTab()) === 0 ) return false; + if (localStorage.getItem("locked")) { + console.info("Lock Rambox:", "Enabled"); + Ext.cq1("app-main").getController().showLockWindow(); + } + Ext.getStore("Services").load(); + }); + }, - if ( delta === 1 ) { // Zoom In - tabPanel.getActiveTab().zoomIn(); - } else { // Zoom Out - tabPanel.getActiveTab().zoomOut(); - } - } - }); + updateTotalNotifications: function (newValue, oldValue) { + newValue = parseInt(newValue); + if (newValue > 0) { + if (Ext.cq1("app-main").getActiveTab().record) { + document.title = + "Rambox (" + + Rambox.util.Format.formatNumber(newValue) + + ") - " + + Ext.cq1("app-main").getActiveTab().record.get("name"); + } else { + document.title = + "Rambox (" + Rambox.util.Format.formatNumber(newValue) + ")"; + } + } else { + if (Ext.cq1("app-main") && Ext.cq1("app-main").getActiveTab().record) { + document.title = + "Rambox - " + Ext.cq1("app-main").getActiveTab().record.get("name"); + } else { + document.title = "Rambox"; + } + } + }, - // Define default value - if ( localStorage.getItem('dontDisturb') === null ) localStorage.setItem('dontDisturb', false); - ipc.send('setDontDisturb', localStorage.getItem('dontDisturb')); // We store it in config + checkUpdate: function (silence) { + console.info("Checking for updates..."); + Ext.Ajax.request({ + url: + "https://api.github.com/repos/ramboxapp/community-edition/releases/latest", + method: "GET", + success: function (response) { + var json = Ext.decode(response.responseText); + var appVersion = new Ext.Version( + require("electron").remote.app.getVersion() + ); + if ( + appVersion.isLessThan(json.name) && + !json.draft && + !json.prerelease + ) { + console.info("New version is available", json.version); + Ext.cq1("app-main").addDocked({ + xtype: "toolbar", + dock: "top", + ui: "newversion", + items: [ + "->", + { + xtype: "label", + html: + "" + + locale["app.update[0]"] + + " (" + + json.version + + ")" + + (process.platform === "win32" + ? " is downloading in the background and you will be notified when it is ready to be installed." + : ""), + }, + { + xtype: "button", + text: locale["app.update[1]"], + href: + process.platform === "darwin" + ? "https://getrambox.herokuapp.com/download/" + + process.platform + + "_" + + process.arch + : "https://github.com/ramboxapp/community-edition/releases/latest", + hidden: process.platform === "win32", + }, + { + xtype: "button", + text: locale["app.update[2]"], + ui: "decline", + tooltip: + "Click here to see more information about the new version.", + href: + "https://github.com/ramboxapp/community-edition/releases/tag/" + + json.version, + }, + "->", + { + glyph: "xf00d@FontAwesome", + baseCls: "", + style: "cursor:pointer;", + handler: function (btn) { + Ext.cq1("app-main").removeDocked(btn.up("toolbar"), true); + }, + }, + ], + }); + ipc.send("autoUpdater:check-for-updates"); + return; + } else if (!silence) { + Ext.Msg.show({ + title: locale["app.update[3]"], + message: locale["app.update[4]"], + icon: Ext.Msg.INFO, + buttons: Ext.Msg.OK, + }); + } - if ( localStorage.getItem('locked') ) { - console.info('Lock Rambox:', 'Enabled'); - Ext.cq1('app-main').getController().showLockWindow(); - } - Ext.getStore('Services').load(); - }); - } - - ,updateTotalNotifications: function( newValue, oldValue ) { - newValue = parseInt(newValue); - if ( newValue > 0 ) { - if ( Ext.cq1('app-main').getActiveTab().record ) { - document.title = 'Rambox (' + Rambox.util.Format.formatNumber(newValue) + ') - '+Ext.cq1('app-main').getActiveTab().record.get('name'); - } else { - document.title = 'Rambox (' + Rambox.util.Format.formatNumber(newValue) + ')'; - } - } else { - if ( Ext.cq1('app-main') && Ext.cq1('app-main').getActiveTab().record ) { - document.title = 'Rambox - '+Ext.cq1('app-main').getActiveTab().record.get('name'); - } else { - document.title = 'Rambox'; - } - } - } - - ,checkUpdate: function(silence) { - console.info('Checking for updates...'); - Ext.Ajax.request({ - url: 'https://api.github.com/repos/ramboxapp/community-edition/releases/latest' - ,method: 'GET' - ,success: function(response) { - var json = Ext.decode(response.responseText); - var appVersion = new Ext.Version(require('electron').remote.app.getVersion()); - if ( appVersion.isLessThan(json.name) && !json.draft && !json.prerelease ) { - console.info('New version is available', json.version); - Ext.cq1('app-main').addDocked({ - xtype: 'toolbar' - ,dock: 'top' - ,ui: 'newversion' - ,items: [ - '->' - ,{ - xtype: 'label' - ,html: ''+locale['app.update[0]']+' ('+json.version+')' + ( process.platform === 'win32' ? ' is downloading in the background and you will be notified when it is ready to be installed.' : '' ) - } - ,{ - xtype: 'button' - ,text: locale['app.update[1]'] - ,href: process.platform === 'darwin' ? 'https://getrambox.herokuapp.com/download/'+process.platform+'_'+process.arch : 'https://github.com/ramboxapp/community-edition/releases/latest' - ,hidden: process.platform === 'win32' - } - ,{ - xtype: 'button' - ,text: locale['app.update[2]'] - ,ui: 'decline' - ,tooltip: 'Click here to see more information about the new version.' - ,href: 'https://github.com/ramboxapp/community-edition/releases/tag/'+json.version - } - ,'->' - ,{ - glyph: 'xf00d@FontAwesome' - ,baseCls: '' - ,style: 'cursor:pointer;' - ,handler: function(btn) { Ext.cq1('app-main').removeDocked(btn.up('toolbar'), true); } - } - ] - }); - ipc.send('autoUpdater:check-for-updates'); - return; - } else if ( !silence ) { - Ext.Msg.show({ - title: locale['app.update[3]'] - ,message: locale['app.update[4]'] - ,icon: Ext.Msg.INFO - ,buttons: Ext.Msg.OK - }); - } - - console.info('Your version is the latest. No need to update.'); - } - }); - } + console.info("Your version is the latest. No need to update."); + }, + }); + }, }); diff --git a/app/store/ServicesList.js b/app/store/ServicesList.js index a2172525..07ea29a7 100644 --- a/app/store/ServicesList.js +++ b/app/store/ServicesList.js @@ -1,42 +1,43 @@ -Ext.define('Rambox.store.ServicesList', { - extend: 'Ext.data.Store' - ,alias: 'store.serviceslist' +Ext.define("Rambox.store.ServicesList", { + extend: "Ext.data.Store", + alias: "store.serviceslist", - ,requires: [ - 'Ext.data.proxy.LocalStorage' - ] + requires: ["Ext.data.proxy.LocalStorage"], - ,model: 'Rambox.model.ServiceList' + model: "Rambox.model.ServiceList", - ,proxy: { - type: 'ajax', - url: 'https://raw.githubusercontent.com/ramboxapp/community-edition/gh-pages/api/services.json', - reader: { - type: 'json', - rootProperty: 'responseText' - } - } - ,listeners: { - load: function () { - Ext.get('spinner') ? Ext.get('spinner').destroy() : null; - Ext.get('background') ? Ext.get('background').destroy() : null; - this.add({ - id: 'custom' - ,logo: 'custom.png' - ,name: '_Custom Service' - ,description: locale['services[38]'] - ,url: '___' - ,type: 'custom' - ,allow_popups: true - }) - } - } - ,sorters: [{ - property: 'name' - ,direction: 'ASC' - }] + proxy: { + type: "ajax", + url: + "https://raw.githubusercontent.com/ramboxapp/community-edition/gh-pages/api/services.json", + reader: { + type: "json", + rootProperty: "responseText", + }, + }, + listeners: { + load: function () { + Ext.get("spinner") ? Ext.get("spinner").destroy() : null; + Ext.get("background") ? Ext.get("background").destroy() : null; + this.add({ + id: "custom", + logo: "custom.png", + name: "_Custom Service", + description: locale["services[38]"], + url: "___", + type: "custom", + allow_popups: true, + }); + }, + }, + sorters: [ + { + property: "name", + direction: "ASC", + }, + ], - ,autoLoad: true - ,autoSync: true - ,pageSize: 100000 + autoLoad: true, + autoSync: true, + pageSize: 100000, }); diff --git a/app/ux/Auth0.js b/app/ux/Auth0.js index 8f3b6afe..6cb41969 100644 --- a/app/ux/Auth0.js +++ b/app/ux/Auth0.js @@ -1,298 +1,344 @@ -Ext.define('Rambox.ux.Auth0', { - singleton: true - - // private - ,lock: null - ,auth0: null - ,authService: null - ,backupCurrent: false - - ,init: function() { - var me = this; - - var Auth0 = require('auth0-js'); - var _AuthService = require('./resources/js/AuthService'); - - me.authService = new _AuthService.default({ - clientId: auth0Cfg.clientID, - authorizeEndpoint: 'https://'+auth0Cfg.domain+'/authorize', - audience: 'https://'+auth0Cfg.domain+'/userinfo', - scope: 'openid profile offline_access', - redirectUri: 'https://'+auth0Cfg.domain+'/mobile', - tokenEndpoint: 'https://'+auth0Cfg.domain+'/oauth/token' - }); - - me.auth0 = new Auth0.WebAuth({ clientID: auth0Cfg.clientID, domain : auth0Cfg.domain }); - - //me.defineEvents(); - } - - ,onLogin: function(token, authWindow) { - var me = this; - - authWindow.close(); - - me.auth0.client.userInfo(token.access_token, function(err, profile) { - if ( err ) { - if ( err.error === 401 || err.error === 'Unauthorized' ) return me.renewToken(me.checkConfiguration); - Ext.Msg.hide(); - return Ext.Msg.show({ - title: 'Error' - ,message: 'There was an error getting the profile: ' + err.error_description - ,icon: Ext.Msg.ERROR - ,buttons: Ext.Msg.OK - }); - } - - profile.user_metadata = profile['https://rambox.pro/user_metadata']; - delete profile['https://rambox.pro/user_metadata']; - - // Display a spinner while waiting - Ext.Msg.wait(locale['app.window[29]'], locale['app.window[28]']); - - // Google Analytics Event - ga_storage._trackEvent('Users', 'loggedIn'); - - // Set cookies to help Tooltip.io messages segmentation - Ext.util.Cookies.set('auth0', true); - - // User is logged in - // Save the profile and JWT. - localStorage.setItem('profile', JSON.stringify(profile)); - localStorage.setItem('access_token', token.access_token); - localStorage.setItem('id_token', token.id_token); - localStorage.setItem('refresh_token', token.refresh_token); - - if ( !Ext.isEmpty(profile.user_metadata) && !Ext.isEmpty(profile.user_metadata.services) && !me.backupCurrent ) { - Ext.each(profile.user_metadata.services, function(s) { - var service = Ext.create('Rambox.model.Service', s); - service.save(); - Ext.getStore('Services').add(service); - }); - - require('electron').remote.app.relaunch(); - require('electron').remote.app.exit(); - } - - Ext.Msg.hide(); - Ext.cq1('app-main').getViewModel().set('username', profile.name); - Ext.cq1('app-main').getViewModel().set('avatar', profile.picture); - }); - } - - ,backupConfiguration: function(callback) { - var me = this; - - Ext.Msg.wait('Saving backup...', 'Please wait...'); - - // Getting all services - var lastupdate = (new Date()).toJSON(); - var services = []; - Ext.getStore('Services').each(function(service) { - var s = Ext.clone(service); - delete s.data.id; - delete s.data.zoomLevel; - services.push(s.data); - }); - - Ext.Ajax.request({ - url: 'https://rambox.auth0.com/api/v2/users/'+Ext.decode(localStorage.getItem('profile')).sub - ,method: 'PATCH' - ,headers: { authorization: "Bearer " + localStorage.getItem('id_token') } - ,jsonData: { user_metadata: { services: services, services_lastupdate: lastupdate } } - ,success: function(response) { - Ext.Msg.hide(); - // Save the last update in localStorage - var profile = Ext.decode(localStorage.getItem('profile')); - if ( !profile.user_metadata ) profile.user_metadata = {}; - profile.user_metadata.services_lastupdate = lastupdate; - localStorage.setItem('profile', Ext.encode(profile)); - Ext.cq1('app-main').getViewModel().set('last_sync', new Date(lastupdate).toUTCString()); - - Ext.toast({ - html: ' Your configuration were successfully backed up.' - ,title: 'Synchronize Configuration' - ,width: 300 - ,align: 't' - ,closable: false - }); - - if ( Ext.isFunction(callback) ) callback.bind(me)(); - } - ,failure: function(response) { - if ( response.status === 401 ) return me.renewToken(me.backupConfiguration); - - Ext.Msg.hide(); - Ext.toast({ - html: ' Error occurred when trying to backup your configuration.' - ,title: 'Synchronize Configuration' - ,width: 300 - ,align: 't' - ,closable: false - }); - - if ( Ext.isFunction(callback) ) callback.bind(me)(); - - console.error(response); - } - }); - } - - ,restoreConfiguration: function() { - var me = this; - - me.auth0.client.userInfo(localStorage.getItem('access_token'), function(err, profile) { - if ( err ) { - if ( err.code === 401 ) return me.renewToken(me.restoreConfiguration); - return Ext.Msg.show({ - title: 'Error' - ,message: 'There was an error getting the profile: ' + err.description - ,icon: Ext.Msg.ERROR - ,buttons: Ext.Msg.OK - }); - } - - profile.user_metadata = profile['https://rambox.pro/user_metadata']; - delete profile['https://rambox.pro/user_metadata']; - - // First we remove all current services - Ext.cq1('app-main').getController().removeAllServices(false, function() { - if ( !profile.user_metadata || !profile.user_metadata.services ) return; - Ext.each(profile.user_metadata.services, function(s) { - var service = Ext.create('Rambox.model.Service', s); - service.save(); - Ext.getStore('Services').add(service); - }); - - require('electron').remote.getCurrentWindow().reload(); - }); - }); - } - - ,checkConfiguration: function() { - var me = this; - - me.auth0.client.userInfo(localStorage.getItem('access_token'), function(err, profile) { - if ( err ) { - if ( err.code === 401 ) return me.renewToken(me.checkConfiguration); - return Ext.Msg.show({ - title: 'Error' - ,message: 'There was an error getting the profile: ' + err.description - ,icon: Ext.Msg.ERROR - ,buttons: Ext.Msg.OK - }); - } - - profile.user_metadata = profile['https://rambox.pro/user_metadata']; - delete profile['https://rambox.pro/user_metadata']; - - if ( !profile.user_metadata ) { - Ext.toast({ - html: 'You don\'t have any backup yet.' - ,title: 'Synchronize Configuration' - ,width: 300 - ,align: 't' - ,closable: false - }); - return; - } - - if ( Math.floor(new Date(profile.user_metadata.services_lastupdate) / 1000) > Math.floor(new Date(Ext.decode(localStorage.getItem('profile')).user_metadata.services_lastupdate) / 1000) ) { - Ext.toast({ - html: 'Your settings are out of date.' - ,title: 'Synchronize Configuration' - ,width: 300 - ,align: 't' - ,closable: false - }); - } else { - Ext.toast({ - html: ' Latest backup is already applied.' - ,title: 'Synchronize Configuration' - ,width: 300 - ,align: 't' - ,closable: false - }); - } - }); - } - - ,renewToken: function(callback) { - var me = this; - - Ext.Ajax.request({ - url: 'https://rambox.auth0.com/oauth/token' - ,method: 'POST' - ,jsonData: { - grant_type: 'refresh_token' - ,client_id: auth0Cfg.clientID - ,client_secret: auth0Cfg.clientSecret - ,refresh_token: localStorage.getItem('refresh_token') - ,api_type: 'app' - } - ,success: function(response) { - var json = Ext.decode(response.responseText); - localStorage.setItem('access_token', json.access_token); - localStorage.setItem('id_token', json.id_token); - - if ( Ext.isFunction(callback) ) callback.bind(me)(); - } - ,failure: function(response) { - console.error(response); - } - }); - } - - ,login: function() { - var me = this; - - var electron = require('electron').remote; - var authWindow = new electron.BrowserWindow({ - title: 'Rambox - Login' - ,width: 400 - ,height: 600 - ,maximizable: false - ,minimizable: false - ,resizable: true - ,closable: true - ,center: true - ,autoHideMenuBar: true - ,skipTaskbar: true - ,fullscreenable: false - ,parent: require('electron').remote.getCurrentWindow() - ,webPreferences: { - partition: 'persist:rambox' - } - }); - - authWindow.on('closed', function() { - authWindow = null; - }); - - authWindow.loadURL(me.authService.requestAuthCode()); - - authWindow.webContents.on('did-start-loading', function(e) { - authWindow.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { - Rambox.app.config.googleURLs.forEach((loginURL) => { - if ( details.url.indexOf(loginURL) > -1 ) details.requestHeaders['User-Agent'] = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0' }) - callback({ cancel: false, requestHeaders: details.requestHeaders }); - }); - }); - - authWindow.webContents.on('did-navigate', function(e, url) { - me.authService.requestAccessCode(url, me.onLogin.bind(me), authWindow); - }); - } - - ,logout: function() { - var me = this; - - localStorage.removeItem('profile'); - localStorage.removeItem('id_token'); - localStorage.removeItem('refresh_token'); - localStorage.removeItem('access_token'); - - // Set cookies to help Tooltip.io messages segmentation - Ext.util.Cookies.set('auth0', false); - } +Ext.define("Rambox.ux.Auth0", { + singleton: true, + + // private + lock: null, + auth0: null, + authService: null, + backupCurrent: false, + + init: function () { + var me = this; + + var Auth0 = require("auth0-js"); + var _AuthService = require("./resources/js/AuthService"); + + me.authService = new _AuthService.default({ + clientId: auth0Cfg.clientID, + authorizeEndpoint: "https://" + auth0Cfg.domain + "/authorize", + audience: "https://" + auth0Cfg.domain + "/userinfo", + scope: "openid profile offline_access", + redirectUri: "https://" + auth0Cfg.domain + "/mobile", + tokenEndpoint: "https://" + auth0Cfg.domain + "/oauth/token", + }); + + me.auth0 = new Auth0.WebAuth({ + clientID: auth0Cfg.clientID, + domain: auth0Cfg.domain, + }); + + //me.defineEvents(); + }, + + onLogin: function (token, authWindow) { + var me = this; + + authWindow.close(); + + me.auth0.client.userInfo(token.access_token, function (err, profile) { + if (err) { + if (err.error === 401 || err.error === "Unauthorized") + return me.renewToken(me.checkConfiguration); + Ext.Msg.hide(); + return Ext.Msg.show({ + title: "Error", + message: + "There was an error getting the profile: " + err.error_description, + icon: Ext.Msg.ERROR, + buttons: Ext.Msg.OK, + }); + } + + profile.user_metadata = profile["https://rambox.pro/user_metadata"]; + delete profile["https://rambox.pro/user_metadata"]; + + // Display a spinner while waiting + Ext.Msg.wait(locale["app.window[29]"], locale["app.window[28]"]); + + // Google Analytics Event + ga_storage._trackEvent("Users", "loggedIn"); + + // Set cookies to help Tooltip.io messages segmentation + Ext.util.Cookies.set("auth0", true); + + // User is logged in + // Save the profile and JWT. + localStorage.setItem("profile", JSON.stringify(profile)); + localStorage.setItem("access_token", token.access_token); + localStorage.setItem("id_token", token.id_token); + localStorage.setItem("refresh_token", token.refresh_token); + + if ( + !Ext.isEmpty(profile.user_metadata) && + !Ext.isEmpty(profile.user_metadata.services) && + !me.backupCurrent + ) { + Ext.each(profile.user_metadata.services, function (s) { + var service = Ext.create("Rambox.model.Service", s); + service.save(); + Ext.getStore("Services").add(service); + }); + + require("electron").remote.app.relaunch(); + require("electron").remote.app.exit(); + } + + Ext.Msg.hide(); + Ext.cq1("app-main").getViewModel().set("username", profile.name); + Ext.cq1("app-main").getViewModel().set("avatar", profile.picture); + }); + }, + + backupConfiguration: function (callback) { + var me = this; + + Ext.Msg.wait("Saving backup...", "Please wait..."); + + // Getting all services + var lastupdate = new Date().toJSON(); + var services = []; + Ext.getStore("Services").each(function (service) { + var s = Ext.clone(service); + delete s.data.id; + delete s.data.zoomLevel; + services.push(s.data); + }); + + Ext.Ajax.request({ + url: + "https://rambox.auth0.com/api/v2/users/" + + Ext.decode(localStorage.getItem("profile")).sub, + method: "PATCH", + headers: { authorization: "Bearer " + localStorage.getItem("id_token") }, + jsonData: { + user_metadata: { services: services, services_lastupdate: lastupdate }, + }, + success: function (response) { + Ext.Msg.hide(); + // Save the last update in localStorage + var profile = Ext.decode(localStorage.getItem("profile")); + if (!profile.user_metadata) profile.user_metadata = {}; + profile.user_metadata.services_lastupdate = lastupdate; + localStorage.setItem("profile", Ext.encode(profile)); + Ext.cq1("app-main") + .getViewModel() + .set("last_sync", new Date(lastupdate).toUTCString()); + + Ext.toast({ + html: + ' Your configuration were successfully backed up.', + title: "Synchronize Configuration", + width: 300, + align: "t", + closable: false, + }); + + if (Ext.isFunction(callback)) callback.bind(me)(); + }, + failure: function (response) { + if (response.status === 401) + return me.renewToken(me.backupConfiguration); + + Ext.Msg.hide(); + Ext.toast({ + html: + ' Error occurred when trying to backup your configuration.', + title: "Synchronize Configuration", + width: 300, + align: "t", + closable: false, + }); + + if (Ext.isFunction(callback)) callback.bind(me)(); + + console.error(response); + }, + }); + }, + + restoreConfiguration: function () { + var me = this; + + me.auth0.client.userInfo( + localStorage.getItem("access_token"), + function (err, profile) { + if (err) { + if (err.code === 401) return me.renewToken(me.restoreConfiguration); + return Ext.Msg.show({ + title: "Error", + message: + "There was an error getting the profile: " + err.description, + icon: Ext.Msg.ERROR, + buttons: Ext.Msg.OK, + }); + } + + profile.user_metadata = profile["https://rambox.pro/user_metadata"]; + delete profile["https://rambox.pro/user_metadata"]; + + // First we remove all current services + Ext.cq1("app-main") + .getController() + .removeAllServices(false, function () { + if (!profile.user_metadata || !profile.user_metadata.services) + return; + Ext.each(profile.user_metadata.services, function (s) { + var service = Ext.create("Rambox.model.Service", s); + service.save(); + Ext.getStore("Services").add(service); + }); + + require("electron").remote.getCurrentWindow().reload(); + }); + } + ); + }, + + checkConfiguration: function () { + var me = this; + + me.auth0.client.userInfo( + localStorage.getItem("access_token"), + function (err, profile) { + if (err) { + if (err.code === 401) return me.renewToken(me.checkConfiguration); + return Ext.Msg.show({ + title: "Error", + message: + "There was an error getting the profile: " + err.description, + icon: Ext.Msg.ERROR, + buttons: Ext.Msg.OK, + }); + } + + profile.user_metadata = profile["https://rambox.pro/user_metadata"]; + delete profile["https://rambox.pro/user_metadata"]; + + if (!profile.user_metadata) { + Ext.toast({ + html: "You don't have any backup yet.", + title: "Synchronize Configuration", + width: 300, + align: "t", + closable: false, + }); + return; + } + + if ( + Math.floor( + new Date(profile.user_metadata.services_lastupdate) / 1000 + ) > + Math.floor( + new Date( + Ext.decode( + localStorage.getItem("profile") + ).user_metadata.services_lastupdate + ) / 1000 + ) + ) { + Ext.toast({ + html: "Your settings are out of date.", + title: "Synchronize Configuration", + width: 300, + align: "t", + closable: false, + }); + } else { + Ext.toast({ + html: + ' Latest backup is already applied.', + title: "Synchronize Configuration", + width: 300, + align: "t", + closable: false, + }); + } + } + ); + }, + + renewToken: function (callback) { + var me = this; + + Ext.Ajax.request({ + url: "https://rambox.auth0.com/oauth/token", + method: "POST", + jsonData: { + grant_type: "refresh_token", + client_id: auth0Cfg.clientID, + client_secret: auth0Cfg.clientSecret, + refresh_token: localStorage.getItem("refresh_token"), + api_type: "app", + }, + success: function (response) { + var json = Ext.decode(response.responseText); + localStorage.setItem("access_token", json.access_token); + localStorage.setItem("id_token", json.id_token); + + if (Ext.isFunction(callback)) callback.bind(me)(); + }, + failure: function (response) { + console.error(response); + }, + }); + }, + + login: function () { + var me = this; + + var electron = require("electron").remote; + var authWindow = new electron.BrowserWindow({ + title: "Rambox - Login", + width: 400, + height: 600, + maximizable: false, + minimizable: false, + resizable: true, + closable: true, + center: true, + autoHideMenuBar: true, + skipTaskbar: true, + fullscreenable: false, + parent: require("electron").remote.getCurrentWindow(), + webPreferences: { + partition: "persist:rambox", + }, + }); + + authWindow.on("closed", function () { + authWindow = null; + }); + + authWindow.loadURL(me.authService.requestAuthCode()); + + authWindow.webContents.on("did-start-loading", function (e) { + authWindow.webContents.session.webRequest.onBeforeSendHeaders( + (details, callback) => { + Rambox.app.config.googleURLs.forEach((loginURL) => { + if (details.url.indexOf(loginURL) > -1) + details.requestHeaders["User-Agent"] = + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0"; + }); + callback({ cancel: false, requestHeaders: details.requestHeaders }); + } + ); + }); + + authWindow.webContents.on("did-navigate", function (e, url) { + me.authService.requestAccessCode(url, me.onLogin.bind(me), authWindow); + }); + }, + + logout: function () { + var me = this; + + localStorage.removeItem("profile"); + localStorage.removeItem("id_token"); + localStorage.removeItem("refresh_token"); + localStorage.removeItem("access_token"); + + // Set cookies to help Tooltip.io messages segmentation + Ext.util.Cookies.set("auth0", false); + }, }); diff --git a/app/ux/WebView.js b/app/ux/WebView.js index db92fbe9..44110495 100644 --- a/app/ux/WebView.js +++ b/app/ux/WebView.js @@ -2,807 +2,978 @@ * Default config for all webviews created */ -Ext.define('Rambox.ux.WebView',{ - extend: 'Ext.panel.Panel' - ,xtype: 'webview' - - ,requires: [ - 'Rambox.util.Format' - ,'Rambox.util.Notifier' - ,'Rambox.util.UnreadCounter' - ,'Rambox.util.IconLoader' - ] - - // private - ,zoomLevel: 0 - ,currentUnreadCount: 0 - - // CONFIG - ,hideMode: 'offsets' - ,initComponent: function(config) { - var me = this; - - function getLocation(href) { - var match = href.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)(\/[^?#]*)(\?[^#]*|)(#.*|)$/); - return match && { - protocol: match[1], - host: match[2], - hostname: match[3], - port: match[4], - pathname: match[5], - search: match[6], - hash: match[7] - } - } - - const prefConfig = ipc.sendSync('getConfig'); - Ext.apply(me, { - items: me.webViewConstructor() - ,title: prefConfig.hide_tabbar_labels ? '' : (me.record.get('tabname') ? me.record.get('name') : '') - ,icon: me.record.get('type') === 'custom' ? (me.record.get('logo') === '' ? 'resources/icons/custom.png' : me.record.get('logo')) : 'https://firebasestorage.googleapis.com/v0/b/rambox-d1326.appspot.com/o/services%2F'+me.record.get('logo')+'?alt=media' - ,src: me.record.get('url') - ,type: me.record.get('type') - ,align: me.record.get('align') - ,notifications: me.record.get('notifications') - ,muted: me.record.get('muted') - ,tabConfig: { - listeners: { - afterrender : function( btn ) { - btn.el.on('contextmenu', function(e) { - btn.showMenu('contextmenu'); - e.stopEvent(); - }); - } - ,scope: me - } - ,clickEvent: '' - ,style: !me.record.get('enabled') ? '-webkit-filter: grayscale(1)' : '' - ,menu: { - plain: true - ,items: [ - { - xtype: 'toolbar' - ,items: [ - { - xtype: 'segmentedbutton' - ,allowToggle: false - ,flex: 1 - ,items: [ - { - text: 'Back' - ,glyph: 'xf053@FontAwesome' - ,flex: 1 - ,scope: me - ,handler: me.goBack - } - ,{ - text: 'Forward' - ,glyph: 'xf054@FontAwesome' - ,iconAlign: 'right' - ,flex: 1 - ,scope: me - ,handler: me.goForward - } - ] - } - ] - } - ,'-' - ,{ - text: 'Zoom In' - ,glyph: 'xf00e@FontAwesome' - ,scope: me - ,handler: me.zoomIn - } - ,{ - text: 'Zoom Out' - ,glyph: 'xf010@FontAwesome' - ,scope: me - ,handler: me.zoomOut - } - ,{ - text: 'Reset Zoom' - ,glyph: 'xf002@FontAwesome' - ,scope: me - ,handler: me.resetZoom - } - ,'-' - ,{ - text: locale['app.webview[0]'] - ,glyph: 'xf021@FontAwesome' - ,scope: me - ,handler: me.reloadService - } - ,'-' - ,{ - text: locale['app.webview[3]'] - ,glyph: 'xf121@FontAwesome' - ,scope: me - ,handler: me.toggleDevTools - } - ] - } - } - ,tbar: { - itemId: 'searchBar' - ,hidden: true - ,items: ['->', { - xtype: 'textfield' - ,emptyText: 'Search...' - ,listeners: { - scope: me - ,change: me.doSearchText - ,specialkey: function(field, e) { - if ( e.getKey() === e.ENTER ) return me.doSearchText(field, field.getValue(), null, null, true) - if ( e.getKey() === e.ESC ) return me.showSearchBox(false) - } - } - }, { - xtype: 'displayfield' - }, { - xtype: 'segmentedbutton' - ,allowMultiple: false - ,allowToggle: false - ,items: [{ - glyph: 'xf053@FontAwesome' - ,handler: function() { - var field = this.up('toolbar').down('textfield'); - me.doSearchText(field, field.getValue(), null, null, false) - } - }, { - glyph: 'xf054@FontAwesome' - ,handler: function() { - var field = this.up('toolbar').down('textfield'); - me.doSearchText(field, field.getValue(), null, null, true) - } - }] - }, { - xtype: 'button' - ,glyph: 'xf00d@FontAwesome' - ,handler: function() { me.showSearchBox(false) } - }] - } - ,listeners: { - afterrender: me.onAfterRender - ,beforedestroy: me.onBeforeDestroy - } - }); - - if ( me.record.get('statusbar') ) { - Ext.apply(me, { - bbar: me.statusBarConstructor(false) - }); - } else { - me.items.push(me.statusBarConstructor(true)); - } - - me.callParent(config); - } - - ,onBeforeDestroy: function() { - var me = this; - - me.setUnreadCount(0); - } - - ,webViewConstructor: function( enabled ) { - var me = this; - - var cfg; - enabled = enabled || me.record.get('enabled'); - - if ( !enabled ) { - cfg = { - xtype: 'container' - ,html: '

Service Disabled

' - ,style: 'text-align:center;' - ,padding: 100 - }; - } else { - cfg = [{ - xtype: 'component' - ,cls: 'webview' - ,hideMode: 'offsets' - ,autoRender: true - ,autoShow: true - ,autoEl: { - tag: 'webview' - ,src: me.record.get('url') - ,style: 'width:100%;height:100%;visibility:visible;' - ,partition: 'persist:' + me.record.get('type') + '_' + me.id.replace('tab_', '') + (localStorage.getItem('id_token') ? '_' + Ext.decode(localStorage.getItem('profile')).sub : '') - ,plugins: 'true' - ,allowtransparency: 'on' - ,autosize: 'on' - ,webpreferences: 'nativeWindowOpen=yes, spellcheck=no, contextIsolation=no' - ,allowpopups: 'on' - // ,disablewebsecurity: 'on' // Disabled because some services (Like Google Drive) dont work with this enabled - ,useragent: me.getUserAgent() - ,preload: './resources/js/rambox-service-api.js' - } - }]; - } - - return cfg; - } - ,getUserAgent: function() { - var ua = ipc.sendSync('getConfig').user_agent ? ipc.sendSync('getConfig').user_agent : Ext.getStore('ServicesList').getById(this.record.get('type')) ? Ext.getStore('ServicesList').getById(this.record.get('type')).get('userAgent') : '' - return ua.length === 0 ? window.clientInformation.userAgent.replace(/Rambox\/([0-9]\.?)+\s/ig,'').replace(/Electron\/([0-9]\.?)+\s/ig,'') : ua; - } - - ,statusBarConstructor: function(floating) { - var me = this; - - return { - xtype: 'statusbar' - ,id: me.id+'statusbar' - ,hidden: !me.record.get('statusbar') - ,keep: me.record.get('statusbar') - ,y: floating ? '-18px' : 'auto' - ,height: 19 - ,dock: 'bottom' - ,defaultText: ' Ready' - ,busyIconCls : '' - ,busyText: ' '+locale['app.webview[4]'] - ,items: [ - { - xtype: 'tbtext' - ,itemId: 'url' - } - ,{ - xtype: 'button' - ,glyph: 'xf00d@FontAwesome' - ,scale: 'small' - ,ui: 'decline' - ,padding: 0 - ,scope: me - ,hidden: floating - ,handler: me.closeStatusBar - ,tooltip: { - text: 'Close statusbar until next time' - ,mouseOffset: [0,-60] - } - } - ] - }; - } - - ,onAfterRender: function() { - var me = this; - - if ( !me.record.get('enabled') ) return; - - var webview = me.getWebView(); - me.errorCodeLog = [] - - // Google Analytics Event - ga_storage._trackEvent('Services', 'load', me.type, 1, true); - - // Notifications in Webview - me.setNotifications(localStorage.getItem('locked') || JSON.parse(localStorage.getItem('dontDisturb')) ? false : me.record.get('notifications')); - - // Show and hide spinner when is loading - webview.addEventListener("did-start-loading", function() { - console.info('Start loading...', me.src); - - require('electron').remote.webContents.fromId(webview.getWebContentsId()).session.webRequest.onBeforeSendHeaders((details, callback) => { - Rambox.app.config.googleURLs.forEach((loginURL) => { if ( details.url.indexOf(loginURL) > -1 ) details.requestHeaders['User-Agent'] = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0'}); - callback({ cancel: false, requestHeaders: details.requestHeaders }); - }); - - if ( !me.down('statusbar').closed || !me.down('statusbar').keep ) me.down('statusbar').show(); - me.down('statusbar').showBusy(); - }); - webview.addEventListener("did-stop-loading", function() { - me.down('statusbar').clearStatus({useDefaults: true}); - if ( !me.down('statusbar').keep ) me.down('statusbar').hide(); - }); - - webview.addEventListener("did-finish-load", function(e) { - Rambox.app.setTotalServicesLoaded( Rambox.app.getTotalServicesLoaded() + 1 ); - - // Apply saved zoom level - webview.setZoomLevel(me.record.get('zoomLevel')); - - // Fix cursor sometimes dissapear - let currentTab = Ext.cq1('app-main').getActiveTab(); - if ( currentTab.id === me.id ) { - webview.blur(); - webview.focus(); - } - // Set special icon for some service (like Slack) - Rambox.util.IconLoader.loadServiceIconUrl(me, webview); - }); - - // On search text - webview.addEventListener('found-in-page', function(e) { - me.onSearchText(e.result) - }); - - // On search text - webview.addEventListener('did-fail-load', function(e) { - console.info('The service fail at loading', me.src, e); - - if ( me.record.get('disableAutoReloadOnFail') || !e.isMainFrame ) return - me.errorCodeLog.push(e.errorCode) - - var attempt = me.errorCodeLog.filter(function(code) { return code === e.errorCode }); - - // Error codes: https://cs.chromium.org/chromium/src/net/base/net_error_list.h - var msg = [] - msg[-2] = 'NET error: failed.' - msg[-3] = 'An operation was aborted (due to user action)' - msg[-7] = 'Connection timeout.' - msg[-21] = 'Network change.' - msg[-100] = 'The connection was reset. Check your internet connection.' - msg[-101] = 'The connection was reset. Check your internet connection.' - msg[-105] = 'Name not resolved. Check your internet connection.' - msg[-106] = 'There is no active internet connection.' - msg[-118] = 'Connection timed out. Check your internet connection.' - msg[-130] = 'Proxy connection failed. Please, check the proxy configuration.' - msg[-300] = 'The URL is invalid.' - msg[-324] = 'Empty response. Check your internet connection.' - - switch ( e.errorCode ) { - case 0: - break - case -3: // An operation was aborted (due to user action) I think that gmail an other pages that use iframes stop some of them making this error fired - if ( attempt.length <= 4 ) return - setTimeout(() => me.reloadService(me), 200); - me.errorCodeLog = [] - break; - case -2: - case -7: - case -21: - case -118: - case -324: - case -100: - case -101: - case -105: - attempt.length > 4 ? me.onFailLoad(msg[e.errorCode]) : setTimeout(() => me.reloadService(me), 2000); - break; - case -106: - me.onFailLoad(msg[e.errorCode]) - break; - case -130: - // Could not create a connection to the proxy server. An error occurred - // either in resolving its name, or in connecting a socket to it. - // Note that this does NOT include failures during the actual "CONNECT" method - // of an HTTP proxy. - case -300: - attempt.length > 4 ? me.onFailLoad(msg[e.errorCode]) : me.reloadService(me); - break; - } - }); - - // Open links in default browser - webview.addEventListener('new-window', function(e) { - e.preventDefault(); - const protocol = require('url').parse(e.url).protocol; - // Block some Deep links to prevent that open its app (Ex: Slack) - if ( ['slack:'].includes(protocol) ) return; - // Allow Deep links - if ( !['http:', 'https:', 'about:'].includes(protocol) ) return require('electron').shell.openExternal(e.url); - }); - - webview.addEventListener('will-navigate', function(e, url) { - e.preventDefault(); - }); - - let eventsOnDom = false; - webview.addEventListener("dom-ready", function(e) { - // Mute Webview - if ( me.record.get('muted') || localStorage.getItem('locked') || JSON.parse(localStorage.getItem('dontDisturb')) ) me.setAudioMuted(true, true); - - var js_inject = ''; - // Injected code to detect new messages - if ( me.record ) { - var js_unread = Ext.getStore('ServicesList').getById(me.record.get('type')) ? Ext.getStore('ServicesList').getById(me.record.get('type')).get('js_unread') : '' ; - js_unread = js_unread + me.record.get('js_unread'); - if ( js_unread !== '' ) { - console.groupCollapsed(me.record.get('type').toUpperCase() + ' - JS Injected to Detect New Messages'); - console.info(me.type); - console.log(js_unread); - js_inject += js_unread; - } - } - - // Prevent Title blinking (some services have) and only allow when the title have an unread regex match: "(3) Title" - if ( Ext.getStore('ServicesList').getById(me.record.get('type')) ? Ext.getStore('ServicesList').getById(me.record.get('type')).get('titleBlink') : false ) { - var js_preventBlink = 'var originalTitle=document.title;Object.defineProperty(document,"title",{configurable:!0,set:function(a){null===a.match(new RegExp("[(]([0-9•]+)[)][ ](.*)","g"))&&a!==originalTitle||(document.getElementsByTagName("title")[0].innerHTML=a)},get:function(){return document.getElementsByTagName("title")[0].innerHTML}});'; - console.log(js_preventBlink); - js_inject += js_preventBlink; - } - - console.groupEnd(); - - // Scroll always to top (bug) - js_inject += 'document.body.scrollTop=0;'; - - // Handles Certificate Errors - require('electron').remote.webContents.fromId(webview.getWebContentsId()).on('certificate-error', function(event, url, error, certificate, callback) { - if (me.record.get('trust')) { - event.preventDefault(); - callback(true); - } else { - callback(false); - } - - me.down('statusbar').keep = true; - me.down('statusbar').show(); - me.down('statusbar').setStatus({ - text: ' Certification Warning', - }); - me.down('statusbar').down('button').show(); - }); - if (!eventsOnDom) { - require('electron').remote.webContents.fromId(webview.getWebContentsId()).on('before-input-event', (event, input) => { - if (input.type !== 'keyDown') return; - - var modifiers = []; - if (input.shift) modifiers.push('shift'); - if (input.control) modifiers.push('control'); - if (input.alt) modifiers.push('alt'); - if (input.meta) modifiers.push('meta'); - if (input.isAutoRepeat) modifiers.push('isAutoRepeat'); - - if (input.key === 'Tab' && !(modifiers && modifiers.length)) return; - - // Maps special keys to fire the correct event in Mac OS - if (require('electron').remote.process.platform === 'darwin') { - var keys = []; - keys['ƒ'] = 'f'; // Search - keys[' '] = 'l'; // Lock - keys['∂'] = 'd'; // DND - - input.key = keys[input.key] ? keys[input.key] : input.key; - } - - if ( - input.key === 'F11' || - input.key === 'a' || - input.key === 'A' || - input.key === 'F12' || - input.key === 'q' || - (input.key === 'F1' && modifiers.includes('control')) - ) - return; - - require('electron').remote.getCurrentWebContents().sendInputEvent({ - type: input.type, - keyCode: input.key, - modifiers: modifiers, - }); - }); - eventsOnDom = true; - - Rambox.app.config.googleURLs.forEach((loginURL) => { if ( webview.getURL().indexOf(loginURL) > -1 ) webview.reload() }) - } - webview.executeJavaScript(js_inject).then(result => {} ).catch(err => { console.log(err) }) - }); - - webview.addEventListener('ipc-message', function(event) { - var channel = event.channel; - switch (channel) { - case 'rambox.setUnreadCount': - handleSetUnreadCount(event); - break; - case 'rambox.clearUnreadCount': - handleClearUnreadCount(event); - break; - case 'rambox.showWindowAndActivateTab': - showWindowAndActivateTab(event); - break; - } - /** - * Handles 'rambox.clearUnreadCount' messages. - * Clears the unread count. - */ - function handleClearUnreadCount() { - me.tab.setBadgeText(''); - me.currentUnreadCount = 0; - me.setUnreadCount(0); - } - - /** - * Handles 'rambox.setUnreadCount' messages. - * Sets the badge text if the event contains an integer or a '•' (indicating non-zero but unknown number of unreads) as first argument. - * - * @param event - */ - function handleSetUnreadCount(event) { - if (Array.isArray(event.args) === true && event.args.length > 0) { - var count = event.args[0]; - if (count === parseInt(count, 10) || "•" === count) { - if ( count === 999999 ) count = "•"; - me.setUnreadCount(count); - } - } - } - - function showWindowAndActivateTab(event) { - require('electron').remote.getCurrentWindow().show(); - var tabPanel = Ext.cq1('app-main'); - // Temp fix missing cursor after upgrade to electron 3.x + - tabPanel.setActiveTab(me); - tabPanel.getActiveTab().getWebView().blur(); - tabPanel.getActiveTab().getWebView().focus(); - } - }); - - /** - * Register page title update event listener only for services that don't specify a js_unread - */ - if ( Ext.getStore('ServicesList').getById(me.record.get('type')) ? Ext.getStore('ServicesList').getById(me.record.get('type')).get('js_unread') === '' : false && me.record.get('js_unread') === '' ) { - webview.addEventListener("page-title-updated", function(e) { - var count = e.title.match(/\(([^)]+)\)/); // Get text between (...) - count = count ? count[1] : '0'; - count = count === '•' ? count : Ext.isArray(count.match(/\d+/g)) ? count.match(/\d+/g).join("") : count.match(/\d+/g); // Some services have special characters. Example: (•) - count = count === null ? '0' : count; - - me.setUnreadCount(count); - }); - } - - webview.addEventListener('did-navigate', function( e ) { - if ( e.isMainFrame && me.record.get('type') === 'tweetdeck' ) Ext.defer(function() { webview.loadURL(e.newURL); }, 1000); // Applied a defer because sometimes is not redirecting. TweetDeck 2FA is an example. - }); - - webview.addEventListener('update-target-url', function( url ) { - me.down('statusbar #url').setText(url.url); - }); - } - - ,setUnreadCount: function(newUnreadCount) { - var me = this; - - if ( !isNaN(newUnreadCount) && (function(x) { return (x | 0) === x; })(parseFloat(newUnreadCount)) && me.record.get('includeInGlobalUnreadCounter') === true) { - Rambox.util.UnreadCounter.setUnreadCountForService(me.record.get('id'), newUnreadCount); - } else { - Rambox.util.UnreadCounter.clearUnreadCountForService(me.record.get('id')); - } - - me.setTabBadgeText(Rambox.util.Format.formatNumber(newUnreadCount)); - - me.doManualNotification(parseInt(newUnreadCount)); - } - - ,refreshUnreadCount: function() { - this.setUnreadCount(this.currentUnreadCount); - } - - /** - * Dispatch manual notification if - * • service doesn't have notifications, so Rambox does them - * • count increased - * • not in dnd mode - * • notifications enabled - * - * @param {int} count - */ - ,doManualNotification: function(count) { - var me = this; - var manualNotifications = Ext.getStore('ServicesList').getById(me.type) ? Ext.getStore('ServicesList').getById(me.type).get('manual_notifications') : false; - if ( manualNotifications && me.currentUnreadCount < count && me.record.get('notifications') && !JSON.parse(localStorage.getItem('dontDisturb'))) { - Rambox.util.Notifier.dispatchNotification(me, count); - } - - me.currentUnreadCount = count; - } - - /** - * Sets the tab badge text depending on the service config param "displayTabUnreadCounter". - * - * @param {string} badgeText - */ - ,setTabBadgeText: function(badgeText) { - var me = this; - if (me.record.get('displayTabUnreadCounter') === true) { - me.tab.setBadgeText(badgeText); - } else { - me.tab.setBadgeText(''); - } - } - - /** - * Clears the unread counter for this view: - * • clears the badge text - * • clears the global unread counter - */ - ,clearUnreadCounter: function() { - var me = this; - me.tab.setBadgeText(''); - Rambox.util.UnreadCounter.clearUnreadCountForService(me.record.get('id')); - } - - ,reloadService: function(btn) { - var me = this; - var webview = me.getWebView(); - - if ( me.record.get('enabled') ) { - me.clearUnreadCounter(); - webview.loadURL(me.src); - } - } - - ,onFailLoad: function(v) { - let me = this - me.errorCodeLog = [] - setTimeout(() => Ext.getCmp(me.id+'statusbar').setStatus({ text: ' The service failed at loading, Error: '+ v }), 1000); - } - - ,showSearchBox: function(v) { - var me = this; - if ( !me.record.get('enabled') ) return; - var webview = me.getWebView(); - - webview.stopFindInPage('keepSelection'); - if ( v ) { - me.down('#searchBar').show(); - setTimeout(() => { me.down('#searchBar textfield').focus() }, 100) - } else { - me.down('#searchBar').hide(); - me.down('#searchBar textfield').setValue(''); - } - - me.down('#searchBar displayfield').setValue(''); - } - - ,doSearchText: function(field, newValue, oldValue, eOpts, forward = true) { - var me = this; - var webview = me.getWebView(); - - if ( newValue === '' ) { - webview.stopFindInPage('clearSelection'); - me.down('#searchBar displayfield').setValue(''); - return; - } - - webview.findInPage(newValue, { - forward: forward, - findNext: false, - matchCase: false - }) - } - - ,onSearchText: function( result ) { - var me = this; - - me.down('#searchBar displayfield').setValue(result.activeMatchOrdinal+ '/' + result.matches); - } - - ,toggleDevTools: function(btn) { - var me = this; - var webview = me.getWebView(); - - if ( me.record.get('enabled') ) webview.isDevToolsOpened() ? webview.closeDevTools() : webview.openDevTools(); - } - - ,setURL: function(url) { - var me = this; - var webview = me.getWebView(); - - me.src = url; - - if ( me.record.get('enabled') ) webview.loadURL(url); - } - - ,setAudioMuted: function(muted, calledFromDisturb) { - var me = this; - var webview = me.getWebView(); - - me.muted = muted; - - if ( !muted && !calledFromDisturb && JSON.parse(localStorage.getItem('dontDisturb')) ) return; - - if ( me.record.get('enabled') ) webview.setAudioMuted(muted); - } - - ,closeStatusBar: function() { - var me = this; - - me.down('statusbar').hide(); - me.down('statusbar').closed = true; - me.down('statusbar').keep = me.record.get('statusbar'); - } - - ,setStatusBar: function(keep) { - var me = this; - - me.removeDocked(me.down('statusbar'), true); - - if ( keep ) { - me.addDocked(me.statusBarConstructor(false)); - } else { - me.add(me.statusBarConstructor(true)); - } - me.down('statusbar').keep = keep; - } - - ,setNotifications: function(notification, calledFromDisturb) { - var me = this; - var webview = me.getWebView(); - - me.notifications = notification; - - if ( notification && !calledFromDisturb && JSON.parse(localStorage.getItem('dontDisturb')) ) return; - - if ( me.record.get('enabled') ) ipc.send('setServiceNotifications', webview.partition, notification); - } - - ,setEnabled: function(enabled) { - var me = this; - - me.clearUnreadCounter(); - - me.removeAll(); - me.add(me.webViewConstructor(enabled)); - if ( enabled ) { - me.resumeEvent('afterrender'); - me.show(); - me.tab.setStyle('-webkit-filter', 'grayscale(0)'); - me.onAfterRender(); - } else { - me.suspendEvent('afterrender'); - me.tab.setStyle('-webkit-filter', 'grayscale(1)'); - } - } - - ,goBack: function() { - var me = this; - var webview = me.getWebView(); - - if ( me.record.get('enabled') ) webview.goBack(); - } - - ,goForward: function() { - var me = this; - var webview = me.getWebView(); - - if ( me.record.get('enabled') ) webview.goForward(); - } - - ,zoomIn: function() { - if ( this.timeout ) clearTimeout( this.timeout ); - this.timeout = setTimeout(() => { - var me = this; - var webview = me.getWebView(); - me.zoomLevel = me.zoomLevel + 0.25; - if ( me.record.get('enabled') ) { - webview.setZoomLevel(me.zoomLevel); - me.record.set('zoomLevel', me.zoomLevel); - } - }, 100); - } - - ,zoomOut: function() { - if ( this.timeout ) clearTimeout( this.timeout ); - this.timeout = setTimeout(() => { - var me = this; - var webview = me.getWebView(); - me.zoomLevel = me.zoomLevel - 0.25; - if ( me.record.get('enabled') ) { - webview.setZoomLevel(me.zoomLevel); - me.record.set('zoomLevel', me.zoomLevel); - } - }, 100); - } - - ,resetZoom: function() { - var me = this; - var webview = me.getWebView(); - - me.zoomLevel = 0; - if ( me.record.get('enabled') ) { - webview.setZoomLevel(0); - me.record.set('zoomLevel', me.zoomLevel); - } - } - - ,getWebView: function() { - if ( this.record.get('enabled') ) { - return this.down('component[cls=webview]').el.dom; - } else { - return false; - } - } +Ext.define("Rambox.ux.WebView", { + extend: "Ext.panel.Panel", + xtype: "webview", + + requires: [ + "Rambox.util.Format", + "Rambox.util.Notifier", + "Rambox.util.UnreadCounter", + "Rambox.util.IconLoader", + ], + + // private + zoomLevel: 0, + currentUnreadCount: 0, + + // CONFIG + hideMode: "offsets", + initComponent: function (config) { + var me = this; + + function getLocation(href) { + var match = href.match( + /^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)(\/[^?#]*)(\?[^#]*|)(#.*|)$/ + ); + return ( + match && { + protocol: match[1], + host: match[2], + hostname: match[3], + port: match[4], + pathname: match[5], + search: match[6], + hash: match[7], + } + ); + } + + const prefConfig = ipc.sendSync("getConfig"); + Ext.apply(me, { + items: me.webViewConstructor(), + title: prefConfig.hide_tabbar_labels + ? "" + : me.record.get("tabname") + ? me.record.get("name") + : "", + icon: + me.record.get("type") === "custom" + ? me.record.get("logo") === "" + ? "resources/icons/custom.png" + : me.record.get("logo") + : "https://firebasestorage.googleapis.com/v0/b/rambox-d1326.appspot.com/o/services%2F" + + me.record.get("logo") + + "?alt=media", + src: me.record.get("url"), + type: me.record.get("type"), + align: me.record.get("align"), + notifications: me.record.get("notifications"), + muted: me.record.get("muted"), + tabConfig: { + listeners: { + afterrender: function (btn) { + btn.el.on("contextmenu", function (e) { + btn.showMenu("contextmenu"); + e.stopEvent(); + }); + }, + scope: me, + }, + clickEvent: "", + style: !me.record.get("enabled") ? "-webkit-filter: grayscale(1)" : "", + menu: { + plain: true, + items: [ + { + xtype: "toolbar", + items: [ + { + xtype: "segmentedbutton", + allowToggle: false, + flex: 1, + items: [ + { + text: "Back", + glyph: "xf053@FontAwesome", + flex: 1, + scope: me, + handler: me.goBack, + }, + { + text: "Forward", + glyph: "xf054@FontAwesome", + iconAlign: "right", + flex: 1, + scope: me, + handler: me.goForward, + }, + ], + }, + ], + }, + "-", + { + text: "Zoom In", + glyph: "xf00e@FontAwesome", + scope: me, + handler: me.zoomIn, + }, + { + text: "Zoom Out", + glyph: "xf010@FontAwesome", + scope: me, + handler: me.zoomOut, + }, + { + text: "Reset Zoom", + glyph: "xf002@FontAwesome", + scope: me, + handler: me.resetZoom, + }, + "-", + { + text: locale["app.webview[0]"], + glyph: "xf021@FontAwesome", + scope: me, + handler: me.reloadService, + }, + "-", + { + text: locale["app.webview[3]"], + glyph: "xf121@FontAwesome", + scope: me, + handler: me.toggleDevTools, + }, + ], + }, + }, + tbar: { + itemId: "searchBar", + hidden: true, + items: [ + "->", + { + xtype: "textfield", + emptyText: "Search...", + listeners: { + scope: me, + change: me.doSearchText, + specialkey: function (field, e) { + if (e.getKey() === e.ENTER) + return me.doSearchText( + field, + field.getValue(), + null, + null, + true + ); + if (e.getKey() === e.ESC) return me.showSearchBox(false); + }, + }, + }, + { + xtype: "displayfield", + }, + { + xtype: "segmentedbutton", + allowMultiple: false, + allowToggle: false, + items: [ + { + glyph: "xf053@FontAwesome", + handler: function () { + var field = this.up("toolbar").down("textfield"); + me.doSearchText(field, field.getValue(), null, null, false); + }, + }, + { + glyph: "xf054@FontAwesome", + handler: function () { + var field = this.up("toolbar").down("textfield"); + me.doSearchText(field, field.getValue(), null, null, true); + }, + }, + ], + }, + { + xtype: "button", + glyph: "xf00d@FontAwesome", + handler: function () { + me.showSearchBox(false); + }, + }, + ], + }, + listeners: { + afterrender: me.onAfterRender, + beforedestroy: me.onBeforeDestroy, + }, + }); + + if (me.record.get("statusbar")) { + Ext.apply(me, { + bbar: me.statusBarConstructor(false), + }); + } else { + me.items.push(me.statusBarConstructor(true)); + } + + me.callParent(config); + }, + + onBeforeDestroy: function () { + var me = this; + + me.setUnreadCount(0); + }, + + webViewConstructor: function (enabled) { + var me = this; + + var cfg; + enabled = enabled || me.record.get("enabled"); + + if (!enabled) { + cfg = { + xtype: "container", + html: "

Service Disabled

", + style: "text-align:center;", + padding: 100, + }; + } else { + cfg = [ + { + xtype: "component", + cls: "webview", + hideMode: "offsets", + autoRender: true, + autoShow: true, + autoEl: { + tag: "webview", + src: me.record.get("url"), + style: "width:100%;height:100%;visibility:visible;", + partition: + "persist:" + + me.record.get("type") + + "_" + + me.id.replace("tab_", "") + + (localStorage.getItem("id_token") + ? "_" + Ext.decode(localStorage.getItem("profile")).sub + : ""), + plugins: "true", + allowtransparency: "on", + autosize: "on", + webpreferences: + "nativeWindowOpen=yes, spellcheck=no, contextIsolation=no", + allowpopups: "on", + // ,disablewebsecurity: 'on' // Disabled because some services (Like Google Drive) dont work with this enabled + useragent: me.getUserAgent(), + preload: "./resources/js/rambox-service-api.js", + }, + }, + ]; + } + + return cfg; + }, + getUserAgent: function () { + var ua = ipc.sendSync("getConfig").user_agent + ? ipc.sendSync("getConfig").user_agent + : Ext.getStore("ServicesList").getById(this.record.get("type")) + ? Ext.getStore("ServicesList") + .getById(this.record.get("type")) + .get("userAgent") + : ""; + return ua.length === 0 + ? window.clientInformation.userAgent + .replace(/Rambox\/([0-9]\.?)+\s/gi, "") + .replace(/Electron\/([0-9]\.?)+\s/gi, "") + : ua; + }, + + statusBarConstructor: function (floating) { + var me = this; + + return { + xtype: "statusbar", + id: me.id + "statusbar", + hidden: !me.record.get("statusbar"), + keep: me.record.get("statusbar"), + y: floating ? "-18px" : "auto", + height: 19, + dock: "bottom", + defaultText: ' Ready', + busyIconCls: "", + busyText: + ' ' + + locale["app.webview[4]"], + items: [ + { + xtype: "tbtext", + itemId: "url", + }, + { + xtype: "button", + glyph: "xf00d@FontAwesome", + scale: "small", + ui: "decline", + padding: 0, + scope: me, + hidden: floating, + handler: me.closeStatusBar, + tooltip: { + text: "Close statusbar until next time", + mouseOffset: [0, -60], + }, + }, + ], + }; + }, + + onAfterRender: function () { + var me = this; + + if (!me.record.get("enabled")) return; + + var webview = me.getWebView(); + me.errorCodeLog = []; + + // Google Analytics Event + ga_storage._trackEvent("Services", "load", me.type, 1, true); + + // Notifications in Webview + me.setNotifications( + localStorage.getItem("locked") || + JSON.parse(localStorage.getItem("dontDisturb")) + ? false + : me.record.get("notifications") + ); + + // Show and hide spinner when is loading + webview.addEventListener("did-start-loading", function () { + console.info("Start loading...", me.src); + + require("electron") + .remote.webContents.fromId(webview.getWebContentsId()) + .session.webRequest.onBeforeSendHeaders((details, callback) => { + Rambox.app.config.googleURLs.forEach((loginURL) => { + if (details.url.indexOf(loginURL) > -1) + details.requestHeaders["User-Agent"] = + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0"; + }); + callback({ cancel: false, requestHeaders: details.requestHeaders }); + }); + + if (!me.down("statusbar").closed || !me.down("statusbar").keep) + me.down("statusbar").show(); + me.down("statusbar").showBusy(); + }); + webview.addEventListener("did-stop-loading", function () { + me.down("statusbar").clearStatus({ useDefaults: true }); + if (!me.down("statusbar").keep) me.down("statusbar").hide(); + }); + + webview.addEventListener("did-finish-load", function (e) { + Rambox.app.setTotalServicesLoaded( + Rambox.app.getTotalServicesLoaded() + 1 + ); + + // Apply saved zoom level + webview.setZoomLevel(me.record.get("zoomLevel")); + + // Fix cursor sometimes dissapear + let currentTab = Ext.cq1("app-main").getActiveTab(); + if (currentTab.id === me.id) { + webview.blur(); + webview.focus(); + } + // Set special icon for some service (like Slack) + Rambox.util.IconLoader.loadServiceIconUrl(me, webview); + }); + + // On search text + webview.addEventListener("found-in-page", function (e) { + me.onSearchText(e.result); + }); + + // On search text + webview.addEventListener("did-fail-load", function (e) { + console.info("The service fail at loading", me.src, e); + + if (me.record.get("disableAutoReloadOnFail") || !e.isMainFrame) return; + me.errorCodeLog.push(e.errorCode); + + var attempt = me.errorCodeLog.filter(function (code) { + return code === e.errorCode; + }); + + // Error codes: https://cs.chromium.org/chromium/src/net/base/net_error_list.h + var msg = []; + msg[-2] = "NET error: failed."; + msg[-3] = "An operation was aborted (due to user action)"; + msg[-7] = "Connection timeout."; + msg[-21] = "Network change."; + msg[-100] = "The connection was reset. Check your internet connection."; + msg[-101] = "The connection was reset. Check your internet connection."; + msg[-105] = "Name not resolved. Check your internet connection."; + msg[-106] = "There is no active internet connection."; + msg[-118] = "Connection timed out. Check your internet connection."; + msg[-130] = + "Proxy connection failed. Please, check the proxy configuration."; + msg[-300] = "The URL is invalid."; + msg[-324] = "Empty response. Check your internet connection."; + + switch (e.errorCode) { + case 0: + break; + case -3: // An operation was aborted (due to user action) I think that gmail an other pages that use iframes stop some of them making this error fired + if (attempt.length <= 4) return; + setTimeout(() => me.reloadService(me), 200); + me.errorCodeLog = []; + break; + case -2: + case -7: + case -21: + case -118: + case -324: + case -100: + case -101: + case -105: + attempt.length > 4 + ? me.onFailLoad(msg[e.errorCode]) + : setTimeout(() => me.reloadService(me), 2000); + break; + case -106: + me.onFailLoad(msg[e.errorCode]); + break; + case -130: + // Could not create a connection to the proxy server. An error occurred + // either in resolving its name, or in connecting a socket to it. + // Note that this does NOT include failures during the actual "CONNECT" method + // of an HTTP proxy. + case -300: + attempt.length > 4 + ? me.onFailLoad(msg[e.errorCode]) + : me.reloadService(me); + break; + } + }); + + // Open links in default browser + webview.addEventListener("new-window", function (e) { + e.preventDefault(); + const protocol = require("url").parse(e.url).protocol; + // Block some Deep links to prevent that open its app (Ex: Slack) + if (["slack:"].includes(protocol)) return; + // Allow Deep links + if (!["http:", "https:", "about:"].includes(protocol)) + return require("electron").shell.openExternal(e.url); + }); + + webview.addEventListener("will-navigate", function (e, url) { + e.preventDefault(); + }); + + let eventsOnDom = false; + webview.addEventListener("dom-ready", function (e) { + // Mute Webview + if ( + me.record.get("muted") || + localStorage.getItem("locked") || + JSON.parse(localStorage.getItem("dontDisturb")) + ) + me.setAudioMuted(true, true); + + var js_inject = ""; + // Injected code to detect new messages + if (me.record) { + var js_unread = Ext.getStore("ServicesList").getById( + me.record.get("type") + ) + ? Ext.getStore("ServicesList") + .getById(me.record.get("type")) + .get("js_unread") + : ""; + js_unread = js_unread + me.record.get("js_unread"); + if (js_unread !== "") { + console.groupCollapsed( + me.record.get("type").toUpperCase() + + " - JS Injected to Detect New Messages" + ); + console.info(me.type); + console.log(js_unread); + js_inject += js_unread; + } + } + + // Prevent Title blinking (some services have) and only allow when the title have an unread regex match: "(3) Title" + if ( + Ext.getStore("ServicesList").getById(me.record.get("type")) + ? Ext.getStore("ServicesList") + .getById(me.record.get("type")) + .get("titleBlink") + : false + ) { + var js_preventBlink = + 'var originalTitle=document.title;Object.defineProperty(document,"title",{configurable:!0,set:function(a){null===a.match(new RegExp("[(]([0-9•]+)[)][ ](.*)","g"))&&a!==originalTitle||(document.getElementsByTagName("title")[0].innerHTML=a)},get:function(){return document.getElementsByTagName("title")[0].innerHTML}});'; + console.log(js_preventBlink); + js_inject += js_preventBlink; + } + + console.groupEnd(); + + // Scroll always to top (bug) + js_inject += "document.body.scrollTop=0;"; + + // Handles Certificate Errors + require("electron") + .remote.webContents.fromId(webview.getWebContentsId()) + .on( + "certificate-error", + function (event, url, error, certificate, callback) { + if (me.record.get("trust")) { + event.preventDefault(); + callback(true); + } else { + callback(false); + } + + me.down("statusbar").keep = true; + me.down("statusbar").show(); + me.down("statusbar").setStatus({ + text: + ' Certification Warning', + }); + me.down("statusbar").down("button").show(); + } + ); + if (!eventsOnDom) { + require("electron") + .remote.webContents.fromId(webview.getWebContentsId()) + .on("before-input-event", (event, input) => { + if (input.type !== "keyDown") return; + + var modifiers = []; + if (input.shift) modifiers.push("shift"); + if (input.control) modifiers.push("control"); + if (input.alt) modifiers.push("alt"); + if (input.meta) modifiers.push("meta"); + if (input.isAutoRepeat) modifiers.push("isAutoRepeat"); + + if (input.key === "Tab" && !(modifiers && modifiers.length)) return; + + // Maps special keys to fire the correct event in Mac OS + if (require("electron").remote.process.platform === "darwin") { + var keys = []; + keys["ƒ"] = "f"; // Search + keys[" "] = "l"; // Lock + keys["∂"] = "d"; // DND + + input.key = keys[input.key] ? keys[input.key] : input.key; + } + + if ( + input.key === "F11" || + input.key === "a" || + input.key === "A" || + input.key === "F12" || + input.key === "q" || + (input.key === "F1" && modifiers.includes("control")) + ) + return; + + require("electron").remote.getCurrentWebContents().sendInputEvent({ + type: input.type, + keyCode: input.key, + modifiers: modifiers, + }); + }); + eventsOnDom = true; + + Rambox.app.config.googleURLs.forEach((loginURL) => { + if (webview.getURL().indexOf(loginURL) > -1) webview.reload(); + }); + } + webview + .executeJavaScript(js_inject) + .then((result) => {}) + .catch((err) => { + console.log(err); + }); + }); + + webview.addEventListener("ipc-message", function (event) { + var channel = event.channel; + switch (channel) { + case "rambox.setUnreadCount": + handleSetUnreadCount(event); + break; + case "rambox.clearUnreadCount": + handleClearUnreadCount(event); + break; + case "rambox.showWindowAndActivateTab": + showWindowAndActivateTab(event); + break; + } + /** + * Handles 'rambox.clearUnreadCount' messages. + * Clears the unread count. + */ + function handleClearUnreadCount() { + me.tab.setBadgeText(""); + me.currentUnreadCount = 0; + me.setUnreadCount(0); + } + + /** + * Handles 'rambox.setUnreadCount' messages. + * Sets the badge text if the event contains an integer or a '•' (indicating non-zero but unknown number of unreads) as first argument. + * + * @param event + */ + function handleSetUnreadCount(event) { + if (Array.isArray(event.args) === true && event.args.length > 0) { + var count = event.args[0]; + if (count === parseInt(count, 10) || "•" === count) { + if (count === 999999) count = "•"; + me.setUnreadCount(count); + } + } + } + + function showWindowAndActivateTab(event) { + require("electron").remote.getCurrentWindow().show(); + var tabPanel = Ext.cq1("app-main"); + // Temp fix missing cursor after upgrade to electron 3.x + + tabPanel.setActiveTab(me); + tabPanel.getActiveTab().getWebView().blur(); + tabPanel.getActiveTab().getWebView().focus(); + } + }); + + /** + * Register page title update event listener only for services that don't specify a js_unread + */ + if ( + Ext.getStore("ServicesList").getById(me.record.get("type")) + ? Ext.getStore("ServicesList") + .getById(me.record.get("type")) + .get("js_unread") === "" + : false && me.record.get("js_unread") === "" + ) { + webview.addEventListener("page-title-updated", function (e) { + var count = e.title.match(/\(([^)]+)\)/); // Get text between (...) + count = count ? count[1] : "0"; + count = + count === "•" + ? count + : Ext.isArray(count.match(/\d+/g)) + ? count.match(/\d+/g).join("") + : count.match(/\d+/g); // Some services have special characters. Example: (•) + count = count === null ? "0" : count; + + me.setUnreadCount(count); + }); + } + + webview.addEventListener("did-navigate", function (e) { + if (e.isMainFrame && me.record.get("type") === "tweetdeck") + Ext.defer(function () { + webview.loadURL(e.newURL); + }, 1000); // Applied a defer because sometimes is not redirecting. TweetDeck 2FA is an example. + }); + + webview.addEventListener("update-target-url", function (url) { + me.down("statusbar #url").setText(url.url); + }); + }, + + setUnreadCount: function (newUnreadCount) { + var me = this; + + if ( + !isNaN(newUnreadCount) && + (function (x) { + return (x | 0) === x; + })(parseFloat(newUnreadCount)) && + me.record.get("includeInGlobalUnreadCounter") === true + ) { + Rambox.util.UnreadCounter.setUnreadCountForService( + me.record.get("id"), + newUnreadCount + ); + } else { + Rambox.util.UnreadCounter.clearUnreadCountForService(me.record.get("id")); + } + + me.setTabBadgeText(Rambox.util.Format.formatNumber(newUnreadCount)); + + me.doManualNotification(parseInt(newUnreadCount)); + }, + + refreshUnreadCount: function () { + this.setUnreadCount(this.currentUnreadCount); + }, + + /** + * Dispatch manual notification if + * • service doesn't have notifications, so Rambox does them + * • count increased + * • not in dnd mode + * • notifications enabled + * + * @param {int} count + */ + doManualNotification: function (count) { + var me = this; + var manualNotifications = Ext.getStore("ServicesList").getById(me.type) + ? Ext.getStore("ServicesList") + .getById(me.type) + .get("manual_notifications") + : false; + if ( + manualNotifications && + me.currentUnreadCount < count && + me.record.get("notifications") && + !JSON.parse(localStorage.getItem("dontDisturb")) + ) { + Rambox.util.Notifier.dispatchNotification(me, count); + } + + me.currentUnreadCount = count; + }, + + /** + * Sets the tab badge text depending on the service config param "displayTabUnreadCounter". + * + * @param {string} badgeText + */ + setTabBadgeText: function (badgeText) { + var me = this; + if (me.record.get("displayTabUnreadCounter") === true) { + me.tab.setBadgeText(badgeText); + } else { + me.tab.setBadgeText(""); + } + }, + + /** + * Clears the unread counter for this view: + * • clears the badge text + * • clears the global unread counter + */ + clearUnreadCounter: function () { + var me = this; + me.tab.setBadgeText(""); + Rambox.util.UnreadCounter.clearUnreadCountForService(me.record.get("id")); + }, + + reloadService: function (btn) { + var me = this; + var webview = me.getWebView(); + + if (me.record.get("enabled")) { + me.clearUnreadCounter(); + webview.loadURL(me.src); + } + }, + + onFailLoad: function (v) { + let me = this; + me.errorCodeLog = []; + setTimeout( + () => + Ext.getCmp(me.id + "statusbar").setStatus({ + text: + ' The service failed at loading, Error: ' + + v, + }), + 1000 + ); + }, + + showSearchBox: function (v) { + var me = this; + if (!me.record.get("enabled")) return; + var webview = me.getWebView(); + + webview.stopFindInPage("keepSelection"); + if (v) { + me.down("#searchBar").show(); + setTimeout(() => { + me.down("#searchBar textfield").focus(); + }, 100); + } else { + me.down("#searchBar").hide(); + me.down("#searchBar textfield").setValue(""); + } + + me.down("#searchBar displayfield").setValue(""); + }, + + doSearchText: function (field, newValue, oldValue, eOpts, forward = true) { + var me = this; + var webview = me.getWebView(); + + if (newValue === "") { + webview.stopFindInPage("clearSelection"); + me.down("#searchBar displayfield").setValue(""); + return; + } + + webview.findInPage(newValue, { + forward: forward, + findNext: false, + matchCase: false, + }); + }, + + onSearchText: function (result) { + var me = this; + + me.down("#searchBar displayfield").setValue( + result.activeMatchOrdinal + "/" + result.matches + ); + }, + + toggleDevTools: function (btn) { + var me = this; + var webview = me.getWebView(); + + if (me.record.get("enabled")) + webview.isDevToolsOpened() + ? webview.closeDevTools() + : webview.openDevTools(); + }, + + setURL: function (url) { + var me = this; + var webview = me.getWebView(); + + me.src = url; + + if (me.record.get("enabled")) webview.loadURL(url); + }, + + setAudioMuted: function (muted, calledFromDisturb) { + var me = this; + var webview = me.getWebView(); + + me.muted = muted; + + if ( + !muted && + !calledFromDisturb && + JSON.parse(localStorage.getItem("dontDisturb")) + ) + return; + + if (me.record.get("enabled")) webview.setAudioMuted(muted); + }, + + closeStatusBar: function () { + var me = this; + + me.down("statusbar").hide(); + me.down("statusbar").closed = true; + me.down("statusbar").keep = me.record.get("statusbar"); + }, + + setStatusBar: function (keep) { + var me = this; + + me.removeDocked(me.down("statusbar"), true); + + if (keep) { + me.addDocked(me.statusBarConstructor(false)); + } else { + me.add(me.statusBarConstructor(true)); + } + me.down("statusbar").keep = keep; + }, + + setNotifications: function (notification, calledFromDisturb) { + var me = this; + var webview = me.getWebView(); + + me.notifications = notification; + + if ( + notification && + !calledFromDisturb && + JSON.parse(localStorage.getItem("dontDisturb")) + ) + return; + + if (me.record.get("enabled")) + ipc.send("setServiceNotifications", webview.partition, notification); + }, + + setEnabled: function (enabled) { + var me = this; + + me.clearUnreadCounter(); + + me.removeAll(); + me.add(me.webViewConstructor(enabled)); + if (enabled) { + me.resumeEvent("afterrender"); + me.show(); + me.tab.setStyle("-webkit-filter", "grayscale(0)"); + me.onAfterRender(); + } else { + me.suspendEvent("afterrender"); + me.tab.setStyle("-webkit-filter", "grayscale(1)"); + } + }, + + goBack: function () { + var me = this; + var webview = me.getWebView(); + + if (me.record.get("enabled")) webview.goBack(); + }, + + goForward: function () { + var me = this; + var webview = me.getWebView(); + + if (me.record.get("enabled")) webview.goForward(); + }, + + zoomIn: function () { + if (this.timeout) clearTimeout(this.timeout); + this.timeout = setTimeout(() => { + var me = this; + var webview = me.getWebView(); + me.zoomLevel = me.zoomLevel + 0.25; + if (me.record.get("enabled")) { + webview.setZoomLevel(me.zoomLevel); + me.record.set("zoomLevel", me.zoomLevel); + } + }, 100); + }, + + zoomOut: function () { + if (this.timeout) clearTimeout(this.timeout); + this.timeout = setTimeout(() => { + var me = this; + var webview = me.getWebView(); + me.zoomLevel = me.zoomLevel - 0.25; + if (me.record.get("enabled")) { + webview.setZoomLevel(me.zoomLevel); + me.record.set("zoomLevel", me.zoomLevel); + } + }, 100); + }, + + resetZoom: function () { + var me = this; + var webview = me.getWebView(); + + me.zoomLevel = 0; + if (me.record.get("enabled")) { + webview.setZoomLevel(0); + me.record.set("zoomLevel", me.zoomLevel); + } + }, + + getWebView: function () { + if (this.record.get("enabled")) { + return this.down("component[cls=webview]").el.dom; + } else { + return false; + } + }, }); diff --git a/app/view/main/MainController.js b/app/view/main/MainController.js index 81d311b7..ec52f3a8 100644 --- a/app/view/main/MainController.js +++ b/app/view/main/MainController.js @@ -1,546 +1,675 @@ -const darkreader = require('darkreader'); - -Ext.define('Rambox.view.main.MainController', { - extend: 'Ext.app.ViewController' - - ,alias: 'controller.main' - - ,initialize: function( tabPanel ) { - const config = ipc.sendSync('getConfig'); - - if (config.darkreader) { - darkreader.enable(); - } else { - darkreader.disable(); - } - tabPanel.setTabPosition(config.tabbar_location); - tabPanel.setTabRotation(0); - - var reorderer = tabPanel.plugins.find(function(plugin) { return plugin.ptype == "tabreorderer"}); - - if ( reorderer !== undefined ) { - const names = reorderer.container.getLayout().names; - reorderer.dd.dim = names.width; - reorderer.dd.startAttr = names.beforeX; - reorderer.dd.endAttr = names.afterX; - } - } - - // Make focus on webview every time the user change tabs, to enable the autofocus in websites - ,onTabChange: function( tabPanel, newTab, oldTab ) { - var me = this; - - // Set Google Analytics event - ga_storage._trackPageview('/index.html', 'main'); - - localStorage.setItem('last_active_service', newTab.id); - - if ( newTab.id === 'ramboxTab' ) { - if ( Rambox.app.getTotalNotifications() > 0 ) { - document.title = 'Rambox ('+ Rambox.app.getTotalNotifications() +')'; - } else { - document.title = 'Rambox'; - } - return; - } - - if (!newTab.record.get('enabled') ) { - return; - } - - var webview = newTab.down('component').el.dom; - - setTimeout(function () { - if ( webview ) { - tabPanel.getActiveTab().getWebView().blur(); - tabPanel.getActiveTab().getWebView().focus(); - } - }, 300); - - // Update the main window so it includes the active tab title. - if ( Rambox.app.getTotalNotifications() > 0 ) { - document.title = 'Rambox ('+ Rambox.app.getTotalNotifications() +') - ' + newTab.record.get('name'); - } else { - document.title = 'Rambox - ' + newTab.record.get('name'); - } - } - - ,updatePositions: function(tabPanel, tab) { - if ( tab.id === 'ramboxTab' || tab.id === 'tbfill' ) return true; - - console.log('Updating Tabs positions...'); - - var store = Ext.getStore('Services'); - var align = 'left'; - store.suspendEvent('remove'); - Ext.each(tabPanel.items.items, function(t, i) { - if ( t.id !== 'ramboxTab' && t.id !== 'tbfill' && t.record.get('enabled') ) { - var rec = store.getById(t.record.get('id')); - if ( align === 'right' ) i--; - rec.set('align', align); - rec.set('position', i); - rec.save(); - } - else if ( t.id === 'tbfill' ) { - align = 'right'; - } - - }); - - store.load(); - store.resumeEvent('remove'); - } - - ,showServiceTab: function( grid, record, tr, rowIndex, e ) { - if ( e.position.colIdx === 0 ) { // Service Logo - Ext.getCmp('tab_'+record.get('id')).show(); - } - } - - ,onRenameService: function(editor, e) { - var me = this; - - e.record.commit(); - - // Change the title of the Tab - Ext.getCmp('tab_'+e.record.get('id')).setTitle(e.record.get('name')); - } - - ,onEnableDisableService: function(cc, rowIndex, checked, obj, hideTab) { - var rec = Ext.getStore('Services').getAt(rowIndex); - - if ( !checked ) { - Ext.getCmp('tab_'+rec.get('id')).destroy(); - } else { - Ext.cq1('app-main').insert(rec.get('align') === 'left' ? rec.get('position') : rec.get('position')+1, { - xtype: 'webview' - ,id: 'tab_'+rec.get('id') - ,title: rec.get('name') - ,icon: rec.get('type') !== 'custom' ? 'resources/icons/'+rec.get('logo') : ( rec.get('logo') === '' ? 'resources/icons/custom.png' : rec.get('logo')) - ,src: rec.get('url') - ,type: rec.get('type') - ,muted: rec.get('muted') - ,includeInGlobalUnreadCounter: rec.get('includeInGlobalUnreadCounter') - ,displayTabUnreadCounter: rec.get('displayTabUnreadCounter') - ,enabled: rec.get('enabled') - ,record: rec - ,useragent: ipc.sendSync('getConfig').user_agent - ,hidden: hideTab - ,tabConfig: { - service: rec - } - }); - } - } - - ,onNewServiceSelect: function( view, record, item, index, e ) { - Ext.create('Rambox.view.add.Add', { - record: record - }); - } - - ,removeServiceFn: function(serviceId, total, actual, callback) { - var me = this; - if ( !serviceId ) return false; - - // Get Record - var rec = Ext.getStore('Services').getById(serviceId); - - if ( !rec.get('enabled') ) { - rec.set('enabled', true); - me.onEnableDisableService(null, Ext.getStore('Services').indexOf(rec), true, null, true); - - // Get Tab - var tab = Ext.getCmp('tab_'+serviceId); - // Clear all trash data - const webview = tab.getWebView(); - webview.addEventListener("did-start-loading", function() { - clearData(webview, tab); - }); - } else { - // Get Tab - var tab = Ext.getCmp('tab_'+serviceId); - // Clear all trash data - const webview = tab.getWebView(); - clearData(webview, tab); - } - - const config = ipc.sendSync('getConfig'); - if ( config.default_service === rec.get('id') ) ipc.send('setConfig', Ext.apply(config, { default_service: 'ramboxTab' })); - - function clearData(webview, tab) { - const currentWebView = require("electron").remote.webContents.fromId( - webview.getWebContentsId() - ); - - currentWebView.clearHistory(); - currentWebView.session.flushStorageData(); - currentWebView.session.clearCache().then(() => { - currentWebView.session.clearStorageData().then(() => { - currentWebView.session.cookies.flushStore().then(() => { - // Remove record from localStorage - Ext.getStore('Services').remove(rec); - // Close tab - tab.close(); - // Close waiting message - if ( total === actual ) { - Ext.Msg.hide(); - if ( Ext.isFunction(callback) ) callback(); - } - }).catch(err => { console.log(err) }) - }).catch(err => { console.log(err) }) - }).catch(err => { console.log(err) }) - } - } - - ,removeService: function( gridView, rowIndex, colIndex, col, e, rec, rowEl ) { - var me = this; - - Ext.Msg.confirm(locale['app.window[12]'], locale['app.window[13]']+' '+rec.get('name')+'?', function(btnId) { - if ( btnId === 'yes' ) { - Ext.Msg.wait('Please wait until we clear all.', 'Removing...'); - me.removeServiceFn(rec.get('id'), 1, 1); - } - }); - } - - ,removeAllServices: function(btn, callback) { - var me = this; - - if ( btn ) { - Ext.Msg.confirm(locale['app.window[12]'], locale['app.window[14]'], function(btnId) { - if ( btnId === 'yes' ) { - // Clear counter for unread messaging - document.title = 'Rambox'; - - Ext.cq1('app-main').suspendEvent('remove'); - Ext.getStore('Services').load(); - Ext.Msg.wait('Please wait until we clear all.', 'Removing...'); - const count = Ext.getStore('Services').getCount(); - var i = 1; - Ext.Array.each(Ext.getStore('Services').collect('id'), function(serviceId) { - me.removeServiceFn(serviceId, count, i++, callback || false); - }); - if ( count === 0 && Ext.isFunction(callback) ) callback(); - Ext.cq1('app-main').resumeEvent('remove'); - } - }); - } else { - Ext.cq1('app-main').suspendEvent('remove'); - Ext.getStore('Services').load(); - const count = Ext.getStore('Services').getCount(); - var i = 1; - Ext.Array.each(Ext.getStore('Services').collect('id'), function(serviceId) { - me.removeServiceFn(serviceId, count, i++, callback || false); - }); - if ( count === 0 && Ext.isFunction(callback) ) callback(); - Ext.cq1('app-main').resumeEvent('remove'); - } - } - - ,configureService: function( gridView, rowIndex, colIndex, col, e, rec, rowEl ) { - Ext.create('Rambox.view.add.Add', { - record: rec - ,service: Ext.getStore('ServicesList').getById(rec.get('type')) - ,edit: true - }); - } - - ,onSearchRender: function( field ) { - field.focus(false, 1000); - } - - ,onSearchEnter: function( field, e ) { - var me = this; - - if ( e.getKey() == e.ENTER && Ext.getStore('ServicesList').getCount() === 2 ) { // Two because we always shows Custom Service option - me.onNewServiceSelect(field.up().down('dataview'), Ext.getStore('ServicesList').getAt(0)); - me.onClearClick(field); - } - } - - ,doTypeFilter: function( cg, newValue, oldValue ) { - var me = this; - - Ext.getStore('ServicesList').getFilters().replaceAll({ - fn: function(record) { - return Ext.Array.contains(Ext.Object.getKeys(cg.getValue()), record.get('type')) || record.get('type') === 'custom'; - } - }); - } - - ,onSearchServiceChange: function(field, newValue, oldValue) { - var me = this; - - var cg = field.up().down('checkboxgroup'); - if ( !Ext.isEmpty(newValue) && newValue.length > 0 ) { - field.getTrigger('clear').show(); - - Ext.getStore('ServicesList').getFilters().replaceAll({ - fn: function(record) { - if ( record.get('type') === 'custom' ) return true; - if ( !Ext.Array.contains(Ext.Object.getKeys(cg.getValue()), record.get('type')) ) return false; - return record.get('name').toLowerCase().indexOf(newValue.toLowerCase()) > -1 ? true : false; - } - }); - } else { - field.getTrigger('clear').hide(); - Ext.getStore('ServicesList').getFilters().removeAll(); - me.doTypeFilter(cg); - } - field.updateLayout(); - } - - ,onClearClick: function(field, trigger, e) { - var me = this; - - var cg = field.up().down('checkboxgroup'); - - field.reset(); - field.getTrigger('clear').hide(); - field.updateLayout(); - - Ext.getStore('ServicesList').getFilters().removeAll(); - me.doTypeFilter(cg); - } - - ,dontDisturb: function(btn, e, called) { - console.info('Dont Disturb:', btn.pressed ? 'Enabled' : 'Disabled'); - - // Google Analytics Event - if ( !called ) ga_storage._trackEvent('Usability', 'dontDisturb', ( btn.pressed ? 'on' : 'off' )); - - Ext.Array.each(Ext.getStore('Services').collect('id'), function(serviceId) { - // Get Tab - var tab = Ext.getCmp('tab_'+serviceId); - - if ( !tab ) return; // Skip disabled services - - // Mute sounds - tab.setAudioMuted(btn.pressed ? true : tab.record.get('muted'), true); - - // Prevent Notifications - tab.setNotifications(btn.pressed ? false : tab.record.get('notifications'), true); - }); - - localStorage.setItem('dontDisturb', btn.pressed); - - ipc.send('setDontDisturb', btn.pressed); - - btn.setText(locale['app.main[16]']+': ' + ( btn.pressed ? locale['app.window[20]'] : locale['app.window[21]'] )); - - // var btn_icon = document.getElementById('disturbBtn-btnIconEl'); - // btn_icon.innerHTML = btn.pressed ? "" : ""; - - btn.pressed ? btn.setGlyph('xf1f7@FontAwesome') : btn.setGlyph('xf0f3@FontAwesome'); - - Ext.getCmp('mainTabBar').getEl().toggleCls('dontdisturb'); - - // If this method is called from Lock method, prevent showing toast - if ( !e ) return; - Ext.toast({ - html: btn.pressed ? 'ENABLED' : 'DISABLED' - ,title: 'Don\'t Disturb' - ,width: 200 - ,align: 't' - ,closable: false - }); - } - - ,lockRambox: function(btn) { - var me = this; - - if ( ipc.sendSync('getConfig').master_password ) { - Ext.Msg.confirm(locale['app.main[19]'], 'Do you want to use the Master Password as your temporal password?', function(btnId) { - if ( btnId === 'yes' ) { - setLock(ipc.sendSync('getConfig').master_password); - } else { - showTempPass(); - } - }); - } else { - showTempPass(); - } - - function showTempPass() { - var msgbox = Ext.Msg.prompt(locale['app.main[19]'], locale['app.window[22]'], function(btnId, text) { - if ( btnId === 'ok' ) { - var msgbox2 = Ext.Msg.prompt(locale['app.main[19]'], locale['app.window[23]'], function(btnId, text2) { - if ( btnId === 'ok' ) { - if ( text !== text2 ) { - Ext.Msg.show({ - title: locale['app.window[24]'] - ,message: locale['app.window[25]'] - ,icon: Ext.Msg.WARNING - ,buttons: Ext.Msg.OK - ,fn: me.lockRambox - }); - return false; - } - - setLock(Rambox.util.MD5.encypt(text)); - } - }); - msgbox2.textField.inputEl.dom.type = 'password'; - } - }); - msgbox.textField.inputEl.dom.type = 'password'; - } - - function setLock(text) { - var ramboxTab = Ext.cq1('#ramboxTab'); - - // Related to issue #2065. Focusing in an sub frame is a workaround - if (ramboxTab.getWebView) { - ramboxTab.down('component').el.dom.executeJavaScript(` +const darkreader = require("darkreader"); + +Ext.define("Rambox.view.main.MainController", { + extend: "Ext.app.ViewController", + + alias: "controller.main", + + initialize: function (tabPanel) { + const config = ipc.sendSync("getConfig"); + + if (config.darkreader) { + darkreader.enable(); + } else { + darkreader.disable(); + } + tabPanel.setTabPosition(config.tabbar_location); + tabPanel.setTabRotation(0); + + var reorderer = tabPanel.plugins.find(function (plugin) { + return plugin.ptype == "tabreorderer"; + }); + + if (reorderer !== undefined) { + const names = reorderer.container.getLayout().names; + reorderer.dd.dim = names.width; + reorderer.dd.startAttr = names.beforeX; + reorderer.dd.endAttr = names.afterX; + } + }, + + // Make focus on webview every time the user change tabs, to enable the autofocus in websites + onTabChange: function (tabPanel, newTab, oldTab) { + var me = this; + + // Set Google Analytics event + ga_storage._trackPageview("/index.html", "main"); + + localStorage.setItem("last_active_service", newTab.id); + + if (newTab.id === "ramboxTab") { + if (Rambox.app.getTotalNotifications() > 0) { + document.title = "Rambox (" + Rambox.app.getTotalNotifications() + ")"; + } else { + document.title = "Rambox"; + } + return; + } + + if (!newTab.record.get("enabled")) { + return; + } + + var webview = newTab.down("component").el.dom; + + setTimeout(function () { + if (webview) { + tabPanel.getActiveTab().getWebView().blur(); + tabPanel.getActiveTab().getWebView().focus(); + } + }, 300); + + // Update the main window so it includes the active tab title. + if (Rambox.app.getTotalNotifications() > 0) { + document.title = + "Rambox (" + + Rambox.app.getTotalNotifications() + + ") - " + + newTab.record.get("name"); + } else { + document.title = "Rambox - " + newTab.record.get("name"); + } + }, + + updatePositions: function (tabPanel, tab) { + if (tab.id === "ramboxTab" || tab.id === "tbfill") return true; + + console.log("Updating Tabs positions..."); + + var store = Ext.getStore("Services"); + var align = "left"; + store.suspendEvent("remove"); + Ext.each(tabPanel.items.items, function (t, i) { + if ( + t.id !== "ramboxTab" && + t.id !== "tbfill" && + t.record.get("enabled") + ) { + var rec = store.getById(t.record.get("id")); + if (align === "right") i--; + rec.set("align", align); + rec.set("position", i); + rec.save(); + } else if (t.id === "tbfill") { + align = "right"; + } + }); + + store.load(); + store.resumeEvent("remove"); + }, + + showServiceTab: function (grid, record, tr, rowIndex, e) { + if (e.position.colIdx === 0) { + // Service Logo + Ext.getCmp("tab_" + record.get("id")).show(); + } + }, + + onRenameService: function (editor, e) { + var me = this; + + e.record.commit(); + + // Change the title of the Tab + Ext.getCmp("tab_" + e.record.get("id")).setTitle(e.record.get("name")); + }, + + onEnableDisableService: function (cc, rowIndex, checked, obj, hideTab) { + var rec = Ext.getStore("Services").getAt(rowIndex); + + if (!checked) { + Ext.getCmp("tab_" + rec.get("id")).destroy(); + } else { + Ext.cq1("app-main").insert( + rec.get("align") === "left" + ? rec.get("position") + : rec.get("position") + 1, + { + xtype: "webview", + id: "tab_" + rec.get("id"), + title: rec.get("name"), + icon: + rec.get("type") !== "custom" + ? "resources/icons/" + rec.get("logo") + : rec.get("logo") === "" + ? "resources/icons/custom.png" + : rec.get("logo"), + src: rec.get("url"), + type: rec.get("type"), + muted: rec.get("muted"), + includeInGlobalUnreadCounter: rec.get("includeInGlobalUnreadCounter"), + displayTabUnreadCounter: rec.get("displayTabUnreadCounter"), + enabled: rec.get("enabled"), + record: rec, + useragent: ipc.sendSync("getConfig").user_agent, + hidden: hideTab, + tabConfig: { + service: rec, + }, + } + ); + } + }, + + onNewServiceSelect: function (view, record, item, index, e) { + Ext.create("Rambox.view.add.Add", { + record: record, + }); + }, + + removeServiceFn: function (serviceId, total, actual, callback) { + var me = this; + if (!serviceId) return false; + + // Get Record + var rec = Ext.getStore("Services").getById(serviceId); + + if (!rec.get("enabled")) { + rec.set("enabled", true); + me.onEnableDisableService( + null, + Ext.getStore("Services").indexOf(rec), + true, + null, + true + ); + + // Get Tab + var tab = Ext.getCmp("tab_" + serviceId); + // Clear all trash data + const webview = tab.getWebView(); + webview.addEventListener("did-start-loading", function () { + clearData(webview, tab); + }); + } else { + // Get Tab + var tab = Ext.getCmp("tab_" + serviceId); + // Clear all trash data + const webview = tab.getWebView(); + clearData(webview, tab); + } + + const config = ipc.sendSync("getConfig"); + if (config.default_service === rec.get("id")) + ipc.send( + "setConfig", + Ext.apply(config, { default_service: "ramboxTab" }) + ); + + function clearData(webview, tab) { + const currentWebView = require("electron").remote.webContents.fromId( + webview.getWebContentsId() + ); + + currentWebView.clearHistory(); + currentWebView.session.flushStorageData(); + currentWebView.session + .clearCache() + .then(() => { + currentWebView.session + .clearStorageData() + .then(() => { + currentWebView.session.cookies + .flushStore() + .then(() => { + // Remove record from localStorage + Ext.getStore("Services").remove(rec); + // Close tab + tab.close(); + // Close waiting message + if (total === actual) { + Ext.Msg.hide(); + if (Ext.isFunction(callback)) callback(); + } + }) + .catch((err) => { + console.log(err); + }); + }) + .catch((err) => { + console.log(err); + }); + }) + .catch((err) => { + console.log(err); + }); + } + }, + + removeService: function (gridView, rowIndex, colIndex, col, e, rec, rowEl) { + var me = this; + + Ext.Msg.confirm( + locale["app.window[12]"], + locale["app.window[13]"] + " " + rec.get("name") + "?", + function (btnId) { + if (btnId === "yes") { + Ext.Msg.wait("Please wait until we clear all.", "Removing..."); + me.removeServiceFn(rec.get("id"), 1, 1); + } + } + ); + }, + + removeAllServices: function (btn, callback) { + var me = this; + + if (btn) { + Ext.Msg.confirm( + locale["app.window[12]"], + locale["app.window[14]"], + function (btnId) { + if (btnId === "yes") { + // Clear counter for unread messaging + document.title = "Rambox"; + + Ext.cq1("app-main").suspendEvent("remove"); + Ext.getStore("Services").load(); + Ext.Msg.wait("Please wait until we clear all.", "Removing..."); + const count = Ext.getStore("Services").getCount(); + var i = 1; + Ext.Array.each( + Ext.getStore("Services").collect("id"), + function (serviceId) { + me.removeServiceFn(serviceId, count, i++, callback || false); + } + ); + if (count === 0 && Ext.isFunction(callback)) callback(); + Ext.cq1("app-main").resumeEvent("remove"); + } + } + ); + } else { + Ext.cq1("app-main").suspendEvent("remove"); + Ext.getStore("Services").load(); + const count = Ext.getStore("Services").getCount(); + var i = 1; + Ext.Array.each( + Ext.getStore("Services").collect("id"), + function (serviceId) { + me.removeServiceFn(serviceId, count, i++, callback || false); + } + ); + if (count === 0 && Ext.isFunction(callback)) callback(); + Ext.cq1("app-main").resumeEvent("remove"); + } + }, + + configureService: function ( + gridView, + rowIndex, + colIndex, + col, + e, + rec, + rowEl + ) { + Ext.create("Rambox.view.add.Add", { + record: rec, + service: Ext.getStore("ServicesList").getById(rec.get("type")), + edit: true, + }); + }, + + onSearchRender: function (field) { + field.focus(false, 1000); + }, + + onSearchEnter: function (field, e) { + var me = this; + + if ( + e.getKey() == e.ENTER && + Ext.getStore("ServicesList").getCount() === 2 + ) { + // Two because we always shows Custom Service option + me.onNewServiceSelect( + field.up().down("dataview"), + Ext.getStore("ServicesList").getAt(0) + ); + me.onClearClick(field); + } + }, + + doTypeFilter: function (cg, newValue, oldValue) { + var me = this; + + Ext.getStore("ServicesList") + .getFilters() + .replaceAll({ + fn: function (record) { + return ( + Ext.Array.contains( + Ext.Object.getKeys(cg.getValue()), + record.get("type") + ) || record.get("type") === "custom" + ); + }, + }); + }, + + onSearchServiceChange: function (field, newValue, oldValue) { + var me = this; + + var cg = field.up().down("checkboxgroup"); + if (!Ext.isEmpty(newValue) && newValue.length > 0) { + field.getTrigger("clear").show(); + + Ext.getStore("ServicesList") + .getFilters() + .replaceAll({ + fn: function (record) { + if (record.get("type") === "custom") return true; + if ( + !Ext.Array.contains( + Ext.Object.getKeys(cg.getValue()), + record.get("type") + ) + ) + return false; + return record + .get("name") + .toLowerCase() + .indexOf(newValue.toLowerCase()) > -1 + ? true + : false; + }, + }); + } else { + field.getTrigger("clear").hide(); + Ext.getStore("ServicesList").getFilters().removeAll(); + me.doTypeFilter(cg); + } + field.updateLayout(); + }, + + onClearClick: function (field, trigger, e) { + var me = this; + + var cg = field.up().down("checkboxgroup"); + + field.reset(); + field.getTrigger("clear").hide(); + field.updateLayout(); + + Ext.getStore("ServicesList").getFilters().removeAll(); + me.doTypeFilter(cg); + }, + + dontDisturb: function (btn, e, called) { + console.info("Dont Disturb:", btn.pressed ? "Enabled" : "Disabled"); + + // Google Analytics Event + if (!called) + ga_storage._trackEvent( + "Usability", + "dontDisturb", + btn.pressed ? "on" : "off" + ); + + Ext.Array.each( + Ext.getStore("Services").collect("id"), + function (serviceId) { + // Get Tab + var tab = Ext.getCmp("tab_" + serviceId); + + if (!tab) return; // Skip disabled services + + // Mute sounds + tab.setAudioMuted(btn.pressed ? true : tab.record.get("muted"), true); + + // Prevent Notifications + tab.setNotifications( + btn.pressed ? false : tab.record.get("notifications"), + true + ); + } + ); + + localStorage.setItem("dontDisturb", btn.pressed); + + ipc.send("setDontDisturb", btn.pressed); + + btn.setText( + locale["app.main[16]"] + + ": " + + (btn.pressed ? locale["app.window[20]"] : locale["app.window[21]"]) + ); + + // var btn_icon = document.getElementById('disturbBtn-btnIconEl'); + // btn_icon.innerHTML = btn.pressed ? "" : ""; + + btn.pressed + ? btn.setGlyph("xf1f7@FontAwesome") + : btn.setGlyph("xf0f3@FontAwesome"); + + Ext.getCmp("mainTabBar").getEl().toggleCls("dontdisturb"); + + // If this method is called from Lock method, prevent showing toast + if (!e) return; + Ext.toast({ + html: btn.pressed ? "ENABLED" : "DISABLED", + title: "Don't Disturb", + width: 200, + align: "t", + closable: false, + }); + }, + + lockRambox: function (btn) { + var me = this; + + if (ipc.sendSync("getConfig").master_password) { + Ext.Msg.confirm( + locale["app.main[19]"], + "Do you want to use the Master Password as your temporal password?", + function (btnId) { + if (btnId === "yes") { + setLock(ipc.sendSync("getConfig").master_password); + } else { + showTempPass(); + } + } + ); + } else { + showTempPass(); + } + + function showTempPass() { + var msgbox = Ext.Msg.prompt( + locale["app.main[19]"], + locale["app.window[22]"], + function (btnId, text) { + if (btnId === "ok") { + var msgbox2 = Ext.Msg.prompt( + locale["app.main[19]"], + locale["app.window[23]"], + function (btnId, text2) { + if (btnId === "ok") { + if (text !== text2) { + Ext.Msg.show({ + title: locale["app.window[24]"], + message: locale["app.window[25]"], + icon: Ext.Msg.WARNING, + buttons: Ext.Msg.OK, + fn: me.lockRambox, + }); + return false; + } + + setLock(Rambox.util.MD5.encypt(text)); + } + } + ); + msgbox2.textField.inputEl.dom.type = "password"; + } + } + ); + msgbox.textField.inputEl.dom.type = "password"; + } + + function setLock(text) { + var ramboxTab = Ext.cq1("#ramboxTab"); + + // Related to issue #2065. Focusing in an sub frame is a workaround + if (ramboxTab.getWebView) { + ramboxTab.down("component").el.dom.executeJavaScript(` var iframeFix = document.createElement('iframe'); document.body.appendChild(iframeFix); iframeFix.focus(); document.body.removeChild(iframeFix); `); - } - console.info('Lock Rambox:', 'Enabled'); - - // Save encrypted password in localStorage to show locked when app is reopen - localStorage.setItem('locked', text); - - // Google Analytics Event - ga_storage._trackEvent('Usability', 'locked'); - - me.lookupReference('disturbBtn').setPressed(true); - me.dontDisturb(me.lookupReference('disturbBtn'), false, true); - - me.showLockWindow(); - } - } - - ,showLockWindow: function() { - var me = this; - - var validateFn = function() { - if ( localStorage.getItem('locked') === Rambox.util.MD5.encypt(winLock.down('textfield').getValue()) ) { - console.info('Lock Rambox:', 'Disabled'); - localStorage.removeItem('locked'); - winLock.close(); - me.lookupReference('disturbBtn').setPressed(false); - me.dontDisturb(me.lookupReference('disturbBtn'), false); - } else { - winLock.down('textfield').reset(); - winLock.down('textfield').markInvalid('Unlock password is invalid'); - } - }; - - var winLock = Ext.create('Ext.window.Window', { - maximized: true - ,closable: false - ,resizable: false - ,minimizable: false - ,maximizable: false - ,draggable: false - ,onEsc: Ext.emptyFn - ,layout: 'center' - ,bodyStyle: 'background-color:#2e658e;' - ,items: [ - { - xtype: 'container' - ,layout: 'vbox' - ,items: [ - { - xtype: 'image' - ,src: 'resources/Icon.png' - ,width: 256 - ,height: 256 - } - ,{ - xtype: 'component' - ,autoEl: { - tag: 'h1' - ,html: locale['app.window[26]'] - ,style: 'text-align:center;width:256px;' - } - } - ,{ - xtype: 'textfield' - ,inputType: 'password' - ,width: 256 - ,listeners: { - specialkey: function(field, e){ - if ( e.getKey() == e.ENTER ) { - validateFn(); - } - } - } - } - ,{ - xtype: 'button' - ,text: locale['app.window[27]'] - ,glyph: 'xf13e@FontAwesome' - ,width: 256 - ,scale: 'large' - ,handler: validateFn - } - ] - } - ] - ,listeners: { - render: function(win) { - win.getEl().on('click', function() { - win.down('textfield').focus(100); - }); - } - } - }).show(); - winLock.down('textfield').focus(1000); - } - - ,openPreferences: function( btn ) { - var me = this; - - Ext.create('Rambox.view.preferences.Preferences').show(); - } - - ,login: function(btn) { - var me = this; - - Rambox.ux.Auth0.login(); - } - - ,logout: function(btn) { - var me = this; - - var logoutFn = function(callback) { - Ext.Msg.wait(locale['app.window[37]'], locale['app.main[21]']); - - // Google Analytics Event - ga_storage._trackEvent('Users', 'loggedOut'); - - // Logout from Auth0 - Rambox.ux.Auth0.logout(); - - Ext.cq1('app-main').getViewModel().set('username', ''); - Ext.cq1('app-main').getViewModel().set('avatar', ''); - - if ( Ext.isFunction(callback) ) { - callback(false, function() { - Ext.Msg.hide(); - }); - } else { - Ext.Msg.hide(); - } - } - - if ( btn ) { - Ext.Msg.confirm(locale['app.main[21]'], locale['app.window[38]'], function(btnId) { - if ( btnId === 'yes' ) { - logoutFn(me.removeAllServices.bind(me)); - } - }); - } else { - logoutFn(); - } - } - - ,showDonate: function( btn ) { - Signalayer.API.show('tChaoq3PwSG9wswhn'); - } + } + console.info("Lock Rambox:", "Enabled"); + + // Save encrypted password in localStorage to show locked when app is reopen + localStorage.setItem("locked", text); + + // Google Analytics Event + ga_storage._trackEvent("Usability", "locked"); + + me.lookupReference("disturbBtn").setPressed(true); + me.dontDisturb(me.lookupReference("disturbBtn"), false, true); + + me.showLockWindow(); + } + }, + + showLockWindow: function () { + var me = this; + + var validateFn = function () { + if ( + localStorage.getItem("locked") === + Rambox.util.MD5.encypt(winLock.down("textfield").getValue()) + ) { + console.info("Lock Rambox:", "Disabled"); + localStorage.removeItem("locked"); + winLock.close(); + me.lookupReference("disturbBtn").setPressed(false); + me.dontDisturb(me.lookupReference("disturbBtn"), false); + } else { + winLock.down("textfield").reset(); + winLock.down("textfield").markInvalid("Unlock password is invalid"); + } + }; + + var winLock = Ext.create("Ext.window.Window", { + maximized: true, + closable: false, + resizable: false, + minimizable: false, + maximizable: false, + draggable: false, + onEsc: Ext.emptyFn, + layout: "center", + bodyStyle: "background-color:#2e658e;", + items: [ + { + xtype: "container", + layout: "vbox", + items: [ + { + xtype: "image", + src: "resources/Icon.png", + width: 256, + height: 256, + }, + { + xtype: "component", + autoEl: { + tag: "h1", + html: locale["app.window[26]"], + style: "text-align:center;width:256px;", + }, + }, + { + xtype: "textfield", + inputType: "password", + width: 256, + listeners: { + specialkey: function (field, e) { + if (e.getKey() == e.ENTER) { + validateFn(); + } + }, + }, + }, + { + xtype: "button", + text: locale["app.window[27]"], + glyph: "xf13e@FontAwesome", + width: 256, + scale: "large", + handler: validateFn, + }, + ], + }, + ], + listeners: { + render: function (win) { + win.getEl().on("click", function () { + win.down("textfield").focus(100); + }); + }, + }, + }).show(); + winLock.down("textfield").focus(1000); + }, + + openPreferences: function (btn) { + var me = this; + + Ext.create("Rambox.view.preferences.Preferences").show(); + }, + + login: function (btn) { + var me = this; + + Rambox.ux.Auth0.login(); + }, + + logout: function (btn) { + var me = this; + + var logoutFn = function (callback) { + Ext.Msg.wait(locale["app.window[37]"], locale["app.main[21]"]); + + // Google Analytics Event + ga_storage._trackEvent("Users", "loggedOut"); + + // Logout from Auth0 + Rambox.ux.Auth0.logout(); + + Ext.cq1("app-main").getViewModel().set("username", ""); + Ext.cq1("app-main").getViewModel().set("avatar", ""); + + if (Ext.isFunction(callback)) { + callback(false, function () { + Ext.Msg.hide(); + }); + } else { + Ext.Msg.hide(); + } + }; + + if (btn) { + Ext.Msg.confirm( + locale["app.main[21]"], + locale["app.window[38]"], + function (btnId) { + if (btnId === "yes") { + logoutFn(me.removeAllServices.bind(me)); + } + } + ); + } else { + logoutFn(); + } + }, + + showDonate: function (btn) { + Signalayer.API.show("tChaoq3PwSG9wswhn"); + }, }); diff --git a/app/view/preferences/Preferences.js b/app/view/preferences/Preferences.js index 7ef806d4..c7cf01e1 100644 --- a/app/view/preferences/Preferences.js +++ b/app/view/preferences/Preferences.js @@ -1,382 +1,412 @@ -Ext.define('Rambox.view.preferences.Preferences',{ - extend: 'Ext.window.Window' - ,xtype: 'preferences' +Ext.define("Rambox.view.preferences.Preferences", { + extend: "Ext.window.Window", + xtype: "preferences", - ,requires: [ - 'Rambox.view.preferences.PreferencesController' - ,'Rambox.view.preferences.PreferencesModel' - ,'Ext.form.field.ComboBox' - ,'Ext.form.field.Checkbox' - ] + requires: [ + "Rambox.view.preferences.PreferencesController", + "Rambox.view.preferences.PreferencesModel", + "Ext.form.field.ComboBox", + "Ext.form.field.Checkbox", + ], - ,controller: 'preferences-preferences' - ,viewModel: { - type: 'preferences-preferences' - } + controller: "preferences-preferences", + viewModel: { + type: "preferences-preferences", + }, - ,title: locale['preferences[0]'] - ,width: 420 - ,height: 500 - ,modal: true - ,closable: true - ,minimizable: false - ,maximizable: false - ,draggable: true - ,resizable: false - ,scrollable: 'vertical' - ,bodyStyle: 'margin-right:15px;' - ,buttons: [ - { - text: locale['button[1]'] - ,ui: 'decline' - ,handler: 'cancel' - } - ,'->' - ,{ - text: locale['button[4]'] - ,handler: 'save' - } - ] + title: locale["preferences[0]"], + width: 420, + height: 500, + modal: true, + closable: true, + minimizable: false, + maximizable: false, + draggable: true, + resizable: false, + scrollable: "vertical", + bodyStyle: "margin-right:15px;", + buttons: [ + { + text: locale["button[1]"], + ui: "decline", + handler: "cancel", + }, + "->", + { + text: locale["button[4]"], + handler: "save", + }, + ], - ,initComponent: function() { - var config = ipc.sendSync('getConfig'); + initComponent: function () { + var config = ipc.sendSync("getConfig"); - var defaultServiceOptions = []; - defaultServiceOptions.push({ value: 'ramboxTab', label: 'Rambox Tab' }); - defaultServiceOptions.push({ value: 'last', label: 'Last Active Service' }); - Ext.getStore('Services').each(function(rec) { - defaultServiceOptions.push({ - value: rec.get('id') - ,label: rec.get('name') - }); - }); + var defaultServiceOptions = []; + defaultServiceOptions.push({ value: "ramboxTab", label: "Rambox Tab" }); + defaultServiceOptions.push({ value: "last", label: "Last Active Service" }); + Ext.getStore("Services").each(function (rec) { + defaultServiceOptions.push({ + value: rec.get("id"), + label: rec.get("name"), + }); + }); - this.items = [ - { - xtype: 'form' - ,bodyPadding: 20 - ,items: [ - { - xtype: 'container' - ,layout: 'hbox' - ,items: [ - { - xtype: 'combo' - ,name: 'locale' - ,fieldLabel: 'Language' - ,labelAlign: 'left' - ,flex: 1 - ,labelWidth: 80 - ,value: config.locale - ,displayField: 'label' - ,valueField: 'value' - ,editable: false - ,store: Ext.create('Ext.data.Store', { - fields: ['value', 'label'] - ,data: [ - { 'value': 'af', 'auth0': 'af', 'label': 'Afrikaans' } - ,{ 'value': 'ar', 'auth0': 'en', 'label': 'Arabic' } - ,{ 'value': 'bs2', 'auth0': 'en', 'label': 'Barndutsch, Switzerland' } - ,{ 'value': 'bn', 'auth0': 'en', 'label': 'Bengali' } - ,{ 'value': 'bg', 'auth0': 'en', 'label': 'Bulgarian' } - ,{ 'value': 'ca', 'auth0': 'ca', 'label': 'Catalan' } - ,{ 'value': 'ceb', 'auth0': 'en', 'label': 'Cebuano' } - ,{ 'value': 'zh-CN', 'auth0': 'zh', 'label': 'Chinese Simplified' } - ,{ 'value': 'zh-TW', 'auth0': 'zh-tw', 'label': 'Chinese Traditional' } - ,{ 'value': 'hr', 'auth0': 'en', 'label': 'Croatian' } - ,{ 'value': 'cs', 'auth0': 'cs', 'label': 'Czech' } - ,{ 'value': 'da', 'auth0': 'da', 'label': 'Danish' } - ,{ 'value': 'nl', 'auth0': 'nl', 'label': 'Dutch' } - ,{ 'value': 'en', 'auth0': 'en', 'label': 'English' } - ,{ 'value': 'fi', 'auth0': 'fi', 'label': 'Finnish' } - ,{ 'value': 'fil', 'auth0': 'en', 'label': 'Filipino' } - ,{ 'value': 'fr', 'auth0': 'fr', 'label': 'French' } - ,{ 'value': 'de', 'auth0': 'de', 'label': 'German' } - ,{ 'value': 'de-CH', 'auth0': 'de', 'label': 'German, Switzerland' } - ,{ 'value': 'el', 'auth0': 'el', 'label': 'Greek' } - ,{ 'value': 'he', 'auth0': 'en', 'label': 'Hebrew' } - ,{ 'value': 'hi', 'auth0': 'en', 'label': 'Hindi' } - ,{ 'value': 'hu', 'auth0': 'hu', 'label': 'Hungarian' } - ,{ 'value': 'id', 'auth0': 'en', 'label': 'Indonesian' } - ,{ 'value': 'it', 'auth0': 'it', 'label': 'Italian' } - ,{ 'value': 'ja', 'auth0': 'ja', 'label': 'Japanese' } - ,{ 'value': 'ko', 'auth0': 'ko', 'label': 'Korean' } - ,{ 'value': 'no', 'auth0': 'no', 'label': 'Norwegian' } - ,{ 'value': 'fa', 'auth0': 'fa', 'label': 'Persian' } - ,{ 'value': 'pl', 'auth0': 'pl', 'label': 'Polish' } - ,{ 'value': 'pt-PT', 'auth0': 'pt-br', 'label': 'Portuguese' } - ,{ 'value': 'pt-BR', 'auth0': 'pt-br', 'label': 'Portuguese (Brazilian)' } - ,{ 'value': 'ro', 'auth0': 'ro', 'label': 'Romanian' } - ,{ 'value': 'ru', 'auth0': 'ru', 'label': 'Russian' } - ,{ 'value': 'sr', 'auth0': 'en', 'label': 'Serbian (Cyrillic)' } - ,{ 'value': 'sk', 'auth0': 'sk', 'label': 'Slovak' } - ,{ 'value': 'es-ES', 'auth0': 'es', 'label': 'Spanish' } - ,{ 'value': 'sv-SE', 'auth0': 'sv', 'label': 'Swedish' } - ,{ 'value': 'tl', 'auth0': 'en', 'label': 'Tagalog' } - ,{ 'value': 'th', 'auth0': 'en', 'label': 'Thai' } - ,{ 'value': 'tr', 'auth0': 'tr', 'label': 'Turkish' } - ,{ 'value': 'uk', 'auth0': 'en', 'label': 'Ukrainian' } - ,{ 'value': 'ur-PK', 'auth0': 'en', 'label': 'Urdu (Pakistan)' } - ,{ 'value': 'vi', 'auth0': 'en', 'label': 'Vietnamese' } - ] - }) - } - ,{ - xtype: 'button' - ,text: 'Help us Translate' - ,style: 'border-top-left-radius:0;border-bottom-left-radius:0;' - ,href: 'https://crowdin.com/project/rambox/invite' - } - ] - } - ,{ - xtype: 'label' - ,text: 'English is the only language that has full translation. We are working with all the others, help us!' - ,style: 'display:block;font-size:10px;line-height:15px;' - ,margin: '0 0 10 0' - } - ,{ - xtype: 'checkbox' - ,name: 'auto_launch' - ,boxLabel: locale['preferences[5]'] - ,value: config.auto_launch - } - ,{ - xtype: 'checkbox' - ,name: 'start_minimized' - ,boxLabel: locale['preferences[4]'] - ,value: config.start_minimized - } - ,{ - xtype: 'checkbox' - ,name: 'darkreader' - ,boxLabel: locale['preferences[29]'] - ,value: config.darkreader - } - ,{ - xtype: 'checkbox' - ,name: 'hide_menu_bar' - ,boxLabel: locale['preferences[1]']+' (Alt key to display)' - ,value: config.hide_menu_bar - ,hidden: process.platform === 'darwin' - } - ,{ - xtype: 'combo' - ,name: 'tabbar_location' - ,fieldLabel: locale['preferences[11]'] - ,labelAlign: 'left' - ,width: 380 - ,labelWidth: 180 - ,value: config.tabbar_location - ,displayField: 'label' - ,valueField: 'value' - ,editable: false - ,store: Ext.create('Ext.data.Store', { - fields: ['value', 'label'] - ,data: [ - { 'value': 'top', 'label': 'Top' } - ,{ 'value': 'left', 'label': 'Left' } - ,{ 'value': 'bottom', 'label': 'Bottom' } - ,{ 'value': 'right', 'label': 'Right' } - ] - }) - } - ,{ - xtype: 'checkbox' - ,name: 'hide_tabbar_labels' - ,boxLabel: locale['preferences[28]'] - ,value: config.hide_tabbar_labels - } - ,{ - xtype: 'combo' - ,name: 'default_service' - ,fieldLabel: locale['preferences[12]'] - ,labelAlign: 'top' - //,width: 380 - //,labelWidth: 105 - ,value: config.default_service - ,displayField: 'label' - ,valueField: 'value' - ,editable: false - ,store: Ext.create('Ext.data.Store', { - fields: ['value', 'label'] - ,data: defaultServiceOptions - }) - } - ,{ - xtype: 'combo' - ,name: 'window_display_behavior' - ,fieldLabel: locale['preferences[13]'] - ,labelAlign: 'left' - ,width: 380 - ,labelWidth: 105 - ,value: config.window_display_behavior - ,displayField: 'label' - ,valueField: 'value' - ,editable: false - ,store: Ext.create('Ext.data.Store', { - fields: ['value', 'label'] - ,data: [ - { 'value': 'show_taskbar', 'label': locale['preferences[14]'] } - ,{ 'value': 'show_trayIcon', 'label': locale['preferences[15]'] } - ,{ 'value': 'taskbar_tray', 'label': locale['preferences[16]'] } - ] - }) - ,hidden: process.platform === 'darwin' - } - ,{ - xtype: 'combo' - ,name: 'window_close_behavior' - ,fieldLabel: locale['preferences[17]'] - ,labelAlign: 'left' - ,width: 380 - ,labelWidth: 180 - ,value: config.window_close_behavior - ,displayField: 'label' - ,valueField: 'value' - ,editable: false - ,store: Ext.create('Ext.data.Store', { - fields: ['value', 'label'] - ,data: [ - { 'value': 'keep_in_tray', 'label': locale['preferences[18]'] } - ,{ 'value': 'keep_in_tray_and_taskbar', 'label': locale['preferences[19]'] } - ,{ 'value': 'quit', 'label': locale['preferences[20]'] } - ] - }) - ,hidden: process.platform === 'darwin' - } - ,{ - xtype: 'checkbox' - ,name: 'always_on_top' - ,boxLabel: locale['preferences[21]'] - ,value: config.always_on_top - } - ,{ - xtype: 'checkbox' - ,name: 'systemtray_indicator' - ,boxLabel: locale['preferences[22]'] - ,value: config.systemtray_indicator - ,hidden: process.platform === 'darwin' - } - ,{ - xtype: 'checkbox' - ,name: 'flash_frame' - ,boxLabel: process.platform === 'darwin' ? locale['preferences[10]'] : locale['preferences[9]'] - ,value: config.flash_frame - } - ,{ - xtype: 'checkbox' - ,name: 'disable_gpu' - ,boxLabel: locale['preferences[23]'] - ,value: config.disable_gpu - } - ,{ - xtype: 'checkbox' - ,name: 'enable_hidpi_support' - ,boxLabel: locale['preferences[8]'] - ,value: config.enable_hidpi_support - ,hidden: process.platform !== 'win32' - }, - { - xtype: 'textfield' - ,fieldLabel: 'Override User-Agent for all services (needs to relaunch)' - ,labelAlign: 'top' - ,name: 'user_agent' - ,value: config.user_agent - ,width: 360 - ,emptyText: 'Leave blank for default user agent' - } - ,{ - xtype: 'fieldset' - ,title: locale['preferences[24]'] - ,collapsed: !config.master_password - ,checkboxToggle: true - ,checkboxName: 'master_password' - ,margin: '10 0 0 0' - ,padding: 10 - ,layout: 'hbox' - ,defaults: { labelAlign: 'top' } - ,items: [ - { - xtype: 'textfield' - ,inputType: 'password' - ,fieldLabel: locale['preferences[25]'] - ,name: 'master_password1' - ,itemId: 'pass' - ,flex: 1 - ,listeners: { - validitychange: function(field) { - field.next().validate(); - }, - blur: function(field) { - field.next().validate(); - } - } - } - ,{ - xtype: 'textfield' - ,inputType: 'password' - ,fieldLabel: locale['preferences[26]'] - ,name: 'master_password2' - ,margin: '0 0 0 10' - ,vtype: 'password' - ,initialPassField: 'pass' - ,flex: 1 - } - ] - } - ,{ - xtype: 'fieldset' - ,title: 'Proxy (needs to relaunch) - Free Proxy Servers' - ,collapsed: !config.proxy - ,checkboxToggle: true - ,checkboxName: 'proxy' - ,margin: '10 0 0 0' - ,padding: 10 - ,layout: 'vbox' - ,defaults: { labelAlign: 'left' } - ,items: [ - { - xtype: 'textfield' - ,vtype: 'url' - ,fieldLabel: 'Host' - ,name: 'proxyHost' - ,value: config.proxyHost - //,flex: 1 - } - ,{ - xtype: 'numberfield' - ,fieldLabel: 'Port' - ,name: 'proxyPort' - ,value: config.proxyPort - } - ,{ - xtype: 'textfield' - ,fieldLabel: 'Login' - ,name: 'proxyLogin' - ,value: config.proxyLogin - ,emptyText: 'Optional' - } - ,{ - xtype: 'textfield' - ,fieldLabel: 'Password' - ,name: 'proxyPassword' - ,value: config.proxyPassword - ,emptyText: 'Optional' - ,inputType: 'password' - } - ] - } - ,{ - xtype: 'checkbox' - ,name: 'sendStatistics' - ,boxLabel: locale['preferences[27]'] - ,value: config.sendStatistics - } - ] - } - ]; + this.items = [ + { + xtype: "form", + bodyPadding: 20, + items: [ + { + xtype: "container", + layout: "hbox", + items: [ + { + xtype: "combo", + name: "locale", + fieldLabel: "Language", + labelAlign: "left", + flex: 1, + labelWidth: 80, + value: config.locale, + displayField: "label", + valueField: "value", + editable: false, + store: Ext.create("Ext.data.Store", { + fields: ["value", "label"], + data: [ + { value: "af", auth0: "af", label: "Afrikaans" }, + { value: "ar", auth0: "en", label: "Arabic" }, + { + value: "bs2", + auth0: "en", + label: "Barndutsch, Switzerland", + }, + { value: "bn", auth0: "en", label: "Bengali" }, + { value: "bg", auth0: "en", label: "Bulgarian" }, + { value: "ca", auth0: "ca", label: "Catalan" }, + { value: "ceb", auth0: "en", label: "Cebuano" }, + { + value: "zh-CN", + auth0: "zh", + label: "Chinese Simplified", + }, + { + value: "zh-TW", + auth0: "zh-tw", + label: "Chinese Traditional", + }, + { value: "hr", auth0: "en", label: "Croatian" }, + { value: "cs", auth0: "cs", label: "Czech" }, + { value: "da", auth0: "da", label: "Danish" }, + { value: "nl", auth0: "nl", label: "Dutch" }, + { value: "en", auth0: "en", label: "English" }, + { value: "fi", auth0: "fi", label: "Finnish" }, + { value: "fil", auth0: "en", label: "Filipino" }, + { value: "fr", auth0: "fr", label: "French" }, + { value: "de", auth0: "de", label: "German" }, + { + value: "de-CH", + auth0: "de", + label: "German, Switzerland", + }, + { value: "el", auth0: "el", label: "Greek" }, + { value: "he", auth0: "en", label: "Hebrew" }, + { value: "hi", auth0: "en", label: "Hindi" }, + { value: "hu", auth0: "hu", label: "Hungarian" }, + { value: "id", auth0: "en", label: "Indonesian" }, + { value: "it", auth0: "it", label: "Italian" }, + { value: "ja", auth0: "ja", label: "Japanese" }, + { value: "ko", auth0: "ko", label: "Korean" }, + { value: "no", auth0: "no", label: "Norwegian" }, + { value: "fa", auth0: "fa", label: "Persian" }, + { value: "pl", auth0: "pl", label: "Polish" }, + { value: "pt-PT", auth0: "pt-br", label: "Portuguese" }, + { + value: "pt-BR", + auth0: "pt-br", + label: "Portuguese (Brazilian)", + }, + { value: "ro", auth0: "ro", label: "Romanian" }, + { value: "ru", auth0: "ru", label: "Russian" }, + { value: "sr", auth0: "en", label: "Serbian (Cyrillic)" }, + { value: "sk", auth0: "sk", label: "Slovak" }, + { value: "es-ES", auth0: "es", label: "Spanish" }, + { value: "sv-SE", auth0: "sv", label: "Swedish" }, + { value: "tl", auth0: "en", label: "Tagalog" }, + { value: "th", auth0: "en", label: "Thai" }, + { value: "tr", auth0: "tr", label: "Turkish" }, + { value: "uk", auth0: "en", label: "Ukrainian" }, + { value: "ur-PK", auth0: "en", label: "Urdu (Pakistan)" }, + { value: "vi", auth0: "en", label: "Vietnamese" }, + ], + }), + }, + { + xtype: "button", + text: "Help us Translate", + style: "border-top-left-radius:0;border-bottom-left-radius:0;", + href: "https://crowdin.com/project/rambox/invite", + }, + ], + }, + { + xtype: "label", + text: + "English is the only language that has full translation. We are working with all the others, help us!", + style: "display:block;font-size:10px;line-height:15px;", + margin: "0 0 10 0", + }, + { + xtype: "checkbox", + name: "auto_launch", + boxLabel: locale["preferences[5]"], + value: config.auto_launch, + }, + { + xtype: "checkbox", + name: "start_minimized", + boxLabel: locale["preferences[4]"], + value: config.start_minimized, + }, + { + xtype: "checkbox", + name: "darkreader", + boxLabel: locale["preferences[29]"], + value: config.darkreader, + }, + { + xtype: "checkbox", + name: "hide_menu_bar", + boxLabel: + locale["preferences[1]"] + " (Alt key to display)", + value: config.hide_menu_bar, + hidden: process.platform === "darwin", + }, + { + xtype: "combo", + name: "tabbar_location", + fieldLabel: locale["preferences[11]"], + labelAlign: "left", + width: 380, + labelWidth: 180, + value: config.tabbar_location, + displayField: "label", + valueField: "value", + editable: false, + store: Ext.create("Ext.data.Store", { + fields: ["value", "label"], + data: [ + { value: "top", label: "Top" }, + { value: "left", label: "Left" }, + { value: "bottom", label: "Bottom" }, + { value: "right", label: "Right" }, + ], + }), + }, + { + xtype: "checkbox", + name: "hide_tabbar_labels", + boxLabel: locale["preferences[28]"], + value: config.hide_tabbar_labels, + }, + { + xtype: "combo", + name: "default_service", + fieldLabel: locale["preferences[12]"], + labelAlign: "top", + //,width: 380 + //,labelWidth: 105 + value: config.default_service, + displayField: "label", + valueField: "value", + editable: false, + store: Ext.create("Ext.data.Store", { + fields: ["value", "label"], + data: defaultServiceOptions, + }), + }, + { + xtype: "combo", + name: "window_display_behavior", + fieldLabel: locale["preferences[13]"], + labelAlign: "left", + width: 380, + labelWidth: 105, + value: config.window_display_behavior, + displayField: "label", + valueField: "value", + editable: false, + store: Ext.create("Ext.data.Store", { + fields: ["value", "label"], + data: [ + { value: "show_taskbar", label: locale["preferences[14]"] }, + { value: "show_trayIcon", label: locale["preferences[15]"] }, + { value: "taskbar_tray", label: locale["preferences[16]"] }, + ], + }), + hidden: process.platform === "darwin", + }, + { + xtype: "combo", + name: "window_close_behavior", + fieldLabel: locale["preferences[17]"], + labelAlign: "left", + width: 380, + labelWidth: 180, + value: config.window_close_behavior, + displayField: "label", + valueField: "value", + editable: false, + store: Ext.create("Ext.data.Store", { + fields: ["value", "label"], + data: [ + { value: "keep_in_tray", label: locale["preferences[18]"] }, + { + value: "keep_in_tray_and_taskbar", + label: locale["preferences[19]"], + }, + { value: "quit", label: locale["preferences[20]"] }, + ], + }), + hidden: process.platform === "darwin", + }, + { + xtype: "checkbox", + name: "always_on_top", + boxLabel: locale["preferences[21]"], + value: config.always_on_top, + }, + { + xtype: "checkbox", + name: "systemtray_indicator", + boxLabel: locale["preferences[22]"], + value: config.systemtray_indicator, + hidden: process.platform === "darwin", + }, + { + xtype: "checkbox", + name: "flash_frame", + boxLabel: + process.platform === "darwin" + ? locale["preferences[10]"] + : locale["preferences[9]"], + value: config.flash_frame, + }, + { + xtype: "checkbox", + name: "disable_gpu", + boxLabel: locale["preferences[23]"], + value: config.disable_gpu, + }, + { + xtype: "checkbox", + name: "enable_hidpi_support", + boxLabel: locale["preferences[8]"], + value: config.enable_hidpi_support, + hidden: process.platform !== "win32", + }, + { + xtype: "textfield", + fieldLabel: + "Override User-Agent for all services (needs to relaunch)", + labelAlign: "top", + name: "user_agent", + value: config.user_agent, + width: 360, + emptyText: "Leave blank for default user agent", + }, + { + xtype: "fieldset", + title: locale["preferences[24]"], + collapsed: !config.master_password, + checkboxToggle: true, + checkboxName: "master_password", + margin: "10 0 0 0", + padding: 10, + layout: "hbox", + defaults: { labelAlign: "top" }, + items: [ + { + xtype: "textfield", + inputType: "password", + fieldLabel: locale["preferences[25]"], + name: "master_password1", + itemId: "pass", + flex: 1, + listeners: { + validitychange: function (field) { + field.next().validate(); + }, + blur: function (field) { + field.next().validate(); + }, + }, + }, + { + xtype: "textfield", + inputType: "password", + fieldLabel: locale["preferences[26]"], + name: "master_password2", + margin: "0 0 0 10", + vtype: "password", + initialPassField: "pass", + flex: 1, + }, + ], + }, + { + xtype: "fieldset", + title: + 'Proxy (needs to relaunch) - Free Proxy Servers', + collapsed: !config.proxy, + checkboxToggle: true, + checkboxName: "proxy", + margin: "10 0 0 0", + padding: 10, + layout: "vbox", + defaults: { labelAlign: "left" }, + items: [ + { + xtype: "textfield", + vtype: "url", + fieldLabel: "Host", + name: "proxyHost", + value: config.proxyHost, + //,flex: 1 + }, + { + xtype: "numberfield", + fieldLabel: "Port", + name: "proxyPort", + value: config.proxyPort, + }, + { + xtype: "textfield", + fieldLabel: "Login", + name: "proxyLogin", + value: config.proxyLogin, + emptyText: "Optional", + }, + { + xtype: "textfield", + fieldLabel: "Password", + name: "proxyPassword", + value: config.proxyPassword, + emptyText: "Optional", + inputType: "password", + }, + ], + }, + { + xtype: "checkbox", + name: "sendStatistics", + boxLabel: locale["preferences[27]"], + value: config.sendStatistics, + }, + ], + }, + ]; - this.callParent(); - } + this.callParent(); + }, }); diff --git a/electron/main.js b/electron/main.js index 5aab2ad4..d68d5906 100644 --- a/electron/main.js +++ b/electron/main.js @@ -33,38 +33,38 @@ if (isDev) // Initial Config const config = new Config({ - defaults: { - always_on_top: false - ,hide_menu_bar: false - ,tabbar_location: 'top' - ,hide_tabbar_labels: false - ,window_display_behavior: 'taskbar_tray' - ,auto_launch: !isDev - ,flash_frame: true - ,window_close_behavior: 'keep_in_tray' - ,start_minimized: false - ,darkreader: true - ,systemtray_indicator: true - ,master_password: false - ,dont_disturb: false - ,disable_gpu: process.platform === 'linux' - ,proxy: false - ,proxyHost: '' - ,proxyPort: '' - ,proxyLogin: '' - ,proxyPassword: '' - ,locale: 'en' - ,enable_hidpi_support: false - ,user_agent: '' - ,default_service: 'ramboxTab' - ,sendStatistics: false - - ,x: undefined - ,y: undefined - ,width: 1000 - ,height: 800 - ,maximized: false - } + defaults: { + always_on_top: false, + hide_menu_bar: false, + tabbar_location: "top", + hide_tabbar_labels: false, + window_display_behavior: "taskbar_tray", + auto_launch: !isDev, + flash_frame: true, + window_close_behavior: "keep_in_tray", + start_minimized: false, + darkreader: true, + systemtray_indicator: true, + master_password: false, + dont_disturb: false, + disable_gpu: process.platform === "linux", + proxy: false, + proxyHost: "", + proxyPort: "", + proxyLogin: "", + proxyPassword: "", + locale: "en", + enable_hidpi_support: false, + user_agent: "", + default_service: "ramboxTab", + sendStatistics: false, + + x: undefined, + y: undefined, + width: 1000, + height: 800, + maximized: false, + }, }); // Fix issues with HiDPI scaling on Windows platform diff --git a/package.json b/package.json index 3bc3ae7a..6c2f62c1 100644 --- a/package.json +++ b/package.json @@ -1,241 +1,241 @@ { - "name": "Rambox", - "productName": "Rambox", - "version": "0.7.9", - "description": "Free and Open Source messaging and emailing app that combines common web applications into one.", - "main": "electron/main.js", - "repository": { - "type": "git", - "url": "https://github.com/ramboxapp/community-edition.git" - }, - "bugs": { - "url": "https://github.com/ramboxapp/community-edition/issues" - }, - "homepage": "https://rambox.app", - "keywords": [ - "Rambox", - "messaging", - "app", - "slack", - "whatsapp", - "facebook", - "messenger", - "telegram", - "google", - "hangouts", - "skype" - ], - "author": "Rambox LLC ", - "license": "GPL-3.0", - "scripts": { - "start": "electron electron/main.js", - "start:debug": "electron electron/main.js --enable-logging", - "dev": "electron electron/main.js", - "test": "./node_modules/.bin/mocha test/tests/**/*.spec.js", - "sencha:clean": "rm -rf ./build/production", - "sencha:compile": "sencha app build && npm --prefix ./build/production/Rambox/ install ./build/production/Rambox/", - "sencha:compile:build": "sencha app build", - "clean": "rm -rf ./dist", - "clean:osx": "rm -rf ./dist/Rambox-darwin-*", - "clean:win": "rm -rf ./dist/Rambox-win32-*", - "pack": "npm run pack:osx && npm run pack:win", - "pack:osx": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=darwin --arch=x64 --icon=resources/installer/Icon.icns --app-version=0.2.0 --build-version=64-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", - "pack:win": "npm run pack:win32 && npm run pack:win64", - "pack:win32": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=win32 --arch=ia32 --icon=resources/installer/Icon.ico --app-version=0.2.0 --build-version=32-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", - "pack:win64": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=win32 --arch=x64 --icon=resources/installer/Icon.ico --app-version=0.2.0 --build-version=64-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", - "pack:linux": "npm run pack:linux32 && npm run pack:linux64", - "pack:linux32": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=linux --arch=ia32 --icon=resources/installer/Icon.ico --app-version=0.2.0 --build-version=64-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", - "pack:linux64": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=linux --arch=x64 --icon=resources/installer/Icon.ico --app-version=0.2.0 --build-version=64-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", - "build": "npm run build:linux && npm run build:osx && npm run build:win", - "build:osx": "electron-builder --macos", - "build:linux": "electron-builder --linux --publish=onTagOrDraft", - "build:linux32": "electron-builder --linux --ia32 --publish=onTagOrDraft", - "build:linux64": "electron-builder --linux --x64 --publish=onTagOrDraft", - "build:win": "electron-builder --win --ia32 --x64", - "build:win32": "electron-builder --win --ia32", - "build:win64": "electron-builder --win --x64", - "setup:osx": "npm run sencha:clean && npm run sencha:compile && npm run clean:osx && npm run pack:osx && npm run build:osx", - "setup:win": "npm run sencha:clean && npm run sencha:compile && npm run clean:win && npm run pack:win && npm run build:win", - "all:win": "npm run sencha:clean && npm run sencha:compile && npm run clean:win && npm run pack:win && npm run zip:win32 && npm run zip:win64 && npm run build:win", - "all:linux": "npm run sencha:clean && npm run sencha:compile && npm run build:linux", - "translations:download": "node languages.js download", - "translations:generate": "node languages.js generate" - }, - "build": { - "productName": "Rambox", - "appId": "com.grupovrs.ramboxce", - "afterSign": "resources/installer/notarize.js", - "asar": true, - "electronVersion": "11.4.10", - "electronDownload": { - "version": "11.4.10" - }, - "mac": { - "category": "public.app-category.productivity", - "artifactName": "Rambox-${version}-mac.${ext}", - "target": [ - "default" - ], - "hardenedRuntime": true, - "gatekeeperAssess": false, - "entitlements": "resources/installer/entitlements.mac.plist", - "entitlementsInherit": "resources/installer/entitlements.mac.plist", - "extendInfo": { - "NSMicrophoneUsageDescription": "Apps inside Rambox CE may need access to your microphone. Please, grant access to have a better experience.", - "NSCameraUsageDescription": "Apps inside Rambox CE may need access to your camera. Please, grant access to have a better experience." - } - }, - "dmg": { - "title": "Rambox", - "iconSize": 128, - "sign": false, - "contents": [ - { - "x": 355, - "y": 125, - "type": "link", - "path": "/Applications" - }, - { - "x": 155, - "y": 125, - "type": "file" - } - ] - }, - "win": { - "publisherName": "Rambox LLC", - "artifactName": "Rambox-${version}-win-${arch}.${ext}", - "target": [ - "nsis", - "zip" - ] - }, - "nsis": { - "deleteAppDataOnUninstall": true, - "oneClick": false, - "perMachine": false, - "runAfterFinish": true - }, - "snap": { - "publish": [ - { - "provider": "github" - } - ], - "plugs": [ - "default", - "camera", - "audio-record", - "audio-playback", - "removable-media", - "raw-usb", - "u2f-devices", - "cups-control" - ] - }, - "linux": { - "icon": "resources/installer/icons", - "category": "Network", - "desktop": { - "Terminal": "false", - "Type": "Application", - "Categories": "GTK;GNOME;Network;Email;Chat;InstantMessaging;" - }, - "artifactName": "Rambox-${version}-linux-${arch}.${ext}", - "executableArgs": [ - "--no-sandbox" - ], - "target": [ - { - "target": "snap", - "arch": [ - "x64" - ] - }, - { - "target": "AppImage", - "arch": [ - "x64", - "ia32" - ] - }, - { - "target": "deb", - "arch": [ - "x64", - "ia32" - ] - }, - { - "target": "rpm", - "arch": [ - "x64", - "ia32" - ] - }, - { - "target": "zip", - "arch": [ - "x64", - "ia32" - ] - }, - { - "target": "tar.gz", - "arch": [ - "x64", - "ia32" - ] - } - ] - }, - "directories": { - "buildResources": "resources/installer/", - "output": "dist/" - }, - "publish": [ - { - "provider": "github", - "owner": "ramboxapp", - "repo": "community-edition", - "vPrefixedTagName": false - } - ] - }, - "devDependencies": { - "asar": "0.12.4", - "chai": "3.5.0", - "crowdin": "1.0.0", - "csvjson": "4.3.3", - "electron": "11.4.10", - "electron-builder": "22.9.1", - "electron-notarize": "1.0.0", - "electron-packager": "15.1.0", - "mocha": "5.2.0", - "spectron": "3.8.0" - }, - "dependencies": { - "@exponent/electron-cookies": "2.0.0", - "auth0-js": "9.13.2", - "auto-launch-patched": "5.0.2", - "crypto": "1.0.1", - "darkreader": "4.9.17", - "diskusage": "1.1.3", - "electron-contextmenu-wrapper": "git+https://github.com/ramboxapp/electron-contextmenu-wrapper.git", - "electron-is-dev": "0.3.0", - "electron-log": "2.2.17", - "electron-store": "6.0.0", - "electron-updater": "4.1.2", - "is-online": "8.2.0", - "mime": "2.3.1", - "mousetrap": "1.6.3", - "request": "2.88.0", - "request-promise": "4.2.2", - "rimraf": "2.6.1", - "tmp": "0.0.28" - }, - "volta": { - "node": "14.16.1" - } + "name": "Rambox", + "productName": "Rambox", + "version": "0.7.9", + "description": "Free and Open Source messaging and emailing app that combines common web applications into one.", + "main": "electron/main.js", + "repository": { + "type": "git", + "url": "https://github.com/ramboxapp/community-edition.git" + }, + "bugs": { + "url": "https://github.com/ramboxapp/community-edition/issues" + }, + "homepage": "https://rambox.app", + "keywords": [ + "Rambox", + "messaging", + "app", + "slack", + "whatsapp", + "facebook", + "messenger", + "telegram", + "google", + "hangouts", + "skype" + ], + "author": "Rambox LLC ", + "license": "GPL-3.0", + "scripts": { + "start": "electron electron/main.js", + "start:debug": "electron electron/main.js --enable-logging", + "dev": "electron electron/main.js", + "test": "./node_modules/.bin/mocha test/tests/**/*.spec.js", + "sencha:clean": "rm -rf ./build/production", + "sencha:compile": "sencha app build && npm --prefix ./build/production/Rambox/ install ./build/production/Rambox/", + "sencha:compile:build": "sencha app build", + "clean": "rm -rf ./dist", + "clean:osx": "rm -rf ./dist/Rambox-darwin-*", + "clean:win": "rm -rf ./dist/Rambox-win32-*", + "pack": "npm run pack:osx && npm run pack:win", + "pack:osx": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=darwin --arch=x64 --icon=resources/installer/Icon.icns --app-version=0.2.0 --build-version=64-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", + "pack:win": "npm run pack:win32 && npm run pack:win64", + "pack:win32": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=win32 --arch=ia32 --icon=resources/installer/Icon.ico --app-version=0.2.0 --build-version=32-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", + "pack:win64": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=win32 --arch=x64 --icon=resources/installer/Icon.ico --app-version=0.2.0 --build-version=64-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", + "pack:linux": "npm run pack:linux32 && npm run pack:linux64", + "pack:linux32": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=linux --arch=ia32 --icon=resources/installer/Icon.ico --app-version=0.2.0 --build-version=64-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", + "pack:linux64": "electron-packager \"./build/production/Rambox/\" \"Rambox\" --out=dist --platform=linux --arch=x64 --icon=resources/installer/Icon.ico --app-version=0.2.0 --build-version=64-bit --version-string.CompanyName=\"Rambox\" --version-string.ProductName=\"Rambox\" --asar --prune --overwrite", + "build": "npm run build:linux && npm run build:osx && npm run build:win", + "build:osx": "electron-builder --macos", + "build:linux": "electron-builder --linux --publish=onTagOrDraft", + "build:linux32": "electron-builder --linux --ia32 --publish=onTagOrDraft", + "build:linux64": "electron-builder --linux --x64 --publish=onTagOrDraft", + "build:win": "electron-builder --win --ia32 --x64", + "build:win32": "electron-builder --win --ia32", + "build:win64": "electron-builder --win --x64", + "setup:osx": "npm run sencha:clean && npm run sencha:compile && npm run clean:osx && npm run pack:osx && npm run build:osx", + "setup:win": "npm run sencha:clean && npm run sencha:compile && npm run clean:win && npm run pack:win && npm run build:win", + "all:win": "npm run sencha:clean && npm run sencha:compile && npm run clean:win && npm run pack:win && npm run zip:win32 && npm run zip:win64 && npm run build:win", + "all:linux": "npm run sencha:clean && npm run sencha:compile && npm run build:linux", + "translations:download": "node languages.js download", + "translations:generate": "node languages.js generate" + }, + "build": { + "productName": "Rambox", + "appId": "com.grupovrs.ramboxce", + "afterSign": "resources/installer/notarize.js", + "asar": true, + "electronVersion": "11.4.10", + "electronDownload": { + "version": "11.4.10" + }, + "mac": { + "category": "public.app-category.productivity", + "artifactName": "Rambox-${version}-mac.${ext}", + "target": [ + "default" + ], + "hardenedRuntime": true, + "gatekeeperAssess": false, + "entitlements": "resources/installer/entitlements.mac.plist", + "entitlementsInherit": "resources/installer/entitlements.mac.plist", + "extendInfo": { + "NSMicrophoneUsageDescription": "Apps inside Rambox CE may need access to your microphone. Please, grant access to have a better experience.", + "NSCameraUsageDescription": "Apps inside Rambox CE may need access to your camera. Please, grant access to have a better experience." + } + }, + "dmg": { + "title": "Rambox", + "iconSize": 128, + "sign": false, + "contents": [ + { + "x": 355, + "y": 125, + "type": "link", + "path": "/Applications" + }, + { + "x": 155, + "y": 125, + "type": "file" + } + ] + }, + "win": { + "publisherName": "Rambox LLC", + "artifactName": "Rambox-${version}-win-${arch}.${ext}", + "target": [ + "nsis", + "zip" + ] + }, + "nsis": { + "deleteAppDataOnUninstall": true, + "oneClick": false, + "perMachine": false, + "runAfterFinish": true + }, + "snap": { + "publish": [ + { + "provider": "github" + } + ], + "plugs": [ + "default", + "camera", + "audio-record", + "audio-playback", + "removable-media", + "raw-usb", + "u2f-devices", + "cups-control" + ] + }, + "linux": { + "icon": "resources/installer/icons", + "category": "Network", + "desktop": { + "Terminal": "false", + "Type": "Application", + "Categories": "GTK;GNOME;Network;Email;Chat;InstantMessaging;" + }, + "artifactName": "Rambox-${version}-linux-${arch}.${ext}", + "executableArgs": [ + "--no-sandbox" + ], + "target": [ + { + "target": "snap", + "arch": [ + "x64" + ] + }, + { + "target": "AppImage", + "arch": [ + "x64", + "ia32" + ] + }, + { + "target": "deb", + "arch": [ + "x64", + "ia32" + ] + }, + { + "target": "rpm", + "arch": [ + "x64", + "ia32" + ] + }, + { + "target": "zip", + "arch": [ + "x64", + "ia32" + ] + }, + { + "target": "tar.gz", + "arch": [ + "x64", + "ia32" + ] + } + ] + }, + "directories": { + "buildResources": "resources/installer/", + "output": "dist/" + }, + "publish": [ + { + "provider": "github", + "owner": "ramboxapp", + "repo": "community-edition", + "vPrefixedTagName": false + } + ] + }, + "devDependencies": { + "asar": "0.12.4", + "chai": "3.5.0", + "crowdin": "1.0.0", + "csvjson": "4.3.3", + "electron": "11.4.10", + "electron-builder": "22.9.1", + "electron-notarize": "1.0.0", + "electron-packager": "15.1.0", + "mocha": "5.2.0", + "spectron": "3.8.0" + }, + "dependencies": { + "@exponent/electron-cookies": "2.0.0", + "auth0-js": "9.13.2", + "auto-launch-patched": "5.0.2", + "crypto": "1.0.1", + "darkreader": "4.9.17", + "diskusage": "1.1.3", + "electron-contextmenu-wrapper": "git+https://github.com/ramboxapp/electron-contextmenu-wrapper.git", + "electron-is-dev": "0.3.0", + "electron-log": "2.2.17", + "electron-store": "6.0.0", + "electron-updater": "4.1.2", + "is-online": "8.2.0", + "mime": "2.3.1", + "mousetrap": "1.6.3", + "request": "2.88.0", + "request-promise": "4.2.2", + "rimraf": "2.6.1", + "tmp": "0.0.28" + }, + "volta": { + "node": "14.16.1" + } } diff --git a/resources/js/darkreader.js b/resources/js/darkreader.js index ad74dfa6..6eb81710 100644 --- a/resources/js/darkreader.js +++ b/resources/js/darkreader.js @@ -1,14 +1,14 @@ -const { ipcRenderer } = require('electron'); -const darkreader = require('darkreader'); +const { ipcRenderer } = require("electron"); +const darkreader = require("darkreader"); darkreader.setFetchMethod(window.fetch); +const getIsEnabled = () => ipcRenderer.sendSync("getConfig").darkreader; +const canEnable = () => + document.readyState === "complete" || document.readyState === "interactive"; -const getIsEnabled = () => ipcRenderer.sendSync('getConfig').darkreader; -const canEnable = () => document.readyState === 'complete' || document.readyState === 'interactive'; - -document.addEventListener('readystatechange', () => { - console.log(document.readyState) - if (canEnable()) { - getIsEnabled()? darkreader.enable(): darkreader.disable(); - } -}); \ No newline at end of file +document.addEventListener("readystatechange", () => { + console.log(document.readyState); + if (canEnable()) { + getIsEnabled() ? darkreader.enable() : darkreader.disable(); + } +}); diff --git a/resources/js/rambox-service-api.js b/resources/js/rambox-service-api.js index 9869b725..782616be 100644 --- a/resources/js/rambox-service-api.js +++ b/resources/js/rambox-service-api.js @@ -3,9 +3,12 @@ */ const { desktopCapturer, ipcRenderer } = require("electron"); -require("./darkreader.js") -const { ipcRenderer } = require('electron'); -const { ContextMenuBuilder, ContextMenuListener } = require('electron-contextmenu-wrapper'); +require("./darkreader.js"); +const { ipcRenderer } = require("electron"); +const { + ContextMenuBuilder, + ContextMenuListener, +} = require("electron-contextmenu-wrapper"); /** * Make the Rambox API available via a global "rambox" variable. diff --git a/resources/languages/en.js b/resources/languages/en.js index 1dc80752..c5e8aa61 100644 --- a/resources/languages/en.js +++ b/resources/languages/en.js @@ -1 +1,252 @@ -var locale=[];locale["preferences[0]"]="Preferences";locale["preferences[1]"]="Auto-hide Menu bar";locale["preferences[2]"]="Show in Taskbar";locale["preferences[3]"]="Keep Rambox in the taskbar when closing it";locale["preferences[4]"]="Start minimized";locale["preferences[5]"]="Start automatically on system startup";locale["preferences[6]"]="Don't need to see the menu bar all the time?";locale["preferences[7]"]="To temporarily show the menu bar, just press the Alt key.";locale["app.update[0]"]="New version is available!";locale["app.update[1]"]="Download";locale["app.update[2]"]="Changelog";locale["app.update[3]"]="You are up to date!";locale["app.update[4]"]="You have the latest version of Rambox.";locale["app.about[0]"]="About Rambox";locale["app.about[1]"]="Free and Open Source messaging and emailing app that combines common web applications into one.";locale["app.about[2]"]="Version";locale["app.about[3]"]="Platform";locale["app.about[4]"]="Developed by";locale["app.main[0]"]="Add a new Service";locale["app.main[1]"]="Messaging";locale["app.main[2]"]="Email";locale["app.main[3]"]="No services found... Try another search.";locale["app.main[4]"]="Enabled Services";locale["app.main[5]"]="ALIGN";locale["app.main[6]"]="Left";locale["app.main[7]"]="Right";locale["app.main[8]"]="Item";locale["app.main[9]"]="Items";locale["app.main[10]"]="Remove all Services";locale["app.main[11]"]="Prevent notifications";locale["app.main[12]"]="Muted";locale["app.main[13]"]="Configure";locale["app.main[14]"]="Remove";locale["app.main[15]"]="No services added...";locale["app.main[16]"]="Don't Disturb";locale["app.main[17]"]="Disable notifications and sounds in all services. Perfect to be concentrated and focused.";locale["app.main[18]"]="Shortcut key";locale["app.main[19]"]="Lock Rambox";locale["app.main[20]"]="Lock this app if you will be away for a period of time.";locale["app.main[21]"]="Logout";locale["app.main[22]"]="Login";locale["app.main[23]"]="Login to save your configuration (no credentials stored) to sync with all your computers.";locale["app.main[24]"]="Powered by";locale["app.main[25]"]="Donate";locale["app.main[26]"]="with";locale["app.main[27]"]="from Argentina as an Open Source project.";locale["app.window[0]"]="Add";locale["app.window[1]"]="Edit";locale["app.window[2]"]="Name";locale["app.window[3]"]="Options";locale["app.window[4]"]="Align to Right";locale["app.window[5]"]="Show notifications";locale["app.window[6]"]="Mute all sounds";locale["app.window[7]"]="Advanced";locale["app.window[8]"]="Custom Code";locale["app.window[9]"]="read more...";locale["app.window[10]"]="Add service";locale["app.window[11]"]="team";locale["app.window[12]"]="Please confirm...";locale["app.window[13]"]="Are you sure you want to remove";locale["app.window[14]"]="Are you sure you want to remove all services?";locale["app.window[15]"]="Add Custom Service";locale["app.window[16]"]="Edit Custom Service";locale["app.window[17]"]="URL";locale["app.window[18]"]="Logo";locale["app.window[19]"]="Trust invalid authority certificates";locale["app.window[20]"]="ON";locale["app.window[21]"]="OFF";locale["app.window[22]"]="Enter a temporal password to unlock it later";locale["app.window[23]"]="Repeat the temporal password";locale["app.window[24]"]="Warning";locale["app.window[25]"]="Passwords are not the same. Please try again...";locale["app.window[26]"]="Rambox is locked";locale["app.window[27]"]="UNLOCK";locale["app.window[28]"]="Connecting...";locale["app.window[29]"]="Please wait until we get your configuration.";locale["app.window[30]"]="Import";locale["app.window[31]"]="You don't have any service saved. Do you want to import your current services?";locale["app.window[32]"]="Clear services";locale["app.window[33]"]="Do you want to remove all your current services to start over?";locale["app.window[34]"]="If no, you will be logged out.";locale["app.window[35]"]="Confirm";locale["app.window[36]"]="To import your configuration, Rambox needs to remove all your current services. Do you want to continue?";locale["app.window[37]"]="Closing your session...";locale["app.window[38]"]="Are you sure you want to logout?";locale["app.webview[0]"]="Reload";locale["app.webview[1]"]="Go Online";locale["app.webview[2]"]="Go Offline";locale["app.webview[3]"]="Toggle Developer Tools";locale["app.webview[4]"]="Loading...";locale["preferences[8]"]="Enable HiDPI support (needs to relaunch)";locale["preferences[9]"]="Flash Taskbar on new message";locale["preferences[10]"]="Bounce Dock on new message";locale["preferences[11]"]="Service bar location";locale["preferences[12]"]="Default service to display when Rambox starts";locale["preferences[13]"]="Display behaviour";locale["preferences[14]"]="Show in Taskbar";locale["preferences[15]"]="Show Tray Icon";locale["preferences[16]"]="Show in Taskbar and Tray Icon";locale["preferences[17]"]="When closing the main window";locale["preferences[18]"]="Keep in tray";locale["preferences[19]"]="Keep in tray and/or taskbar";locale["preferences[20]"]="Quit";locale["preferences[21]"]="Always on top";locale["preferences[22]"]="Show System Tray indicator on unread messages";locale["preferences[23]"]="Disable Hardware Acceleration (needs to relaunch)";locale["preferences[24]"]="Master Password - Ask for password on startup";locale["preferences[25]"]="Password";locale["preferences[26]"]="Repeat Password";locale["preferences[27]"]="Send anonymous usage statistics to help us improve Rambox (needs to relaunch)";locale["preferences[28]"]="Hide service bar labels (requires restart)";;locale["preferences[29]"]="Enable global dark theme (Darkreader). Needs relaunch to apply for service tabs.";locale["button[0]"]="Ok";locale["button[1]"]="Cancel";locale["button[2]"]="Yes";locale["button[3]"]="No";locale["button[4]"]="Save";locale["main.dialog[0]"]="Certification Error";locale["main.dialog[1]"]="The service with the following URL has an invalid authority certification.";locale["main.dialog[2]"]="You have to remove the service and add it again";locale["menu.help[0]"]="Visit Rambox Website";locale["menu.help[1]"]="Report an Issue...";locale["menu.help[2]"]="Ask for Help";locale["menu.help[3]"]="Donate";locale["menu.help[4]"]="Help";locale["menu.edit[0]"]="Edit";locale["menu.edit[1]"]="Undo";locale["menu.edit[2]"]="Redo";locale["menu.edit[3]"]="Cut";locale["menu.edit[4]"]="Copy";locale["menu.edit[5]"]="Paste";locale["menu.edit[6]"]="Select All";locale["menu.view[0]"]="View";locale["menu.view[1]"]="Reload";locale["menu.view[2]"]="Toggle Full Screen";locale["menu.view[3]"]="Toggle Developer Tools";locale["menu.window[0]"]="Window";locale["menu.window[1]"]="Minimize";locale["menu.window[2]"]="Close";locale["menu.window[3]"]="Always on top";locale["menu.help[5]"]="Check for updates...";locale["menu.help[6]"]="About Rambox";locale["menu.osx[0]"]="Services";locale["menu.osx[1]"]="Hide Rambox";locale["menu.osx[2]"]="Hide Others";locale["menu.osx[3]"]="Show All";locale["menu.file[0]"]="File";locale["menu.file[1]"]="Quit Rambox";locale["tray[0]"]="Show/Hide Window";locale["tray[1]"]="Quit";locale["services[0]"]="WhatsApp is a cross-platform mobile messaging app for iPhone, BlackBerry, Android, Windows Phone and Nokia. Send text, video, images, audio for free.";locale["services[1]"]="Slack brings all your communication together in one place. It’s real-time messaging, archiving and search for modern teams.";locale["services[2]"]="Noysi is a communication tool for teams where privacy is guaranteed. With Noysi you can access all your conversations and files in seconds from anywhere and unlimited.";locale["services[3]"]="Instantly reach the people in your life for free. Messenger is just like texting, but you don't have to pay for every message.";locale["services[4]"]="Stay in touch with family and friends for free. Get international calling, free online calls and Skype for Business on desktop and mobile.";locale["services[5]"]="Hangouts bring conversations to life with photos, emoji, and even group video calls for free. Connect with friends across computers, Android, and Apple devices.";locale["services[6]"]="HipChat is hosted group chat and video chat built for teams. Supercharge real-time collaboration with persistent chat rooms, file sharing, and screen sharing.";locale["services[7]"]="Telegram is a messaging app with a focus on speed and security. It’s super-fast, simple, secure and free.";locale["services[8]"]="WeChat is a free messaging calling app that allows you to easily connect with family; friends across countries. It’s the all-in-one communications app for free text (SMS/MMS), voice; video calls, moments, photo sharing, and games.";locale["services[9]"]="Gmail, Google's free email service, is one of the world's most popular email programs.";locale["services[10]"]="Inbox by Gmail is a new app from the Gmail team. Inbox is an organized place to get things done and get back to what matters. Bundles keep emails organized.";locale["services[11]"]="ChatWork is a group chat app for business. Secure messaging, video chat, task management and file sharing. Real-time communication and increase productivity for teams.";locale["services[12]"]="GroupMe brings group text messaging to every phone. Group message with the people in your life that are important to you.";locale["services[13]"]="The world's most advanced team chat meets enterprise search.";locale["services[14]"]="Gitter is built on top of GitHub and is tightly integrated with your organizations, repositories, issues and activity.";locale["services[15]"]="Steam is a digital distribution platform developed by Valve Corporation offering digital rights management (DRM), multiplayer gaming and social networking services.";locale["services[16]"]="Step up your game with a modern voice & text chat app. Crystal clear voice, multiple server and channel support, mobile apps, and more.";locale["services[17]"]="Take control. Do more. Outlook is the free email and calendar service that helps you stay on top of what matters and get things done.";locale["services[18]"]="Outlook for Business";locale["services[19]"]="Web-based email service offered by the American company Yahoo!. The service is free for personal use, and paid-for business email plans are available.";locale["services[20]"]="Free and web-based encrypted email service founded in 2013 at the CERN research facility. ProtonMail is designed as a zero-knowledge system, using client-side encryption to protect emails and user data before they are sent to ProtonMail servers, in contrast to other common webmail services such as Gmail and Hotmail.";locale["services[21]"]="Tutanota is an open-source end-to-end encrypted email software and freemium hosted secure email service based on this software.";locale["services[22]"]="versions of service. Hushmail uses OpenPGP standards and the source is available for download.";locale["services[23]"]="Collaborative email and threaded group chat for productive teams. A single app for all your internal and external communication.";locale["services[24]"]="From group messages and video calls all the way to helpdesk killer features our goal is to become the number one cross-platform open source chat solution.";locale["services[25]"]="HD quality calls, private and group chats with inline photos, music and video. Also available for your phone or tablet.";locale["services[26]"]="Sync is a business chat tool that will boost productivity for your team.";locale["services[27]"]="No description...";locale["services[28]"]="Allows you to instant message with anyone on the Yahoo server. Tells you when you get mail, and gives stock quotes.";locale["services[29]"]="Voxer is a messaging app for your smartphone with live voice (like a PTT walkie talkie), text, photo and location sharing.";locale["services[30]"]="Dasher lets you say what you really want with pics, GIFs, links and more. Take a poll to find out what your friends really think of your new boo.";locale["services[31]"]="Flowdock is your team's chat with a shared inbox. Teams using Flowdock stay up-to-date, react in seconds instead of days, and never forget anything.";locale["services[32]"]="Mattermost is an open source, self-hosted Slack-alternative. As an alternative to proprietary SaaS messaging, Mattermost brings all your team communication into one place, making it searchable and accessible anywhere.";locale["services[33]"]="DingTalk is a multi-sided platform empowers small and medium-sized business to communicate effectively.";locale["services[34]"]="The mysms family of applications helps you text anywhere and enhances your messaging experience on your smartphone, tablet and computer.";locale["services[35]"]="ICQ is an open source instant messaging computer program that was first developed and popularized.";locale["services[36]"]="TweetDeck is a social media dashboard application for management of Twitter accounts.";locale["services[37]"]="Custom Service";locale["services[38]"]="Add a custom service if is not listed above.";locale["services[39]"]="Zinc is a secure communication app for mobile workers, with text, voice, video, file sharing and more.";locale["services[40]"]="Freenode, formerly known as Open Projects Network, is an IRC network used to discuss peer-directed projects.";locale["services[41]"]="Text from your computer, sync'd with your Android phone & number.";locale["services[42]"]="Free and open source webmail software for the masses, written in PHP.";locale["services[43]"]="Horde is a free and open source web-based groupware.";locale["services[44]"]="SquirrelMail is a standards-based webmail package written in PHP.";locale["services[45]"]="Ad-free business Email Hosting with a clean, minimalist interface. Integrated Calendar, Contacts, Notes, Tasks apps.";locale["services[46]"]="Zoho chat is a secure and scalable real-time communication and collaboration platform for teams to improve their productivity.";module.exports = locale; \ No newline at end of file +var locale = []; +locale["preferences[0]"] = "Preferences"; +locale["preferences[1]"] = "Auto-hide Menu bar"; +locale["preferences[2]"] = "Show in Taskbar"; +locale["preferences[3]"] = "Keep Rambox in the taskbar when closing it"; +locale["preferences[4]"] = "Start minimized"; +locale["preferences[5]"] = "Start automatically on system startup"; +locale["preferences[6]"] = "Don't need to see the menu bar all the time?"; +locale["preferences[7]"] = + "To temporarily show the menu bar, just press the Alt key."; +locale["app.update[0]"] = "New version is available!"; +locale["app.update[1]"] = "Download"; +locale["app.update[2]"] = "Changelog"; +locale["app.update[3]"] = "You are up to date!"; +locale["app.update[4]"] = "You have the latest version of Rambox."; +locale["app.about[0]"] = "About Rambox"; +locale["app.about[1]"] = + "Free and Open Source messaging and emailing app that combines common web applications into one."; +locale["app.about[2]"] = "Version"; +locale["app.about[3]"] = "Platform"; +locale["app.about[4]"] = "Developed by"; +locale["app.main[0]"] = "Add a new Service"; +locale["app.main[1]"] = "Messaging"; +locale["app.main[2]"] = "Email"; +locale["app.main[3]"] = "No services found... Try another search."; +locale["app.main[4]"] = "Enabled Services"; +locale["app.main[5]"] = "ALIGN"; +locale["app.main[6]"] = "Left"; +locale["app.main[7]"] = "Right"; +locale["app.main[8]"] = "Item"; +locale["app.main[9]"] = "Items"; +locale["app.main[10]"] = "Remove all Services"; +locale["app.main[11]"] = "Prevent notifications"; +locale["app.main[12]"] = "Muted"; +locale["app.main[13]"] = "Configure"; +locale["app.main[14]"] = "Remove"; +locale["app.main[15]"] = "No services added..."; +locale["app.main[16]"] = "Don't Disturb"; +locale["app.main[17]"] = + "Disable notifications and sounds in all services. Perfect to be concentrated and focused."; +locale["app.main[18]"] = "Shortcut key"; +locale["app.main[19]"] = "Lock Rambox"; +locale["app.main[20]"] = + "Lock this app if you will be away for a period of time."; +locale["app.main[21]"] = "Logout"; +locale["app.main[22]"] = "Login"; +locale["app.main[23]"] = + "Login to save your configuration (no credentials stored) to sync with all your computers."; +locale["app.main[24]"] = "Powered by"; +locale["app.main[25]"] = "Donate"; +locale["app.main[26]"] = "with"; +locale["app.main[27]"] = "from Argentina as an Open Source project."; +locale["app.window[0]"] = "Add"; +locale["app.window[1]"] = "Edit"; +locale["app.window[2]"] = "Name"; +locale["app.window[3]"] = "Options"; +locale["app.window[4]"] = "Align to Right"; +locale["app.window[5]"] = "Show notifications"; +locale["app.window[6]"] = "Mute all sounds"; +locale["app.window[7]"] = "Advanced"; +locale["app.window[8]"] = "Custom Code"; +locale["app.window[9]"] = "read more..."; +locale["app.window[10]"] = "Add service"; +locale["app.window[11]"] = "team"; +locale["app.window[12]"] = "Please confirm..."; +locale["app.window[13]"] = "Are you sure you want to remove"; +locale["app.window[14]"] = "Are you sure you want to remove all services?"; +locale["app.window[15]"] = "Add Custom Service"; +locale["app.window[16]"] = "Edit Custom Service"; +locale["app.window[17]"] = "URL"; +locale["app.window[18]"] = "Logo"; +locale["app.window[19]"] = "Trust invalid authority certificates"; +locale["app.window[20]"] = "ON"; +locale["app.window[21]"] = "OFF"; +locale["app.window[22]"] = "Enter a temporal password to unlock it later"; +locale["app.window[23]"] = "Repeat the temporal password"; +locale["app.window[24]"] = "Warning"; +locale["app.window[25]"] = "Passwords are not the same. Please try again..."; +locale["app.window[26]"] = "Rambox is locked"; +locale["app.window[27]"] = "UNLOCK"; +locale["app.window[28]"] = "Connecting..."; +locale["app.window[29]"] = "Please wait until we get your configuration."; +locale["app.window[30]"] = "Import"; +locale["app.window[31]"] = + "You don't have any service saved. Do you want to import your current services?"; +locale["app.window[32]"] = "Clear services"; +locale["app.window[33]"] = + "Do you want to remove all your current services to start over?"; +locale["app.window[34]"] = "If no, you will be logged out."; +locale["app.window[35]"] = "Confirm"; +locale["app.window[36]"] = + "To import your configuration, Rambox needs to remove all your current services. Do you want to continue?"; +locale["app.window[37]"] = "Closing your session..."; +locale["app.window[38]"] = "Are you sure you want to logout?"; +locale["app.webview[0]"] = "Reload"; +locale["app.webview[1]"] = "Go Online"; +locale["app.webview[2]"] = "Go Offline"; +locale["app.webview[3]"] = "Toggle Developer Tools"; +locale["app.webview[4]"] = "Loading..."; +locale["preferences[8]"] = "Enable HiDPI support (needs to relaunch)"; +locale["preferences[9]"] = "Flash Taskbar on new message"; +locale["preferences[10]"] = "Bounce Dock on new message"; +locale["preferences[11]"] = "Service bar location"; +locale["preferences[12]"] = "Default service to display when Rambox starts"; +locale["preferences[13]"] = "Display behaviour"; +locale["preferences[14]"] = "Show in Taskbar"; +locale["preferences[15]"] = "Show Tray Icon"; +locale["preferences[16]"] = "Show in Taskbar and Tray Icon"; +locale["preferences[17]"] = "When closing the main window"; +locale["preferences[18]"] = "Keep in tray"; +locale["preferences[19]"] = "Keep in tray and/or taskbar"; +locale["preferences[20]"] = "Quit"; +locale["preferences[21]"] = "Always on top"; +locale["preferences[22]"] = "Show System Tray indicator on unread messages"; +locale["preferences[23]"] = "Disable Hardware Acceleration (needs to relaunch)"; +locale["preferences[24]"] = "Master Password - Ask for password on startup"; +locale["preferences[25]"] = "Password"; +locale["preferences[26]"] = "Repeat Password"; +locale["preferences[27]"] = + "Send anonymous usage statistics to help us improve Rambox (needs to relaunch)"; +locale["preferences[28]"] = "Hide service bar labels (requires restart)"; +locale["preferences[29]"] = + "Enable global dark theme (Darkreader). Needs relaunch to apply for service tabs."; +locale["button[0]"] = "Ok"; +locale["button[1]"] = "Cancel"; +locale["button[2]"] = "Yes"; +locale["button[3]"] = "No"; +locale["button[4]"] = "Save"; +locale["main.dialog[0]"] = "Certification Error"; +locale["main.dialog[1]"] = + "The service with the following URL has an invalid authority certification."; +locale["main.dialog[2]"] = "You have to remove the service and add it again"; +locale["menu.help[0]"] = "Visit Rambox Website"; +locale["menu.help[1]"] = "Report an Issue..."; +locale["menu.help[2]"] = "Ask for Help"; +locale["menu.help[3]"] = "Donate"; +locale["menu.help[4]"] = "Help"; +locale["menu.edit[0]"] = "Edit"; +locale["menu.edit[1]"] = "Undo"; +locale["menu.edit[2]"] = "Redo"; +locale["menu.edit[3]"] = "Cut"; +locale["menu.edit[4]"] = "Copy"; +locale["menu.edit[5]"] = "Paste"; +locale["menu.edit[6]"] = "Select All"; +locale["menu.view[0]"] = "View"; +locale["menu.view[1]"] = "Reload"; +locale["menu.view[2]"] = "Toggle Full Screen"; +locale["menu.view[3]"] = "Toggle Developer Tools"; +locale["menu.window[0]"] = "Window"; +locale["menu.window[1]"] = "Minimize"; +locale["menu.window[2]"] = "Close"; +locale["menu.window[3]"] = "Always on top"; +locale["menu.help[5]"] = "Check for updates..."; +locale["menu.help[6]"] = "About Rambox"; +locale["menu.osx[0]"] = "Services"; +locale["menu.osx[1]"] = "Hide Rambox"; +locale["menu.osx[2]"] = "Hide Others"; +locale["menu.osx[3]"] = "Show All"; +locale["menu.file[0]"] = "File"; +locale["menu.file[1]"] = "Quit Rambox"; +locale["tray[0]"] = "Show/Hide Window"; +locale["tray[1]"] = "Quit"; +locale["services[0]"] = + "WhatsApp is a cross-platform mobile messaging app for iPhone, BlackBerry, Android, Windows Phone and Nokia. Send text, video, images, audio for free."; +locale["services[1]"] = + "Slack brings all your communication together in one place. It’s real-time messaging, archiving and search for modern teams."; +locale["services[2]"] = + "Noysi is a communication tool for teams where privacy is guaranteed. With Noysi you can access all your conversations and files in seconds from anywhere and unlimited."; +locale["services[3]"] = + "Instantly reach the people in your life for free. Messenger is just like texting, but you don't have to pay for every message."; +locale["services[4]"] = + "Stay in touch with family and friends for free. Get international calling, free online calls and Skype for Business on desktop and mobile."; +locale["services[5]"] = + "Hangouts bring conversations to life with photos, emoji, and even group video calls for free. Connect with friends across computers, Android, and Apple devices."; +locale["services[6]"] = + "HipChat is hosted group chat and video chat built for teams. Supercharge real-time collaboration with persistent chat rooms, file sharing, and screen sharing."; +locale["services[7]"] = + "Telegram is a messaging app with a focus on speed and security. It’s super-fast, simple, secure and free."; +locale["services[8]"] = + "WeChat is a free messaging calling app that allows you to easily connect with family; friends across countries. It’s the all-in-one communications app for free text (SMS/MMS), voice; video calls, moments, photo sharing, and games."; +locale["services[9]"] = + "Gmail, Google's free email service, is one of the world's most popular email programs."; +locale["services[10]"] = + "Inbox by Gmail is a new app from the Gmail team. Inbox is an organized place to get things done and get back to what matters. Bundles keep emails organized."; +locale["services[11]"] = + "ChatWork is a group chat app for business. Secure messaging, video chat, task management and file sharing. Real-time communication and increase productivity for teams."; +locale["services[12]"] = + "GroupMe brings group text messaging to every phone. Group message with the people in your life that are important to you."; +locale["services[13]"] = + "The world's most advanced team chat meets enterprise search."; +locale["services[14]"] = + "Gitter is built on top of GitHub and is tightly integrated with your organizations, repositories, issues and activity."; +locale["services[15]"] = + "Steam is a digital distribution platform developed by Valve Corporation offering digital rights management (DRM), multiplayer gaming and social networking services."; +locale["services[16]"] = + "Step up your game with a modern voice & text chat app. Crystal clear voice, multiple server and channel support, mobile apps, and more."; +locale["services[17]"] = + "Take control. Do more. Outlook is the free email and calendar service that helps you stay on top of what matters and get things done."; +locale["services[18]"] = "Outlook for Business"; +locale["services[19]"] = + "Web-based email service offered by the American company Yahoo!. The service is free for personal use, and paid-for business email plans are available."; +locale["services[20]"] = + "Free and web-based encrypted email service founded in 2013 at the CERN research facility. ProtonMail is designed as a zero-knowledge system, using client-side encryption to protect emails and user data before they are sent to ProtonMail servers, in contrast to other common webmail services such as Gmail and Hotmail."; +locale["services[21]"] = + "Tutanota is an open-source end-to-end encrypted email software and freemium hosted secure email service based on this software."; +locale["services[22]"] = + "versions of service. Hushmail uses OpenPGP standards and the source is available for download."; +locale["services[23]"] = + "Collaborative email and threaded group chat for productive teams. A single app for all your internal and external communication."; +locale["services[24]"] = + "From group messages and video calls all the way to helpdesk killer features our goal is to become the number one cross-platform open source chat solution."; +locale["services[25]"] = + "HD quality calls, private and group chats with inline photos, music and video. Also available for your phone or tablet."; +locale["services[26]"] = + "Sync is a business chat tool that will boost productivity for your team."; +locale["services[27]"] = "No description..."; +locale["services[28]"] = + "Allows you to instant message with anyone on the Yahoo server. Tells you when you get mail, and gives stock quotes."; +locale["services[29]"] = + "Voxer is a messaging app for your smartphone with live voice (like a PTT walkie talkie), text, photo and location sharing."; +locale["services[30]"] = + "Dasher lets you say what you really want with pics, GIFs, links and more. Take a poll to find out what your friends really think of your new boo."; +locale["services[31]"] = + "Flowdock is your team's chat with a shared inbox. Teams using Flowdock stay up-to-date, react in seconds instead of days, and never forget anything."; +locale["services[32]"] = + "Mattermost is an open source, self-hosted Slack-alternative. As an alternative to proprietary SaaS messaging, Mattermost brings all your team communication into one place, making it searchable and accessible anywhere."; +locale["services[33]"] = + "DingTalk is a multi-sided platform empowers small and medium-sized business to communicate effectively."; +locale["services[34]"] = + "The mysms family of applications helps you text anywhere and enhances your messaging experience on your smartphone, tablet and computer."; +locale["services[35]"] = + "ICQ is an open source instant messaging computer program that was first developed and popularized."; +locale["services[36]"] = + "TweetDeck is a social media dashboard application for management of Twitter accounts."; +locale["services[37]"] = "Custom Service"; +locale["services[38]"] = "Add a custom service if is not listed above."; +locale["services[39]"] = + "Zinc is a secure communication app for mobile workers, with text, voice, video, file sharing and more."; +locale["services[40]"] = + "Freenode, formerly known as Open Projects Network, is an IRC network used to discuss peer-directed projects."; +locale["services[41]"] = + "Text from your computer, sync'd with your Android phone & number."; +locale["services[42]"] = + "Free and open source webmail software for the masses, written in PHP."; +locale["services[43]"] = "Horde is a free and open source web-based groupware."; +locale["services[44]"] = + "SquirrelMail is a standards-based webmail package written in PHP."; +locale["services[45]"] = + "Ad-free business Email Hosting with a clean, minimalist interface. Integrated Calendar, Contacts, Notes, Tasks apps."; +locale["services[46]"] = + "Zoho chat is a secure and scalable real-time communication and collaboration platform for teams to improve their productivity."; +module.exports = locale;