Compare commits

...

68 Commits

Author SHA1 Message Date
akashnimare
e3a622fc07 🎉 v1.2.0-beta 2017-07-12 15:57:56 +05:30
akashnimare
f39839618d open /apps, /api, /integration page in browser instead of app 2017-07-12 15:12:58 +05:30
akashnimare
3f93a3346f Allow server which is signed by root cert WIP #150
App used to throw an error because it assumes that the certificate is invalid or not signed properly.
The solution of this problem is same as self-signed certificate fix which is we simply show a warning dialog asking user if they trust the certificate.
2017-07-12 00:45:44 +05:30
Akash Nimare
9ab75b9800 Merge pull request #202 from geeeeeeeeek/feature/save-server-icon
Save server icon && Provide keyboard shortcut to switch between servers.
2017-07-11 10:30:09 -07:00
Zhongyi Tong
f600c4db0e Eliminate the race condition of pipe and reload. 2017-07-11 11:57:17 +08:00
Zhongyi Tong
4e5816697e Enhance the error handling of icon download. 2017-07-11 11:34:51 +08:00
Zhongyi Tong
dc3c446a46 Put local shortcuts back. 2017-07-11 11:12:39 +08:00
Zhongyi Tong
4cd8efa396 Fix linting errors. 2017-07-11 00:54:51 +08:00
Zhongyi Tong
541ba335ae Save server icon to a local folder. 2017-07-11 00:47:30 +08:00
Zhongyi Tong
3e226400c4 Unregister local shortcuts after reload. 2017-07-10 22:37:22 +08:00
Zhongyi Tong
890d7caea5 Provide keyboard shortcut to switch between servers. 2017-07-10 14:40:02 +08:00
akashnimare
45e7993d0c destroy tray on reload 2017-07-10 03:24:28 +05:30
Akash Nimare
8210a7c472 Merge pull request #200 from geeeeeeeeek/feature/move-add-server-button-up
Add the ability to open Settings with specific view
2017-07-09 14:49:50 -07:00
Zhongyi Tong
9e6bb1b48f Fix stying. 2017-07-09 23:08:59 +08:00
Zhongyi Tong
d3c2da7961 Support switching Settings view from different action buttons. 2017-07-09 23:01:46 +08:00
Zhongyi Tong
a5017456f2 Move add server buttom to above area. 2017-07-09 20:29:40 +08:00
akashnimare
76cd62d0c8 Added desktop notification support on windows 8 fixes #199 2017-07-08 04:43:16 +05:30
akashnimare
6b68217494 Unregister shortcut on window close
this will make sure that app's keyboard shortcut such as CTRL+R etc won't interfare
2017-07-08 03:06:32 +05:30
akashnimare
8148d83448 Add sound settings [WIP] #186 2017-07-07 00:08:14 +05:30
akashnimare
a55cda3b1f Added a warning dialog for deleting server 2017-07-06 19:23:35 +05:30
akashnimare
e381960206 hide Existing servers if there is no server 2017-07-06 00:17:12 +05:30
Akash Nimare
091b641abb Merge pull request #197 from zulip/dev
Add beta update option in settings page #192
2017-07-05 08:33:25 -07:00
akashnimare
af0ec80998 Refinements on settings template 2017-07-05 20:56:07 +05:30
akashnimare
5653a38d9b init betaupdates to false 2017-07-05 18:26:04 +05:30
Akash Nimare
01ea3beb99 Merge pull request #196 from geeeeeeeeek/dev
Reload db after update.
2017-07-05 05:53:01 -07:00
Zhongyi Tong
cd387aaf9c Reload db after update. 2017-07-05 20:41:01 +08:00
akashnimare
24b5e0412b changed betaUpdate >> BetaUpdate 2017-07-05 17:15:38 +05:30
Akash Nimare
8174f7b37e Merge pull request #195 from geeeeeeeeek/dev
Change config filename to avoid conflict.
2017-07-05 04:31:48 -07:00
Zhongyi Tong
d3a5eceaf9 Change config filename to avoid conflict. 2017-07-05 19:25:34 +08:00
Akash Nimare
31f285eba9 Merge pull request #194 from geeeeeeeeek/dev
Fix defaultValue not set on the first time.
2017-07-05 04:19:44 -07:00
Zhongyi Tong
b1365f9669 Fix defaultValue not set on the first time. 2017-07-05 18:58:25 +08:00
akashnimare
21351125fa update betaupdate settings 2017-07-05 16:00:43 +05:30
Akash Nimare
50b9ec7220 Merge pull request #193 from geeeeeeeeek/dev
Make ConfigUtil runnable in both processes.
2017-07-05 03:16:54 -07:00
Zhongyi Tong
46cf5c9a5c Make ConfigUtil runnable in both processes. 2017-07-05 18:05:50 +08:00
akashnimare
35a42f3873 Allow auto update for prerelease based on betaupdates setting 2017-07-05 03:12:01 +05:30
akashnimare
14b9fcf8d7 add settings for beta updates [WIP] #173 2017-07-05 03:05:02 +05:30
Akash Nimare
47cfe86a06 Merge pull request #190 from geeeeeeeeek/feature/settings-page
Feature/settings page
2017-07-02 11:43:14 -07:00
Zhongyi Tong
57ad2d63e0 Fix a styling bug. 2017-07-03 02:11:21 +08:00
Zhongyi Tong
2fcc5d9649 Fix linting errors. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
45523f41aa Add tray options in settings page. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
15b3af7b97 Update tray script to support shown/hidden state on start-up. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
81d3aa8a1b Add ConfigUtil to manage config items. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
321860a232 Disable preload script in functional tab. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
1c290fc2cd Add the ability to forward message from renderer to renderer. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
a74b17b989 Finish the framework of settings. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
ffba6b68f8 Extract Nav from Preference. 2017-07-03 02:02:43 +08:00
akashnimare
b553b29328 ignore docs folder while building 2017-07-01 20:56:22 +05:30
akashnimare
d7b44b23d1 add new release checklist [WIP] 2017-07-01 20:54:52 +05:30
akashnimare
b991fac136 destroy multiple tray icons on reload - fixes #191 2017-07-01 12:22:59 +05:30
Akash Nimare
cda8aa3b09 Merge pull request #189 from geeeeeeeeek/issue/close-settings-bug
Fix close settings causing split webviews (#188).
2017-06-28 08:48:06 -07:00
Zhongyi Tong
1e60643ae9 Fix multiple settings tabs. 2017-06-28 20:37:31 +08:00
Zhongyi Tong
d9fbcaf38e Fix linting errors. 2017-06-28 00:47:42 +08:00
Zhongyi Tong
9e5a67c36b Fix close settings causing split webviews (#188). 2017-06-28 00:44:23 +08:00
akashnimare
a16181be33 🎉 v1.1.0-beta 2017-06-23 17:39:15 +05:30
akashnimare
cd0a7741b1 styling save-server-action button 2017-06-23 17:37:28 +05:30
akashnimare
6da523a18b Remove checkbox and redesign save-server-action 2017-06-23 17:24:37 +05:30
akashnimare
86302308a9 Enable quit app using CTRL+Q on windows as well 2017-06-22 02:44:34 +05:30
akashnimare
b0294db133 Allow global return + use script as sourceType 2017-06-22 00:53:35 +05:30
akashnimare
252586cf71 fix module error #185 2017-06-22 00:52:29 +05:30
Akash Nimare
52ea1a48fd Merge pull request #184 from geeeeeeeeek/issue/network-error-page
Improve network error display.
2017-06-21 06:55:55 -07:00
Zhongyi Tong
98e73f807c Reload the app when users try to resume the connection. 2017-06-21 20:47:40 +08:00
Zhongyi Tong
f96dd6e6bc Fix linting error. 2017-06-21 20:11:58 +08:00
Zhongyi Tong
1511ce4610 Reconnect app when network comes back. 2017-06-21 20:10:58 +08:00
Akash Nimare
c2db6fc0f0 updated multi team screenshot 2017-06-21 17:35:58 +05:30
Zhongyi Tong
937a193a61 Fix linting errors. 2017-06-21 15:59:09 +08:00
Zhongyi Tong
3823ac7f78 Improve network error display. 2017-06-21 15:56:05 +08:00
akashnimare
878cc3fe82 focus window onclicking tray menus #fixes #183 2017-06-20 15:19:57 +05:30
akashnimare
eace637f29 Toggle window on clicking tray icon [Windows only] 2017-06-20 15:00:43 +05:30
33 changed files with 1046 additions and 274 deletions

View File

@@ -5,7 +5,7 @@
Desktop client for Zulip. Available for Mac, Linux and Windows.
<img src="http://i.imgur.com/bDtK47q.png"/>
<img src="http://i.imgur.com/ChzTq4F.png"/>
# Download
You can download the latest version from the [Releases](https://github.com/zulip/zulip-electron/releases/latest) page.

View File

@@ -2,12 +2,15 @@
const {app, dialog} = require('electron');
const {autoUpdater} = require('electron-updater');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
function appUpdater() {
// Log whats happening
const log = require('electron-log');
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.allowPrerelease = false;
// Handle auto updates for beta/pre releases
autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false;
// Ask the user if update is available
// eslint-disable-next-line no-unused-vars

View File

@@ -35,7 +35,7 @@ const isAlreadyRunning = app.makeSingleInstance(() => {
});
if (isAlreadyRunning) {
app.quit();
return app.quit();
}
function isWindowsOrmacOS() {
@@ -86,6 +86,9 @@ function createMainWindow() {
win.hide();
}
}
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
win.setTitle('Zulip');
@@ -135,6 +138,23 @@ function createMainWindow() {
return win;
}
function registerLocalShortcuts(page) {
// TODO - use global shortcut instead
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
// page.send('reload');
mainWindow.reload();
page.send('destroytray');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
page.send('back');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
page.send('forward');
});
}
// eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
@@ -158,20 +178,7 @@ app.on('ready', () => {
const page = mainWindow.webContents;
// TODO - use global shortcut instead
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
// page.send('reload');
mainWindow.reload();
// page.send('destroytray');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
page.send('back');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
page.send('forward');
});
registerLocalShortcuts(page);
page.on('dom-ready', () => {
mainWindow.show();
@@ -186,7 +193,7 @@ app.on('ready', () => {
});
electron.powerMonitor.on('resume', () => {
mainWindow.reload();
mainWindow.webContents.send('destroytray');
page.send('destroytray');
});
ipc.on('focus-app', () => {
@@ -199,6 +206,17 @@ app.on('ready', () => {
ipc.on('reload-main', () => {
page.reload();
page.send('destroytray');
electronLocalshortcut.unregisterAll(mainWindow);
registerLocalShortcuts(page);
});
ipc.on('toggle-app', () => {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
}
});
ipc.on('update-badge', (event, messageCount) => {
@@ -207,6 +225,18 @@ app.on('ready', () => {
}
page.send('tray', messageCount);
});
ipc.on('forward-message', (event, listener, ...params) => {
console.log(listener, ...params);
page.send(listener);
});
ipc.on('register-server-tab-shortcut', (event, index) => {
electronLocalshortcut.register(mainWindow, `CommandOrControl+${index}`, () => {
// Array index == Shown index - 1
page.send('switch-server-tab', index - 1);
});
});
});
app.on('will-quit', () => {

View File

@@ -143,7 +143,7 @@ const darwinTpl = [
type: 'separator'
},
{
label: 'Manage Zulip Servers',
label: 'Settings',
accelerator: 'Cmd+,',
click(item, focusedWindow) {
if (focusedWindow) {
@@ -280,7 +280,7 @@ const otherTpl = [
type: 'separator'
},
{
label: 'Manage Zulip Servers',
label: 'Settings',
accelerator: 'Ctrl+,',
click(item, focusedWindow) {
if (focusedWindow) {
@@ -322,7 +322,8 @@ const otherTpl = [
type: 'separator'
},
{
role: 'quit'
role: 'quit',
accelerator: 'Ctrl+Q'
}
]
},

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.0.0-beta",
"version": "1.2.0-beta",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"email": "<svnitakash@gmail.com>",

View File

@@ -24,6 +24,7 @@ html, body {
display: flex;
flex-direction: column;
-webkit-app-region: drag;
overflow: hidden;
}
@font-face {
@@ -81,7 +82,7 @@ html, body {
.tab {
position: relative;
margin: 5px 0;
margin: 2px 0;
cursor: pointer;
}
@@ -98,7 +99,9 @@ html, body {
.tab .server-tab {
background: #a4d3c4;
background-size: 100%;
background-size: 28px;
background-repeat: no-repeat;
background-position: center;
border-radius: 4px;
width: 35px;
height: 35px;
@@ -162,6 +165,19 @@ html, body {
line-height: 17px;
}
#add-tab {
display: flex;
align-items: center;
flex-direction: column;
}
.tab .server-tab-shortcut {
color: #eee;
font-size: 12px;
text-align: center;
font-family: sans-serif;
}
/*******************
* Webview Area *
*******************/

View File

@@ -0,0 +1,42 @@
html, body {
margin: 0;
cursor: default;
font-size: 14px;
color: #333;
background: #fff;
user-select:none;
}
#content {
display: flex;
flex-direction: column;
font-family: "Trebuchet MS", Helvetica, sans-serif;
margin: 100px 200px;
text-align: center;
}
#title {
font-size: 24px;
font-weight: bold;
margin: 20px 0;
}
#description {
font-size: 16px;
}
#reconnect {
font-size: 16px;
background: #009688;
color: #fff;
width: 84px;
height: 32px;
border-radius: 5px;
line-height: 32px;
margin: 20px auto 0;
cursor: pointer;
}
#reconnect:hover {
opacity: 0.8;
}

View File

@@ -1,10 +1,39 @@
html, body {
html,
body {
height: 100%;
margin: 0;
cursor: default;
font-size: 14px;
color: #333;
background: #fff;
background: #efefef;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
/* Preferred icon size */
font-size: 24px;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
}
#content {
@@ -21,23 +50,23 @@ html, body {
font-size: 16px;
}
#tabs-container {
#nav-container {
padding: 20px 0;
}
.tab {
.nav {
padding: 5px 0;
color: #999;
cursor: pointer;
}
.tab.active {
.nav.active {
color: #464e5a;
cursor: default;
position: relative;
}
.tab.active::before {
.nav.active::before {
background: #464e5a;
width: 3px;
height: 16px;
@@ -58,41 +87,52 @@ html, body {
overflow-y: scroll;
}
.server-info-container {
#new-server-container {
margin: 20px 0;
opacity: 1;
transition: opacity 0.3s;
}
.title {
padding: 4px 0 6px 0;
font-size: 18px;
color: #000;
font-weight: bold;
color: #1e1e1e;
}
.sub-title {
padding: 4px 0 6px 0;
font-weight: bold;
color: #616161;
}
img.server-info-icon {
background: #a4d3c4;
background-size: 100%;
border-radius: 4px;
width: 44px;
height: 44px;
width: 28px;
height: 28px;
padding: 8px;
}
.server-info-left {
margin-right: 20px;
margin: 10px 20px 0 0;
}
.server-info-right {
flex-grow: 1;
margin-right: 10px;
}
.server-info-row {
display: flex;
line-height: 26px;
height: 40px;
line-height: 27px;
margin: 8px 0;
height: 27px;
}
.server-info-key {
width: 40px;
margin-right: 20px;
font-weight: bold;
text-align: right;
}
@@ -101,14 +141,14 @@ img.server-info-icon {
font-size: 14px;
height: 24px;
border: none;
border-bottom: #ddd 1px solid;
border-bottom: #ededed 1px solid;
outline-width: 0;
background: transparent;
max-width: 500px;
}
.server-info-value:focus {
border-bottom: #b0d8ce 2px solid;
border-bottom: #388E3C 1px solid;
}
.actions-container {
@@ -123,12 +163,14 @@ img.server-info-icon {
.action {
display: flex;
align-items: center;
margin-right: 20px;
padding: 0 10px;
border-radius: 2px;
}
.action i {
margin-right: 5px;
font-size: 18px;
line-height: 27px;
}
.settings-pane {
@@ -147,16 +189,44 @@ img.server-info-icon {
color: #999;
}
.server-info.active {
background: #ecf4ef;
}
.server-info {
.settings-card {
display: flex;
padding: 10px;
margin: 10px 0 10px 0;
padding: 16px 30px;
margin: 10px 0 20px 0;
background: #fff;
border-radius: 2px;
width: 540px;
box-shadow: 1px 2px 4px #bcbcbc;
}
.hidden {
display: none;
height: 0 !important;
width: 0 !important;
margin: 5px !important;
opacity: 0 !important;
transition: opacity 0.3s;
}
.red {
color: #ef5350;
background: #ffebee;
border: 1px solid #ef5350;
}
.green {
color: #388E3C;
background: #E8F5E9;
border: 1px solid #388E3C;
}
.grey {
color: #9E9E9E;
background: #FAFAFA;
border: 1px solid #9E9E9E;
}
.setting-row {
display: flex;
justify-content: space-between;
width: 100%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,12 +1,16 @@
'use strict';
const Tab = require(__dirname + '/../components/tab.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const {ipcRenderer} = require('electron');
class ServerTab extends Tab {
template() {
return `<div class="tab">
<div class="server-tab-badge"></div>
<div class="server-tab" style="background-image: url(${this.props.icon});"></div>
<div class="server-tab" style="background-image: url('${this.props.icon}');"></div>
<div class="server-tab-shortcut">${this.generateShortcutText()}</div>
</div>`;
}
@@ -26,6 +30,27 @@ class ServerTab extends Tab {
this.$badge.classList.remove('active');
}
}
generateShortcutText() {
// Only provide shortcuts for server [0..10]
if (this.props.index >= 10) {
return '';
}
const shownIndex = this.props.index + 1;
let cmdKey = '';
if (SystemUtil.getOS() === 'Mac') {
cmdKey = '⌘';
} else {
cmdKey = '⌃';
}
ipcRenderer.send('register-server-tab-shortcut', shownIndex);
return `${cmdKey} ${shownIndex}`;
}
}
module.exports = ServerTab;

View File

@@ -43,7 +43,4 @@ class Tab extends BaseComponent {
}
}
Tab.SERVER_TAB = 0;
Tab.SETTINGS_TAB = 1;
module.exports = Tab;

View File

@@ -25,7 +25,7 @@ class WebView extends BaseComponent {
src="${this.props.url}"
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
disablewebsecurity
preload="js/preload.js"
${this.props.preload ? 'preload="js/preload.js"' : ''}
webpreferences="allowRunningInsecureContent, javascript=yes">
</webview>`;
}
@@ -64,7 +64,7 @@ class WebView extends BaseComponent {
const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0);
if (hasConnectivityErr) {
console.error('error', errorDescription);
this.checkConnectivity();
this.props.onNetworkError();
}
});
@@ -173,6 +173,10 @@ class WebView extends BaseComponent {
this.hide();
this.$el.reload();
}
send(...param) {
this.$el.send(...param);
}
}
module.exports = WebView;

View File

@@ -10,11 +10,11 @@ const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
class ServerManagerView {
constructor() {
this.$addServerButton = document.getElementById('add-tab');
this.$tabsContainer = document.getElementById('tabs-container');
const $actionsContainer = document.getElementById('actions-container');
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
this.$addServerButton = $actionsContainer.querySelector('#add-action');
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$content = document.getElementById('content');
@@ -37,7 +37,7 @@ class ServerManagerView {
}
this.activateTab(0);
} else {
this.openSettings();
this.openSettings('Servers');
}
}
@@ -46,6 +46,7 @@ class ServerManagerView {
icon: server.icon,
$root: this.$tabsContainer,
onClick: this.activateTab.bind(this, index),
index,
webview: new WebView({
$root: this.$content,
index,
@@ -54,8 +55,10 @@ class ServerManagerView {
isActive: () => {
return index === this.activeTabIndex;
},
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: false
nodeIntegration: false,
preload: true
})
}));
}
@@ -64,12 +67,16 @@ class ServerManagerView {
this.$reloadButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.reload();
});
this.$addServerButton.addEventListener('click', this.openSettings.bind(this));
this.$settingsButton.addEventListener('click', this.openSettings.bind(this));
this.$addServerButton.addEventListener('click', () => {
this.openSettings('Servers');
});
this.$settingsButton.addEventListener('click', () => {
this.openSettings('General');
});
}
openFunctionalTab(tabProps) {
if (this.functionalTabs[tabProps.name]) {
if (this.functionalTabs[tabProps.name] !== undefined) {
this.activateTab(this.functionalTabs[tabProps.name]);
return;
}
@@ -89,20 +96,23 @@ class ServerManagerView {
isActive: () => {
return this.functionalTabs[tabProps.name] === this.activeTabIndex;
},
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: true
nodeIntegration: true,
preload: false
})
}));
this.activateTab(this.functionalTabs[tabProps.name]);
}
openSettings() {
openSettings(nav = 'General') {
this.openFunctionalTab({
name: 'Settings',
materialIcon: 'settings',
url: `file://${__dirname}/preference.html`
url: `file://${__dirname}/preference.html#${nav}`
});
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
}
openAbout() {
@@ -113,6 +123,14 @@ class ServerManagerView {
});
}
openNetworkTroubleshooting() {
this.openFunctionalTab({
name: 'Network Troubleshooting',
materialIcon: 'network_check',
url: `file://${__dirname}/network.html`
});
}
activateTab(index, hideOldTab = true) {
if (this.tabs[index].loading) {
return;
@@ -140,7 +158,10 @@ class ServerManagerView {
delete this.tabs[index];
delete this.functionalTabs[name];
this.activateTab(0, false);
// Issue #188: If the functional tab was not focused, do not activate another tab.
if (this.activeTabIndex === index) {
this.activateTab(0, false);
}
}
updateBadge() {
@@ -179,8 +200,13 @@ class ServerManagerView {
});
}
ipcRenderer.on('open-settings', this.openSettings.bind(this));
ipcRenderer.on('open-settings', (event, settingNav) => {
this.openSettings(settingNav);
});
ipcRenderer.on('open-about', this.openAbout.bind(this));
ipcRenderer.on('switch-server-tab', (event, index) => {
this.activateTab(index);
});
}
}
@@ -188,3 +214,7 @@ window.onload = () => {
const serverManagerView = new ServerManagerView();
serverManagerView.init();
};
window.addEventListener('online', () => {
ipcRenderer.send('reload-main');
});

View File

@@ -0,0 +1,22 @@
'use strict';
const {remote} = require('electron');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const app = remote.app;
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
app.setAppUserModelId('org.zulip.zulip-electron');
const NativeNotification = window.Notification;
class SilentNotification extends NativeNotification {
constructor(title, opts) {
opts.silent = ConfigUtil.getConfigItem('silent') || false;
super(title, opts);
}
}
window.Notification = SilentNotification;

View File

@@ -0,0 +1,20 @@
'use strict';
const {ipcRenderer} = require('electron');
class NetworkTroubleshootingView {
constructor() {
this.$reconnectButton = document.getElementById('reconnect');
}
init() {
this.$reconnectButton.addEventListener('click', () => {
ipcRenderer.send('reload-main');
});
}
}
window.onload = () => {
const networkTroubleshootingView = new NetworkTroubleshootingView();
networkTroubleshootingView.init();
};

View File

@@ -1,144 +0,0 @@
'use strict';
const {ipcRenderer} = require('electron');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
class PreferenceView {
constructor() {
this.$newServerButton = document.getElementById('new-server-action');
this.$saveServerButton = document.getElementById('save-server-action');
this.$reloadServerButton = document.getElementById('reload-server-action');
this.$serverInfoContainer = document.querySelector('.server-info-container');
}
init() {
this.initServers();
this.initActions();
}
initServers() {
const servers = DomainUtil.getDomains();
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
this.initNewServerForm();
for (const i in servers) {
this.initServer(servers[i], i);
}
}
initServer(server, index) {
const {
alias,
url,
icon
} = server;
const serverInfoTemplate = `
<div class="server-info">
<div class="server-info-left">
<img class="server-info-icon" src="${icon}"/>
</div>
<div class="server-info-right">
<div class="server-info-row">
<span class="server-info-key">Name</span>
<input class="server-info-value" disabled value="${alias}"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Url</span>
<input class="server-info-value" disabled value="${url}"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Icon</span>
<input class="server-info-value" disabled value="${icon}"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Actions</span>
<div class="action server-info-value" id="delete-server-action-${index}">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
</div>`;
this.$serverInfoContainer.appendChild(this.insertNode(serverInfoTemplate));
document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => {
DomainUtil.removeDomain(index);
this.initServers();
// alert('Success. Reload to apply changes.');
ipcRenderer.send('reload-main');
this.$reloadServerButton.classList.remove('hidden');
});
}
initNewServerForm() {
const newServerFormTemplate = `
<div class="server-info active hidden">
<div class="server-info-left">
<img class="server-info-icon" src="https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png"/>
</div>
<div class="server-info-right">
<div class="server-info-row">
<span class="server-info-key">Name</span>
<input id="server-info-name" class="server-info-value" placeholder="(Required)"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Url</span>
<input id="server-info-url" spellcheck="false" class="server-info-value" placeholder="(Required)"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Icon</span>
<input id="server-info-icon" class="server-info-value" placeholder="(Optional)"/>
</div>
</div>
</div>
`;
this.$serverInfoContainer.appendChild(this.insertNode(newServerFormTemplate));
this.$newServerForm = document.querySelector('.server-info.active');
this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0];
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1];
this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2];
}
initActions() {
this.$newServerButton.addEventListener('click', () => {
this.$newServerForm.classList.remove('hidden');
this.$saveServerButton.classList.remove('hidden');
this.$newServerButton.classList.add('hidden');
});
this.$saveServerButton.addEventListener('click', () => {
DomainUtil.checkDomain(this.$newServerUrl.value).then(domain => {
const server = {
alias: this.$newServerAlias.value,
url: domain,
icon: this.$newServerIcon.value
};
DomainUtil.addDomain(server);
this.$saveServerButton.classList.add('hidden');
this.$newServerButton.classList.remove('hidden');
this.$newServerForm.classList.add('hidden');
this.initServers();
// alert('Success. Reload to apply changes.');
ipcRenderer.send('reload-main');
this.$reloadServerButton.classList.remove('hidden');
}, errorMessage => {
alert(errorMessage);
});
});
this.$reloadServerButton.addEventListener('click', () => {
ipcRenderer.send('reload-main');
});
}
insertNode(html) {
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
return wrapper.firstElementChild;
}
}
window.onload = () => {
const preferenceView = new PreferenceView();
preferenceView.init();
};

View File

@@ -0,0 +1,127 @@
'use strict';
const {ipcRenderer} = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
class GeneralSection extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="title">Tray Options</div>
<div id="tray-option-settings" class="settings-card">
<div class="setting-row">
<div class="setting-description">Show app icon in system tray</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">App updates</div>
<div id="betaupdate-option-settings" class="settings-card">
<div class="setting-row">
<div class="setting-description">Get Beta updates</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Desktop Notification</div>
<div id="silent-option-settings" class="settings-card">
<div class="setting-row">
<div class="setting-description">Mute all sounds from Zulip (requires reload)</div>
<div class="setting-control"></div>
</div>
</div>
</div>
`;
}
settingsOptionTemplate(settingOption) {
if (settingOption) {
return `
<div class="action green">
<span>On</span>
</div>
`;
} else {
return `
<div class="action red">
<span>Off</span>
</div>
`;
}
}
trayOptionTemplate(trayOption) {
this.settingsOptionTemplate(trayOption);
}
updateOptionTemplate(updateOption) {
this.settingsOptionTemplate(updateOption);
}
silentOptionTemplate(silentOption) {
this.settingsOptionTemplate(silentOption);
}
init() {
this.props.$root.innerHTML = this.template();
this.initTrayOption();
this.initUpdateOption();
this.initSilentOption();
}
initTrayOption() {
this.$trayOptionSettings = document.querySelector('#tray-option-settings .setting-control');
this.$trayOptionSettings.innerHTML = '';
const trayOption = ConfigUtil.getConfigItem('trayIcon', true);
const $trayOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(trayOption));
this.$trayOptionSettings.appendChild($trayOption);
$trayOption.addEventListener('click', () => {
const newValue = !ConfigUtil.getConfigItem('trayIcon');
ConfigUtil.setConfigItem('trayIcon', newValue);
ipcRenderer.send('forward-message', 'toggletray');
this.initTrayOption();
});
}
initUpdateOption() {
this.$updateOptionSettings = document.querySelector('#betaupdate-option-settings .setting-control');
this.$updateOptionSettings.innerHTML = '';
const updateOption = ConfigUtil.getConfigItem('betaUpdate', false);
const $updateOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(updateOption));
this.$updateOptionSettings.appendChild($updateOption);
$updateOption.addEventListener('click', () => {
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
ConfigUtil.setConfigItem('betaUpdate', newValue);
this.initUpdateOption();
});
}
initSilentOption() {
this.$silentOptionSettings = document.querySelector('#silent-option-settings .setting-control');
this.$silentOptionSettings.innerHTML = '';
const silentOption = ConfigUtil.getConfigItem('silent', false);
const $silentOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(silentOption));
this.$silentOptionSettings.appendChild($silentOption);
$silentOption.addEventListener('click', () => {
const newValue = !ConfigUtil.getConfigItem('silent');
ConfigUtil.setConfigItem('silent', newValue);
this.initSilentOption();
});
}
handleServerInfoChange() {
ipcRenderer.send('reload-main');
}
}
module.exports = GeneralSection;

View File

@@ -0,0 +1,67 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
class PreferenceNav extends BaseComponent {
constructor(props) {
super();
this.props = props;
this.navItems = ['General', 'Servers'];
this.init();
}
template() {
let navItemsTemplate = '';
for (const navItem of this.navItems) {
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${navItem}</div>`;
}
return `
<div>
<div id="settings-header">Settings</div>
<div id="nav-container">${navItemsTemplate}</div>
</div>
`;
}
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.registerListeners();
}
registerListeners() {
for (const navItem of this.navItems) {
const $item = document.getElementById(`nav-${navItem}`);
$item.addEventListener('click', () => {
this.props.onItemSelected(navItem);
});
}
}
select(navItemToSelect) {
for (const navItem of this.navItems) {
if (navItem === navItemToSelect) {
this.activate(navItem);
} else {
this.deactivate(navItem);
}
}
}
activate(navItem) {
const $item = document.getElementById(`nav-${navItem}`);
$item.classList.add('active');
}
deactivate(navItem) {
const $item = document.getElementById(`nav-${navItem}`);
$item.classList.remove('active');
}
}
module.exports = PreferenceNav;

View File

@@ -0,0 +1,77 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class NewServerForm extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-card" style="border: solid 1px #4CAF50;">
<div class="server-info-left">
<img class="server-info-icon" src="${__dirname + '../../../../img/icon.png'}"/>
</div>
<div class="server-info-right">
<div class="server-info-row">
<span class="server-info-key">Name</span>
<input class="server-info-value" placeholder="(Required)"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Url</span>
<input class="server-info-value" placeholder="(Required)"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Icon</span>
<input class="server-info-value" placeholder="(Optional)"/>
</div>
<div class="server-info-row">
<span class="server-info-key"></span>
<div class="action green server-save-action">
<i class="material-icons">check_box</i>
<span>Save</span>
</div>
</div>
</div>
</div>
`;
}
init() {
this.initForm();
this.initActions();
}
initForm() {
this.$newServerForm = this.generateNodeFromTemplate(this.template());
this.$saveServerButton = this.$newServerForm.getElementsByClassName('server-save-action')[0];
this.props.$root.innerHTML = '';
this.props.$root.appendChild(this.$newServerForm);
this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0];
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1];
this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2];
}
initActions() {
this.$saveServerButton.addEventListener('click', () => {
DomainUtil.checkDomain(this.$newServerUrl.value).then(domain => {
const server = {
alias: this.$newServerAlias.value,
url: domain,
icon: this.$newServerIcon.value
};
DomainUtil.addDomain(server).then(() => {
this.props.onChange(this.props.index);
});
}, errorMessage => {
alert(errorMessage);
});
});
}
}
module.exports = NewServerForm;

View File

@@ -0,0 +1,68 @@
'use strict';
const BaseComponent = require(__dirname + '/js/components/base.js');
const {ipcRenderer} = require('electron');
const Nav = require(__dirname + '/js/pages/preference/nav.js');
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js');
class PreferenceView extends BaseComponent {
constructor() {
super();
this.$sidebarContainer = document.getElementById('sidebar');
this.$settingsContainer = document.getElementById('settings-container');
}
init() {
this.nav = new Nav({
$root: this.$sidebarContainer,
onItemSelected: this.handleNavigation.bind(this)
});
this.setDefaultView();
this.registerIpcs();
}
setDefaultView() {
let nav = 'General';
const hasTag = window.location.hash;
if (hasTag) {
nav = hasTag.substring(1);
}
this.handleNavigation(nav);
}
handleNavigation(navItem) {
this.nav.select(navItem);
switch (navItem) {
case 'Servers': {
this.section = new ServersSection({
$root: this.$settingsContainer
});
break;
}
case 'General': {
this.section = new GeneralSection({
$root: this.$settingsContainer
});
break;
}
default: break;
}
this.section.init();
window.location.hash = `#${navItem}`;
}
registerIpcs() {
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
this.handleNavigation(navItem);
});
}
}
window.onload = () => {
const preferenceView = new PreferenceView();
preferenceView.init();
};

View File

@@ -0,0 +1,72 @@
'use strict';
const {dialog} = require('electron').remote;
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class ServerInfoForm extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-card">
<div class="server-info-left">
<img class="server-info-icon" src="${this.props.server.icon}"/>
</div>
<div class="server-info-right">
<div class="server-info-row">
<span class="server-info-key">Name</span>
<input class="server-info-value" disabled value="${this.props.server.alias}"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Url</span>
<input class="server-info-value" disabled value="${this.props.server.url}"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Icon</span>
<input class="server-info-value" disabled value="${this.props.server.icon}"/>
</div>
<div class="server-info-row">
<span class="server-info-key"></span>
<div class="action red server-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
</div>
`;
}
init() {
this.initForm();
this.initActions();
}
initForm() {
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
this.props.$root.appendChild(this.$serverInfoForm);
}
initActions() {
this.$deleteServerButton.addEventListener('click', () => {
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure you want to delete this server?'
}, response => {
if (response === 0) {
DomainUtil.removeDomain(this.props.index);
this.props.onChange(this.props.index);
}
});
});
}
}
module.exports = ServerInfoForm;

View File

@@ -0,0 +1,83 @@
'use strict';
const {ipcRenderer} = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js');
const NewServerForm = require(__dirname + '/new-server-form.js');
class ServersSection extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="title">Manage Servers</div>
<div class="actions-container">
<div class="action green" id="new-server-action">
<i class="material-icons">add_box</i>
<span>New Server</span>
</div>
</div>
<div id="new-server-container" class="hidden"></div>
<div class="sub-title" id="existing-servers"></div>
<div id="server-info-container"></div>
</div>
`;
}
init() {
this.initServers();
this.initActions();
}
initServers() {
this.props.$root.innerHTML = '';
const servers = DomainUtil.getDomains();
this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.getElementById('server-info-container');
this.$existingServers = document.getElementById('existing-servers');
this.$newServerContainer = document.getElementById('new-server-container');
this.$newServerButton = document.getElementById('new-server-action');
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
// Show Existing servers if servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing servers';
this.initNewServerForm();
for (const i in servers) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.handleServerInfoChange.bind(this)
}).init();
}
}
initNewServerForm() {
new NewServerForm({
$root: this.$newServerContainer,
onChange: this.handleServerInfoChange.bind(this)
}).init();
}
initActions() {
this.$newServerButton.addEventListener('click', () => {
this.$newServerContainer.classList.remove('hidden');
this.$newServerButton.classList.remove('green');
this.$newServerButton.classList.add('grey');
});
}
handleServerInfoChange() {
ipcRenderer.send('reload-main');
}
}
module.exports = ServersSection;

