diff --git a/Backers.md b/Backers.md
index bff2e750..485bd372 100644
--- a/Backers.md
+++ b/Backers.md
@@ -3,3 +3,4 @@
[Martin Grünbaum](https://github.com/alathon)
Ivan Toshkov
+[Simon Joda Stößer](https://github.com/SimJoSt)
diff --git a/README.md b/README.md
index 37509ba1..83278210 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@
+
@@ -57,6 +58,7 @@
+
@@ -67,6 +69,7 @@
+
@@ -77,6 +80,7 @@
+
@@ -87,6 +91,7 @@
+
@@ -97,6 +102,7 @@
+
@@ -107,7 +113,9 @@
+
+
@@ -152,6 +160,35 @@ Want to report a bug, request a feature, contribute to or translate Rambox? We n
If you're comfortable getting up and running from a `git clone`, this method is for you.
+## Adding a service
+
+The available services are stored in the [ServiceList.js](app/store/ServicesList.js).
+Structure of a service entry:
+
+|Name|Description|Required|
+|---|---|---|
+|id|Unique identifier for the service, e.g. "slack"|yes|
+|logo|File name of the service logo located in "/resources/icons/", e.g. "slack.png"|yes|
+|name|Visible name for the service, e.g. "Slack"|yes|
+|description|A short description of the service, e.g. "Slack brings all your communication together..."|yes|
+|url|URL of the service, e.g. "https://\_\_\_.slack.com/". "\_\_\_" may be used as a placeholder, that can be configured when adding a service.|yes|
+|type|Defines the type of the service. Must be one of `email` or `messaging`.|yes|
+|allow_popups|Set to `true` to allow popup windows for the service.|no|
+|note|Additional info to display when adding the service.|no|
+|manual_notifications|Set to `true` to let Rambox trigger notifications. Can be used for services that doesn't support browser notifications.|no|
+|js_unread|JavaScript code for setting the unread count (see below).|no|
+|dont_update_unread_from_title|Set to `true` to prevent updating the unread count from the window title (see below).|no|
+
+### Setting the unread count
+
+While there is also a way to set the unread count by adding ` (COUNT)` to the window title, this describes the preferred way of doing it:
+
+First set `dont_update_unread_from_title` in the service config to `true`.
+
+Code provided by `js_unread` will be injected into the service website.
+You can retrieve the unread count in this JavaScript code e.g. by parsing elements.
+Set the unread count by calling `rambox.setUnreadCount(COUNT)` or clear it by calling `rambox.clearUnreadCount()`.
+
#### Technologies:
* Sencha Ext JS 5.1.1.451
diff --git a/app/Application.js b/app/Application.js
index 43fdc2b3..806924db 100644
--- a/app/Application.js
+++ b/app/Application.js
@@ -44,9 +44,14 @@ Ext.define('Rambox.Application', {
var tabPanel = Ext.cq1('app-main');
var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab());
var i = activeIndex + 1;
- if ( i >= tabPanel.items.items.length - 1 ) i = 0;
- while ( tabPanel.items.items[i].id === 'tbfill' ) i++;
- tabPanel.setActiveTab( i );
+
+ // "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);
}
}
,{
diff --git a/app/model/Service.js b/app/model/Service.js
index 41eb4de2..fe35526b 100644
--- a/app/model/Service.js
+++ b/app/model/Service.js
@@ -37,7 +37,18 @@ Ext.define('Rambox.model.Service', {
name: 'muted'
,type: 'boolean'
,defaultValue: false
- },{
+ },
+ {
+ name: 'displayTabUnreadCounter',
+ type: 'boolean',
+ defaultValue: true
+ },
+ {
+ name: 'includeInGlobalUnreadCounter',
+ type: 'boolean',
+ defaultValue: true
+ },
+ {
name: 'trust'
,type: 'boolean'
,defaultValue: false
diff --git a/app/package.json b/app/package.json
index 74613555..2b5cc84b 100644
--- a/app/package.json
+++ b/app/package.json
@@ -33,6 +33,7 @@
"firebase": "^3.0.5",
"firebase-token-generator": "^2.0.0",
"tmp": "0.0.28",
+ "electron-spell-check-provider": "^1.0.0",
"mime": "^1.3.4",
"electron-is-dev": "^0.1.1",
"electron-config": "0.2.1"
diff --git a/app/store/Services.js b/app/store/Services.js
index 81a2f441..6ab34ffe 100644
--- a/app/store/Services.js
+++ b/app/store/Services.js
@@ -26,6 +26,19 @@ Ext.define('Rambox.store.Services', {
var servicesLeft = [];
var servicesRight = [];
store.each(function(service) {
+ // Fix some services with bad IDs
+ // TODO: Remove in next release
+ switch ( service.get('type') ) {
+ case 'office365':
+ service.set('type', 'outlook365');
+ break;
+ case ' irccloud':
+ service.set('type', 'irccloud');
+ break;
+ default:
+ break;
+ }
+
var cfg = {
xtype: 'webview'
,id: 'tab_'+service.get('id')
@@ -34,6 +47,8 @@ Ext.define('Rambox.store.Services', {
,src: service.get('url')
,type: service.get('type')
,muted: service.get('muted')
+ ,includeInGlobalUnreadCounter: service.get('includeInGlobalUnreadCounter')
+ ,displayTabUnreadCounter: service.get('displayTabUnreadCounter')
,enabled: service.get('enabled')
,record: service
,tabConfig: {
diff --git a/app/store/ServicesList.js b/app/store/ServicesList.js
index 4047e9b1..4499a500 100644
--- a/app/store/ServicesList.js
+++ b/app/store/ServicesList.js
@@ -95,7 +95,7 @@ Ext.define('Rambox.store.ServicesList', {
,url: 'https://web.telegram.org/'
,type: 'messaging'
,titleBlink: true
- ,js_unread: 'function checkUnread(){var e=document.getElementsByClassName("im_dialog_badge badge"),t=0;for(i=0;i= 1) { rambox.setUnreadCount(count); } else { rambox.clearUnreadCount(); } } setInterval(checkUnread, 3000); checkUnread(); })();'
+ ,dont_update_unread_from_title: true
+ },
+ {
+ id: 'Workplace'
+ ,logo: 'workplace.png'
+ ,name: 'Workplace by Facebook'
+ ,description: 'Connect everyone in your company and turn ideas into action. Through group discussion, a personalised News Feed, and voice and video calling, work together and get more done. Workplace is an ad-free space, separate from your personal Facebook account.'
+ ,url: 'https://___.facebook.com/'
+ ,type: 'messaging'
}
- ]
+ ]
});
diff --git a/app/util/Notifier.js b/app/util/Notifier.js
new file mode 100644
index 00000000..5d0da406
--- /dev/null
+++ b/app/util/Notifier.js
@@ -0,0 +1,57 @@
+
+/**
+ * Singleton class for notification dispatching.
+ */
+Ext.define('Rambox.util.Notifier', {
+
+ singleton: true,
+
+ constructor: function(config) {
+
+ config = config || {};
+
+ /**
+ * Returns the notification text depending on the service type.
+ *
+ * @param view
+ * @param count
+ * @return {*}
+ */
+ function getNotificationText(view, count) {
+ var text;
+ switch (Ext.getStore('ServicesList').getById(view.type).get('type')) {
+ case 'messaging':
+ text = 'You have ' + Ext.util.Format.plural(count, 'new message', 'new messages') + '.';
+ break;
+ case 'email':
+ text = 'You have ' + Ext.util.Format.plural(count, 'new email', 'new emails') + '.';
+ break;
+ default:
+ text = 'You have ' + Ext.util.Format.plural(count, 'new activity', 'new activities') + '.';
+ break;
+ }
+ return text;
+ }
+
+ /**
+ * Dispatches a notification for a specific service.
+ *
+ * @param view The view of the service
+ * @param {number} count The unread count
+ */
+ this.dispatchNotification = function(view, count) {
+ var text = getNotificationText(view, count);
+
+ var notification = new Notification(view.record.get('name'), {
+ body: text,
+ icon: view.tab.icon,
+ silent: view.record.get('muted')
+ });
+
+ notification.onclick = function() {
+ require('electron').remote.getCurrentWindow().show();
+ Ext.cq1('app-main').setActiveTab(view);
+ };
+ }
+ }
+});
diff --git a/app/util/UnreadCounter.js b/app/util/UnreadCounter.js
new file mode 100644
index 00000000..46617b0f
--- /dev/null
+++ b/app/util/UnreadCounter.js
@@ -0,0 +1,75 @@
+/**
+ * Singleton class to handle the global unread counter.
+ */
+Ext.define('Rambox.util.UnreadCounter', {
+
+ singleton: true,
+
+ constructor: function(config) {
+
+ config = config || {};
+
+ /**
+ * Map for storing the global unread count.
+ * service id -> unread count
+ *
+ * @type {Map}
+ */
+ var unreadCountByService = new Map();
+
+ /**
+ * Holds the global unread count for internal usage.
+ *
+ * @type {number}
+ */
+ var totalUnreadCount = 0;
+
+ /**
+ * Sets the application's unread count to tracked unread count.
+ */
+ function updateAppUnreadCounter() {
+ Rambox.app.setTotalNotifications(totalUnreadCount);
+ }
+
+ /**
+ * Returns the global unread count.
+ *
+ * @return {number}
+ */
+ this.getTotalUnreadCount = function() {
+ return totalUnreadCount;
+ };
+
+ /**
+ * Sets the global unread count for a specific service.
+ *
+ * @param {*} id Id of the service to set the global unread count for.
+ * @param {number} unreadCount The global unread count for the service.
+ */
+ this.setUnreadCountForService = function(id, unreadCount) {
+ unreadCount = parseInt(unreadCount, 10);
+
+ if (unreadCountByService.has(id)) {
+ totalUnreadCount -= unreadCountByService.get(id);
+ }
+ totalUnreadCount += unreadCount;
+ unreadCountByService.set(id, unreadCount);
+
+ updateAppUnreadCounter();
+ };
+
+ /**
+ * Clears the global unread count for a specific service.
+ *
+ * @param {*} id Id of the service to clear the global unread count for.
+ */
+ this.clearUnreadCountForService = function(id) {
+ if (unreadCountByService.has(id)) {
+ totalUnreadCount -= unreadCountByService.get(id);
+ }
+ unreadCountByService['delete'](id);
+
+ updateAppUnreadCounter();
+ }
+ }
+});
diff --git a/app/ux/WebView.js b/app/ux/WebView.js
index e681b331..8cdfa066 100644
--- a/app/ux/WebView.js
+++ b/app/ux/WebView.js
@@ -6,14 +6,17 @@ Ext.define('Rambox.ux.WebView',{
,xtype: 'webview'
,requires: [
- 'Rambox.util.Format'
- ]
+ 'Rambox.util.Format',
+ 'Rambox.util.Notifier',
+ 'Rambox.util.UnreadCounter'
+ ],
// private
- ,zoomLevel: 0
+ zoomLevel: 0,
+ currentUnreadCount: 0,
// CONFIG
- ,hideMode: 'offsets'
+ hideMode: 'offsets'
,initComponent: function(config) {
var me = this;
@@ -36,16 +39,15 @@ Ext.define('Rambox.ux.WebView',{
Ext.apply(me, {
items: me.webViewConstructor()
,title: me.record.get('name')
- ,icon: me.record.get('type') === 'custom' ? (me.record.get('logo') === '' ? 'resources/icons/custom.png' : me.record.get('logo')) : 'resources/icons/'+me.record.get('logo')
- ,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')
+ ,icon: me.record.get('type') === 'custom' ? (me.record.get('logo') === '' ? 'resources/icons/custom.png' : me.record.get('logo')) : 'resources/icons/'+me.record.get('logo')
+ ,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: {
- badgetextchange: me.onBadgeTextChange
- ,afterrender : function( btn ) {
+ afterrender : function( btn ) {
btn.el.on('contextmenu', function(e) {
btn.showMenu('contextmenu');
e.stopEvent();
@@ -158,7 +160,8 @@ Ext.define('Rambox.ux.WebView',{
,autosize: 'on'
,disablewebsecurity: 'on'
,blinkfeatures: 'ApplicationCache,GlobalCacheStorage'
- ,useragent: Ext.getStore('ServicesList').getById(me.record.get('type')).get('userAgent')
+ ,useragent: Ext.getStore('ServicesList').getById(me.record.get('type')).get('userAgent'),
+ preload: './resources/js/rambox-service-api.js'
}
};
@@ -168,42 +171,6 @@ Ext.define('Rambox.ux.WebView',{
return cfg;
}
- ,onBadgeTextChange: function( tab, badgeText, oldBadgeText ) {
- var me = this;
- if ( oldBadgeText === null ) oldBadgeText = 0;
- var actualNotifications = Rambox.app.getTotalNotifications();
-
- oldBadgeText = Rambox.util.Format.stripNumber(oldBadgeText);
- badgeText = Rambox.util.Format.stripNumber(badgeText);
-
- Rambox.app.setTotalNotifications(actualNotifications - oldBadgeText + badgeText);
-
- // Some services dont have Desktop Notifications, so we add that functionality =)
- if ( Ext.getStore('ServicesList').getById(me.type).get('manual_notifications') && oldBadgeText < badgeText && me.record.get('notifications') && !JSON.parse(localStorage.getItem('dontDisturb')) ) {
- var text;
- switch ( Ext.getStore('ServicesList').getById(me.type).get('type') ) {
- case 'messaging':
- text = 'You have ' + Ext.util.Format.plural(badgeText, 'new message', 'new messages') + '.';
- break;
- case 'email':
- text = 'You have ' + Ext.util.Format.plural(badgeText, 'new email', 'new emails') + '.';
- break;
- default:
- text = 'You have ' + Ext.util.Format.plural(badgeText, 'new activity', 'new activities') + '.';
- break;
- }
- var not = new Notification(me.record.get('name'), {
- body: text
- ,icon: tab.icon
- ,silent: me.record.get('muted')
- });
- not.onclick = function() {
- require('electron').remote.getCurrentWindow().show();
- Ext.cq1('app-main').setActiveTab(me);
- };
- }
- }
-
,onAfterRender: function() {
var me = this;
@@ -274,7 +241,7 @@ Ext.define('Rambox.ux.WebView',{
// Injected code to detect new messages
if ( me.record ) {
- var js_unread = Ext.getStore('ServicesList').getById(me.record.get('type') === 'office365' ? 'outlook365' : me.record.get('type')).get('js_unread');
+ var js_unread = 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');
@@ -296,26 +263,129 @@ Ext.define('Rambox.ux.WebView',{
webview.executeJavaScript('document.body.scrollTop=0;');
});
- webview.addEventListener("page-title-updated", function(e) {
- var count = e.title.match(/\(([^)]+)\)/); // Get text between (...)
+ webview.addEventListener('ipc-message', function(event) {
+ var channel = event.channel;
+ switch (channel) {
+ case 'rambox.setUnreadCount':
+ handleSetUnreadCount(event);
+ break;
+ case 'rambox.clearUnreadCount':
+ handleClearUnreadCount(event);
+ break;
+ }
+
+ /**
+ * Handles 'rambox.clearUnreadCount' messages.
+ * Clears the unread count.
+ */
+ function handleClearUnreadCount() {
+ me.tab.setBadgeText('');
+ }
+
+ /**
+ * Handles 'rambox.setUnreadCount' messages.
+ * Sets the badge text if the event contains an integer 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)) {
+ me.tab.setBadgeText(Rambox.util.Format.formatNumber(count));
+ }
+ }
+ }
+ });
+
+ /**
+ * Register page title update event listener only for services that don't prevent it by setting 'dont_update_unread_from_title' to true.
+ */
+ if (Ext.getStore('ServicesList').getById(me.record.get('type')).get('dont_update_unread_from_title') !== true) {
+ 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.tab.setBadgeText(Rambox.util.Format.formatNumber(count));
- });
+ me.setUnreadCount(count);
+ });
+ }
webview.addEventListener('did-get-redirect-request', function( e ) {
if ( e.isMainFrame ) webview.loadURL(e.newURL);
});
- }
- ,reloadService: function(btn) {
+ if(ipc.sendSync('getConfig').spellcheck) {
+ var webFrame = require('electron').webFrame;
+ var SpellCheckProvider = require('electron-spell-check-provider');
+ webFrame.setSpellCheckProvider('en-US', true, new SpellCheckProvider('en-US'));
+ }
+ },
+
+ setUnreadCount: function(newUnreadCount) {
+ var me = this;
+
+ if (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));
+
+ /**
+ * Dispatch manual notification if
+ * • service doesn't have notifications, so Rambox does them
+ * • count increased
+ * • not in dnd mode
+ * • notifications enabled
+ */
+ if (Ext.getStore('ServicesList').getById(me.type).get('manual_notifications') &&
+ me.currentUnreadCount < newUnreadCount &&
+ me.record.get('notifications') &&
+ !JSON.parse(localStorage.getItem('dontDisturb'))) {
+ Rambox.util.Notifier.dispatchNotification(me, newUnreadCount);
+ }
+
+ me.currentUnreadCount = newUnreadCount;
+ },
+
+ refreshUnreadCount: function() {
+ this.setUnreadCount(this.currentUnreadCount);
+ },
+
+ /**
+ * 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.down('component').el.dom;
if ( me.record.get('enabled') ) {
- me.tab.setBadgeText('');
+ me.clearUnreadCounter();
webview.loadURL(me.src);
}
}
@@ -361,7 +431,8 @@ Ext.define('Rambox.ux.WebView',{
,setEnabled: function(enabled) {
var me = this;
- me.tab.setBadgeText('');
+ me.clearUnreadCounter();
+
me.removeAll();
me.add(me.webViewConstructor(enabled));
if ( enabled ) {
diff --git a/app/view/add/Add.js b/app/view/add/Add.js
index 8e32e81f..26790f9b 100644
--- a/app/view/add/Add.js
+++ b/app/view/add/Add.js
@@ -162,8 +162,31 @@ Ext.define('Rambox.view.add.Add',{
,inputValue: true
}
]
- }
- ,{
+ },
+ {
+ xtype: 'fieldset',
+ title: 'Unread counter',
+ margin: '10 0 0 0',
+ items: [
+ {
+ xtype: 'checkbox',
+ boxLabel: 'Display tab unread counter',
+ name: 'displayTabUnreadCounter',
+ checked: me.edit ? me.record.get('displayTabUnreadCounter') : true,
+ uncheckedValue: false,
+ inputValue: true
+ },
+ {
+ xtype: 'checkbox',
+ boxLabel: 'Include in global unread counter',
+ name: 'includeInGlobalUnreadCounter',
+ checked: me.edit ? me.record.get('includeInGlobalUnreadCounter') : true,
+ uncheckedValue: false,
+ inputValue: true
+ }
+ ]
+ },
+ {
xtype: 'fieldset'
,title: 'Advanced'
,margin: '10 0 0 0'
diff --git a/app/view/add/AddController.js b/app/view/add/AddController.js
index 97ce0a73..5c4e6cbb 100644
--- a/app/view/add/AddController.js
+++ b/app/view/add/AddController.js
@@ -1,8 +1,12 @@
Ext.define('Rambox.view.add.AddController', {
- extend: 'Ext.app.ViewController'
- ,alias: 'controller.add-add'
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.add-add',
- ,doCancel: function( btn ) {
+ requires: [
+ 'Rambox.util.UnreadCounter'
+ ],
+
+ doCancel: function( btn ) {
var me = this;
me.getView().close();
@@ -29,37 +33,44 @@ Ext.define('Rambox.view.add.AddController', {
,url: formValues.url
,align: formValues.align
,notifications: formValues.notifications
- ,muted: formValues.muted
- ,trust: formValues.trust
+ ,muted: formValues.muted,
+ displayTabUnreadCounter: formValues.displayTabUnreadCounter,
+ includeInGlobalUnreadCounter: formValues.includeInGlobalUnreadCounter,
+ trust: formValues.trust
,js_unread: formValues.js_unread
});
+
+ var view = Ext.getCmp('tab_'+win.record.get('id'));
+
// Change the title of the Tab
- Ext.getCmp('tab_'+win.record.get('id')).setTitle(formValues.serviceName);
+ view.setTitle(formValues.serviceName);
// Change sound of the Tab
- Ext.getCmp('tab_'+win.record.get('id')).setAudioMuted(formValues.muted);
+ view.setAudioMuted(formValues.muted);
// Change notifications of the Tab
- Ext.getCmp('tab_'+win.record.get('id')).setNotifications(formValues.notifications);
+ view.setNotifications(formValues.notifications);
// Change the icon of the Tab
if ( win.record.get('type') === 'custom' && oldData.logo !== formValues.logo ) Ext.getCmp('tab_'+win.record.get('id')).setConfig('icon', formValues.logo === '' ? 'resources/icons/custom.png' : formValues.logo);
// Change the URL of the Tab
- if ( oldData.url !== formValues.url ) Ext.getCmp('tab_'+win.record.get('id')).setURL(formValues.url);
+ if ( oldData.url !== formValues.url ) view.setURL(formValues.url);
// Change the align of the Tab
if ( oldData.align !== formValues.align ) {
if ( formValues.align === 'left' ) {
- Ext.cq1('app-main').moveBefore(Ext.getCmp('tab_'+win.record.get('id')), Ext.getCmp('tbfill'));
+ Ext.cq1('app-main').moveBefore(view, Ext.getCmp('tbfill'));
} else {
- Ext.cq1('app-main').moveAfter(Ext.getCmp('tab_'+win.record.get('id')), Ext.getCmp('tbfill'));
+ Ext.cq1('app-main').moveAfter(view, Ext.getCmp('tbfill'));
}
}
// Apply the JS Code of the Tab
if ( win.down('textarea').isDirty() ) {
Ext.Msg.confirm('CUSTOM CODE', 'Rambox needs to reload the service to execute the new JavaScript code. Do you want to do it now?', function( btnId ) {
- if ( btnId === 'yes' ) Ext.getCmp('tab_'+win.record.get('id')).reloadService();
+ if ( btnId === 'yes' ) view.reloadService();
});
}
- Ext.getCmp('tab_'+win.record.get('id')).record = win.record;
- Ext.getCmp('tab_'+win.record.get('id')).tabConfig.service = win.record;
+ view.record = win.record;
+ view.tabConfig.service = win.record;
+
+ view.refreshUnreadCount();
} else {
// Format data
if ( win.record.get('url').indexOf('___') >= 0 ) {
@@ -73,8 +84,10 @@ Ext.define('Rambox.view.add.AddController', {
,url: formValues.url
,align: formValues.align
,notifications: formValues.notifications
- ,muted: formValues.muted
- ,trust: formValues.trust
+ ,muted: formValues.muted,
+ displayTabUnreadCounter: formValues.displayTabUnreadCounter,
+ includeInGlobalUnreadCounter: formValues.includeInGlobalUnreadCounter,
+ trust: formValues.trust
,js_unread: formValues.js_unread
});
service.save();
@@ -121,5 +134,4 @@ Ext.define('Rambox.view.add.AddController', {
// Make focus to the name field
win.down('textfield[name="serviceName"]').focus(true, 100);
}
-
});
diff --git a/app/view/preferences/Preferences.js b/app/view/preferences/Preferences.js
index 749340a1..42cd0a8e 100644
--- a/app/view/preferences/Preferences.js
+++ b/app/view/preferences/Preferences.js
@@ -67,17 +67,28 @@ Ext.define('Rambox.view.preferences.Preferences',{
,boxLabel: 'Show in Taskbar'
,value: config.skip_taskbar
,reference: 'skipTaskbar'
- ,hidden: process.platform !== 'win32'
- }
- ,{
- xtype: 'checkbox'
- ,name: 'keep_in_taskbar_on_close'
- ,boxLabel: 'Keep Rambox in the Taskbar when close it'
- ,value: config.keep_in_taskbar_on_close
- ,bind: { disabled: '{!skipTaskbar.checked}' }
- ,hidden: process.platform !== 'win32'
- }
- ,{
+ ,hidden: process.platform === 'darwin'
+ },
+ {
+ xtype: 'combo',
+ name: 'window_close_behavior',
+ fieldLabel: 'When closing the main window',
+ labelAlign: 'top',
+ 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': 'Keep in tray' },
+ { 'value': 'keep_in_tray_and_taskbar', 'label': 'Keep in tray and taskbar' },
+ { 'value': 'quit', 'label': 'Quit' }
+ ]
+ }),
+ hidden: process.platform === 'darwin'
+ },
+ {
xtype: 'checkbox'
,name: 'always_on_top'
,boxLabel: 'Always on top'
@@ -96,6 +107,12 @@ Ext.define('Rambox.view.preferences.Preferences',{
,boxLabel: 'Disable Hardware Acceleration (needs to relaunch)'
,value: config.disable_gpu
}
+ ,{
+ xtype: 'checkbox'
+ ,name: 'spellcheck'
+ ,boxLabel: 'Enable spellcheck (en_US)'
+ ,value: config.spellcheck
+ }
,{
xtype: 'fieldset'
,title: 'Master Password - Ask for password on startup'
diff --git a/app/view/preferences/PreferencesController.js b/app/view/preferences/PreferencesController.js
index b635e075..c90e9aec 100644
--- a/app/view/preferences/PreferencesController.js
+++ b/app/view/preferences/PreferencesController.js
@@ -13,12 +13,28 @@ Ext.define('Rambox.view.preferences.PreferencesController', {
var values = me.getView().down('form').getForm().getFieldValues();
- // Master Password
- if ( values.master_password && (Ext.isEmpty(values.master_password1) || Ext.isEmpty(values.master_password2)) ) return;
- if ( values.master_password && (values.master_password1 !== values.master_password2) ) return;
- if ( values.master_password ) values.master_password = Rambox.util.MD5.encypt(values.master_password1);
- delete values.master_password1;
- delete values.master_password2;
+ // master password activated and only one of the fields "password" or "password confirmation" filled
+ if (values.master_password === true &&
+ (Ext.isEmpty(values.master_password1) === false && Ext.isEmpty(values.master_password2) === true ||
+ Ext.isEmpty(values.master_password1) === true && Ext.isEmpty(values.master_password2) === false)) return;
+
+ // password and confirmation don't match
+ if (values.master_password === true && (values.master_password1 !== values.master_password2)) return;
+
+ // master password activated and changed
+ if (values.master_password === true &&
+ Ext.isEmpty(values.master_password1) === false &&
+ Ext.isEmpty(values.master_password2) === false) {
+
+ values.master_password = Rambox.util.MD5.encypt(values.master_password1);
+ delete values.master_password1;
+ delete values.master_password2;
+ }
+
+ // prevent overwriting password when unchanged
+ if (values.master_password === true) {
+ delete values.master_password;
+ }
// Proxy
if ( values.proxy && (Ext.isEmpty(values.proxyHost) || Ext.isEmpty(values.proxyPort)) ) return;
diff --git a/electron/main.js b/electron/main.js
index a261020d..8d1cfeae 100644
--- a/electron/main.js
+++ b/electron/main.js
@@ -24,12 +24,12 @@ const config = new Config({
,hide_menu_bar: false
,skip_taskbar: true
,auto_launch: !isDev
- // On Linux false because it's uncommon for apps on linux to stay in the taskbar on close
- ,keep_in_taskbar_on_close: process.platform !== 'linux'
+ ,window_close_behavior: 'keep_in_tray'
,start_minimized: false
,systemtray_indicator: true
,master_password: false
,disable_gpu: process.platform === 'linux'
+ ,spellcheck: false
,proxy: false
,proxyHost: ''
,proxyPort: ''
@@ -44,7 +44,7 @@ const config = new Config({
// Configure AutoLaunch
const appLauncher = new AutoLaunch({
- name: 'Rambox'
+ name: process.platform === 'darwin' ? 'Rambox.app' : 'Rambox'
,isHiddenOnLaunch: config.get('start_minimized')
});
config.get('auto_launch') && !isDev ? appLauncher.enable() : appLauncher.disable();
@@ -199,11 +199,19 @@ function createWindow () {
app.hide();
break;
case 'linux':
- config.get('keep_in_taskbar_on_close') ? mainWindow.hide() : app.quit();
- break;
case 'win32':
default:
- config.get('keep_in_taskbar_on_close') ? mainWindow.minimize() : mainWindow.hide();
+ switch (config.get('window_close_behavior')) {
+ case 'keep_in_tray':
+ mainWindow.hide();
+ break;
+ case 'keep_in_tray_and_taskbar':
+ mainWindow.minimize();
+ break;
+ case 'quit':
+ app.quit();
+ break;
+ }
break;
}
}
diff --git a/overrides/layout/container/boxOverflow/Scroller.js b/overrides/layout/container/boxOverflow/Scroller.js
new file mode 100644
index 00000000..69682125
--- /dev/null
+++ b/overrides/layout/container/boxOverflow/Scroller.js
@@ -0,0 +1,34 @@
+/**
+ * Per default scrolling the tab bar moves the tabs 20 pixels.
+ * To improve the usability of the tab bar this value is increased for Rambox.
+ * Also animations are enabled, so the user understands what's going on.
+ */
+Ext.define('Rambox.overrides.layout.container.boxOverflow.Scroller', {
+ override: 'Ext.layout.container.boxOverflow.Scroller',
+
+ scrollIncrement: 250,
+ wheelIncrement: 50,
+
+ animateScroll: true,
+ scrollDuration: 250,
+
+ /**
+ * In difference to the overridden function this one enables scroll animations.
+ *
+ * @private
+ * Scrolls to the left by the configured amount
+ */
+ scrollLeft: function() {
+ this.scrollBy(-this.scrollIncrement);
+ },
+
+ /**
+ * In difference to the overridden function this one enables scroll animations.
+ *
+ * @private
+ * Scrolls to the right by the configured amount
+ */
+ scrollRight: function() {
+ this.scrollBy(this.scrollIncrement);
+ }
+});
diff --git a/package.json b/package.json
index 707916a1..48abccde 100644
--- a/package.json
+++ b/package.json
@@ -88,17 +88,18 @@
"app": "build/production/Rambox/"
},
"devDependencies": {
- "asar": "^0.12.1",
- "chai": "3.5.0",
- "electron": "1.4.7",
- "electron-builder": "6.5.2",
- "electron-squirrel-startup": "^1.0.0",
- "mocha": "3.2.0",
+ "asar": "^0.12.1",
+ "electron": "1.4.14",
+ "electron-builder": "11.3.0",
+ "electron-squirrel-startup": "^1.0.0",
+ "chai": "3.5.0",
+ "mocha": "3.2.0",
"spectron": "3.4.0"
},
"dependencies": {
"auto-launch": "4.0.0",
"firebase": "^3.0.5",
+ "electron-spell-check-provider": "^1.0.0",
"firebase-token-generator": "^2.0.0",
"tmp": "0.0.28",
"mime": "^1.3.4",
diff --git a/resources/icons/workplace.png b/resources/icons/workplace.png
new file mode 100644
index 00000000..8f402050
Binary files /dev/null and b/resources/icons/workplace.png differ
diff --git a/resources/icons/xing.png b/resources/icons/xing.png
new file mode 100644
index 00000000..070b8eb0
Binary files /dev/null and b/resources/icons/xing.png differ
diff --git a/resources/js/rambox-service-api.js b/resources/js/rambox-service-api.js
new file mode 100644
index 00000000..c37517dd
--- /dev/null
+++ b/resources/js/rambox-service-api.js
@@ -0,0 +1,28 @@
+/**
+ * This file is loaded in the service web views to provide a Rambox API.
+ */
+
+const { ipcRenderer } = require('electron');
+
+/**
+ * Make the Rambox API available via a global "rambox" variable.
+ *
+ * @type {{}}
+ */
+window.rambox = {};
+
+/**
+ * Sets the unraed count of the tab.
+ *
+ * @param {*} count The unread count
+ */
+window.rambox.setUnreadCount = function(count) {
+ ipcRenderer.sendToHost('rambox.setUnreadCount', count);
+};
+
+/**
+ * Clears the unread count.
+ */
+window.rambox.clearUnreadCount = function() {
+ ipcRenderer.sendToHost('rambox.clearUnreadCount');
+}