View File

@@ -1,5 +1,9 @@
'use strict';
const {ipcRenderer} = require('electron');
const {spellChecker} = require('./spellchecker');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
const logout = () => {
// Create the menu for the below
@@ -30,4 +34,9 @@ process.once('loaded', () => {
document.addEventListener('DOMContentLoaded', () => {
// Init spellchecker
spellChecker();
// redirect users to network troubleshooting page
document.querySelector('.restart_get_events_button').addEventListener('click', () => {
ipcRenderer.send('reload-main');
});
});

View File

@@ -1,3 +1,5 @@
'use strict';
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
function spellChecker() {

View File

@@ -9,6 +9,8 @@ const {Tray, Menu, nativeImage, BrowserWindow} = remote;
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const iconPath = () => {
if (process.platform === 'linux') {
return APP_ICON + 'linux.png';
@@ -45,7 +47,8 @@ const config = {
const renderCanvas = function (arg) {
config.unreadCount = arg;
return new Promise((resolve, reject) => {
return new Promise(resolve => {
const SIZE = config.size * config.pixelRatio;
const PADDING = SIZE * 0.05;
const CENTER = SIZE / 2;
@@ -83,8 +86,6 @@ const renderCanvas = function (arg) {
}
resolve(canvas);
} else {
reject(canvas);
}
});
};
@@ -117,6 +118,8 @@ const createTray = function () {
const contextMenu = Menu.buildFromTemplate([{
label: 'About',
click() {
// We need to focus the main window first
ipcRenderer.send('focus-app');
sendAction('open-about');
}
},
@@ -133,8 +136,9 @@ const createTray = function () {
type: 'separator'
},
{
label: 'Manage Zulip servers',
label: 'Settings',
click() {
ipcRenderer.send('focus-app');
sendAction('open-settings');
}
},
@@ -149,9 +153,19 @@ const createTray = function () {
}
]);
window.tray.setContextMenu(contextMenu);
window.tray.on('click', () => {
// Click event only works on Windows
if (process.platform === 'win32') {
ipcRenderer.send('toggle-app');
}
});
};
ipcRenderer.on('destroytray', event => {
if (!window.tray) {
return;
}
window.tray.destroy();
if (window.tray.isDestroyed()) {
window.tray = null;
@@ -163,6 +177,10 @@ ipcRenderer.on('destroytray', event => {
});
ipcRenderer.on('tray', (event, arg) => {
if (!window.tray) {
return;
}
if (arg === 0) {
unread = arg;
// Message Count // console.log("message count is zero.");
@@ -177,21 +195,25 @@ ipcRenderer.on('tray', (event, arg) => {
}
});
ipcRenderer.on('toggletray', event => {
if (event) {
if (window.tray) {
window.tray.destroy();
if (window.tray.isDestroyed()) {
window.tray = null;
}
} else {
createTray();
renderNativeImage(unread).then(image => {
window.tray.setImage(image);
window.tray.setToolTip(unread + ' unread messages');
});
function toggleTray() {
if (window.tray) {
window.tray.destroy();
if (window.tray.isDestroyed()) {
window.tray = null;
}
ConfigUtil.setConfigItem('trayIcon', false);
} else {
createTray();
renderNativeImage(unread).then(image => {
window.tray.setImage(image);
window.tray.setToolTip(unread + ' unread messages');
});
ConfigUtil.setConfigItem('trayIcon', true);
}
});
}
createTray();
ipcRenderer.on('toggletray', toggleTray);
if (ConfigUtil.getConfigItem('trayIcon', true)) {
createTray();
}

View File

@@ -0,0 +1,53 @@
'use strict';
const process = require('process');
const JsonDB = require('node-json-db');
let instance = null;
let app = null;
/* To make the util runnable in both main and renderer process */
if (process.type === 'renderer') {
app = require('electron').remote.app;
} else {
app = require('electron').app;
}
class ConfigUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
return instance;
}
getConfigItem(key, defaultValue = null) {
const value = this.db.getData('/')[key];
if (value === undefined) {
this.setConfigItem(key, defaultValue);
return defaultValue;
} else {
return value;
}
}
setConfigItem(key, value) {
this.db.push(`/${key}`, value, true);
this.reloadDB();
}
removeConfigItem(key) {
this.db.delete(`/${key}`);
this.reloadDB();
}
reloadDB() {
this.db = new JsonDB(app.getPath('userData') + '/settings.json', true, true);
}
}
module.exports = new ConfigUtil();

View File

@@ -1,12 +1,15 @@
'use strict';
const {app} = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
let instance = null;
const defaultIconUrl = __dirname + '../../../img/icon.png';
class DomainUtil {
constructor() {
if (instance) {
@@ -41,8 +44,19 @@ class DomainUtil {
}
addDomain(server) {
server.icon = server.icon || defaultIconUrl;
this.db.push('/domains[]', server, true);
return new Promise(resolve => {
if (server.icon) {
this.saveServerIcon(server.icon).then(localIconUrl => {
server.icon = localIconUrl;
this.db.push('/domains[]', server, true);
resolve();
});
} else {
server.icon = defaultIconUrl;
this.db.push('/domains[]', server, true);
resolve();
}
});
}
removeDomains() {
@@ -65,8 +79,8 @@ class DomainUtil {
request(checkDomain, (error, response) => {
if (!error && response.statusCode !== 404) {
resolve(domain);
} else if (error.toString().indexOf('Error: self signed certificate') >= 0) {
if (window.confirm(`Do you trust certificate from ${domain}?`)) {
} else if (error.toString().indexOf('Error: self signed certificate') >= 0 || 'Error: unable to verify the first certificate') {
if (window.confirm(`Do you trust certificate from ${domain}? \n ${error}`)) {
resolve(domain);
} else {
reject('Untrusted Certificate.');
@@ -77,6 +91,37 @@ class DomainUtil {
});
});
}
saveServerIcon(url) {
// The save will always succeed. If url is invalid, downgrade to default icon.
const dir = `${app.getPath('userData')}/server-icons`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return new Promise(resolve => {
const filePath = `${dir}/${new Date().getMilliseconds()}${path.extname(url)}`;
const file = fs.createWriteStream(filePath);
try {
request(url).on('response', response => {
response.on('error', err => {
console.log(err);
resolve(defaultIconUrl);
});
response.pipe(file).on('finish', () => {
resolve(filePath);
});
}).on('error', err => {
console.log(err);
resolve(defaultIconUrl);
});
} catch (err) {
console.log(err);
resolve(defaultIconUrl);
}
});
}
}
module.exports = new DomainUtil();

View File

@@ -20,9 +20,11 @@ class LinkUtil {
const newDomain = wurl('hostname', newUrl);
const skipImages = '.jpg|.gif|.png|.jpeg|.JPG|.PNG';
const skipPages = ['integrations', 'api'];
// We'll be needing this to open images in default browser
return (currentDomain === newDomain) && !newUrl.match(skipImages);
const getskipPagesUrl = newUrl.substring(8, newUrl.length);
return (currentDomain === newDomain) && !newUrl.match(skipImages) && !skipPages.includes(getskipPagesUrl.split('/')[1]);
}
}

View File

@@ -9,14 +9,18 @@
<body>
<div id="content">
<div id="sidebar">
<div id="tabs-container"></div>
<div id="view-controls-container">
<div id="tabs-container"></div>
<div id ="add-tab" class="tab">
<div class="server-tab functional-tab" id="add-action">
<i class="material-icons">add</i>
</div>
</div>
</div>
<div id="actions-container">
<div class="action-button" id="reload-action">
<i class="material-icons md-48">refresh</i>
</div>
<div class="action-button" id="add-action">
<i class="material-icons md-48">add_circle</i>
</div>
<div class="action-button" id="settings-action">
<i class="material-icons md-48">settings</i>
</div>

21
app/renderer/network.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en" class="responsive desktop">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<title>Zulip - Network Troubleshooting</title>
<link rel="stylesheet" href="css/network.css" type="text/css" media="screen">
</head>
<body>
<div id="content">
<div id="picture"><img src="img/zulip_network.png"></div>
<div id="title">Zulip can't connect</div>
<div id="description">
<div>Your computer seems to be offline.</div>
<div>We will keep trying to reconnect, or you can try now.</div>
</div>
<div id="reconnect">Try now</div>
</div>
</body>
<script src="js/pages/network.js"></script>
</html>

View File

@@ -5,41 +5,12 @@
<meta name="viewport" content="width=device-width">
<title>Zulip - Settings</title>
<link rel="stylesheet" href="css/preference.css" type="text/css" media="screen">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="content">
<div id="sidebar">
<div id="settings-header">Settings</div>
<div id="tabs-container">
<div class="tab" id="general-settings">General</div>
<div class="tab active" id="server-settings">Servers</div>
<div class="tab" id="about-settings">About</div>
</div>
</div>
<div id="settings-container">
<div class="settings-pane" id="server-settings-pane">
<div class="title">Manage Servers</div>
<div class="actions-container">
<div class="action" id="new-server-action">
<i class="material-icons">add_box</i>
<span>New Server</span>
</div>
<div class="action hidden" id="save-server-action">
<i class="material-icons">check_box</i>
<span>Save</span>
</div>
<div class="action hidden" id="reload-server-action">
<i class="material-icons">refresh</i>
<span>Reload to Apply Changes</span>
</div>
</div>
<div class="server-info-container">
</div>
</div>
</div>
<div id="sidebar"></div>
<div id="settings-container"></div>
</div>
</body>
<script src="js/pages/preference.js"></script>
<script src="js/pages/preference/preference.js"></script>
</html>

26
docs/desktop-release.md Normal file
View File

@@ -0,0 +1,26 @@
# New release checklist -
## We need to cross check following things before pushing a new release + after updating electron version. This is just to make sure that nothing gets broken.
## - Desktop notifications
## - Spellchecker
## - Auto updates
**Check for the logs in -**
- **on Linux:** `~/.config/Zulip/log.log`
- **on OS X:** `~/Library/Logs/Zulip/log.log`
- **on Windows:** `%USERPROFILE%\AppData\Roaming\Zulip\log.log`
## - All the installer i.e.
- Linux (.deb, AppImage)
- Mac - (.dmg)
- Windows - (web installer for 32/64bit)
## - Check for errors in console (if any)
## - Code signing verification on Mac and Windows
## - Tray and menu options
# We need to cross check all these things on -
- Windows 7
- Windows 8
- Windows 10
- Ubuntu 14.04/16.04
- macOSX

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.0.0-beta",
"version": "1.2.0-beta",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
@@ -34,6 +34,7 @@
"asar": true,
"files": [
"**/*",
"!docs${/*}",
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
],
"copyright": "©2017 Kandra Labs, Inc.",
@@ -103,6 +104,12 @@
"xo": "0.18.1"
},
"xo": {
"parserOptions": {
"sourceType": "script",
"ecmaFeatures": {
"globalReturn": true
}
},
"esnext": true,
"overrides": [
{
@@ -133,4 +140,4 @@
"mocha"
]
}
}
}