mirror of
				https://github.com/zulip/zulip-desktop.git
				synced 2025-11-04 05:53:21 +00:00 
			
		
		
		
	Merge pull request #151 from zulip/140-fixes
💥 Added multiple server support feature
			
			
This commit is contained in:
		
							
								
								
									
										1
									
								
								.node-version
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.node-version
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
6.9.4
 | 
			
		||||
							
								
								
									
										1
									
								
								.python-version
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.python-version
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
2.7.9
 | 
			
		||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
// Place your settings in this file to overwrite default and user settings.
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -1,24 +1,16 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const os = require('os');
 | 
			
		||||
const electron = require('electron');
 | 
			
		||||
const {app} = require('electron');
 | 
			
		||||
const ipc = require('electron').ipcMain;
 | 
			
		||||
const {dialog} = require('electron');
 | 
			
		||||
const https = require('https');
 | 
			
		||||
const http = require('http');
 | 
			
		||||
const electronLocalshortcut = require('electron-localshortcut');
 | 
			
		||||
const Configstore = require('electron-config');
 | 
			
		||||
const JsonDB = require('node-json-db');
 | 
			
		||||
const isDev = require('electron-is-dev');
 | 
			
		||||
const appMenu = require('./menu');
 | 
			
		||||
const {linkIsInternal, skipImages} = require('./link-helper');
 | 
			
		||||
const {appUpdater} = require('./autoupdater');
 | 
			
		||||
 | 
			
		||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
 | 
			
		||||
const data = db.getData('/');
 | 
			
		||||
 | 
			
		||||
// Adds debug features like hotkeys for triggering dev tools and reload
 | 
			
		||||
require('electron-debug')();
 | 
			
		||||
 | 
			
		||||
@@ -45,48 +37,9 @@ const isUserAgent = 'ZulipElectron/' + app.getVersion() + ' ' + userOS();
 | 
			
		||||
 | 
			
		||||
// Prevent window being garbage collected
 | 
			
		||||
let mainWindow;
 | 
			
		||||
let targetLink;
 | 
			
		||||
 | 
			
		||||
// Load this url in main window
 | 
			
		||||
const staticURL = 'file://' + path.join(__dirname, '../renderer', 'index.html');
 | 
			
		||||
 | 
			
		||||
const targetURL = function () {
 | 
			
		||||
	if (data.domain === undefined) {
 | 
			
		||||
		return staticURL;
 | 
			
		||||
	}
 | 
			
		||||
	return data.domain;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function serverError(targetURL) {
 | 
			
		||||
	if (targetURL.indexOf('localhost:') < 0 && data.domain) {
 | 
			
		||||
		const req = https.request(targetURL + '/static/audio/zulip.ogg', res => {
 | 
			
		||||
			console.log('Server StatusCode:', res.statusCode);
 | 
			
		||||
			console.log('You are connected to:', res.req._headers.host);
 | 
			
		||||
			if (res.statusCode >= 500 && res.statusCode <= 599) {
 | 
			
		||||
				return dialog.showErrorBox('SERVER IS DOWN!', 'We are getting a ' + res.statusCode + ' error status from the server ' + res.req._headers.host + '. Please try again after some time or you may switch server.');
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		req.on('error', e => {
 | 
			
		||||
			if (e.toString().indexOf('Error: self signed certificate') >= 0) {
 | 
			
		||||
				const url = targetURL.replace(/^https?:\/\//, '');
 | 
			
		||||
				console.log('Server StatusCode:', 200);
 | 
			
		||||
				console.log('You are connected to:', url);
 | 
			
		||||
			} else {
 | 
			
		||||
				console.error(e);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		req.end();
 | 
			
		||||
	} else if (data.domain) {
 | 
			
		||||
		const req = http.request(targetURL + '/static/audio/zulip.ogg', res => {
 | 
			
		||||
			console.log('Server StatusCode:', res.statusCode);
 | 
			
		||||
			console.log('You are connected to:', res.req._headers.host);
 | 
			
		||||
		});
 | 
			
		||||
		req.on('error', e => {
 | 
			
		||||
			console.error(e);
 | 
			
		||||
		});
 | 
			
		||||
		req.end();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
 | 
			
		||||
 | 
			
		||||
function checkConnectivity() {
 | 
			
		||||
	return dialog.showMessageBox({
 | 
			
		||||
@@ -113,6 +66,7 @@ const connectivityERR = [
 | 
			
		||||
	'ERR_NAME_NOT_RESOLVED'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
function checkConnection() {
 | 
			
		||||
	// eslint-disable-next-line no-unused-vars
 | 
			
		||||
	mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
 | 
			
		||||
@@ -138,13 +92,6 @@ if (isAlreadyRunning) {
 | 
			
		||||
	app.quit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function checkWindowURL() {
 | 
			
		||||
	if (data.domain !== undefined) {
 | 
			
		||||
		return data.domain;
 | 
			
		||||
	}
 | 
			
		||||
	return targetLink;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isWindowsOrmacOS() {
 | 
			
		||||
	return process.platform === 'darwin' || process.platform === 'win32';
 | 
			
		||||
}
 | 
			
		||||
@@ -161,20 +108,6 @@ function onClosed() {
 | 
			
		||||
	mainWindow = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateDockBadge(title) {
 | 
			
		||||
	if (title.indexOf('Zulip') === -1) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let messageCount = (/\(([0-9]+)\)/).exec(title);
 | 
			
		||||
	messageCount = messageCount ? Number(messageCount[1]) : 0;
 | 
			
		||||
 | 
			
		||||
	if (process.platform === 'darwin') {
 | 
			
		||||
		app.setBadgeCount(messageCount);
 | 
			
		||||
	}
 | 
			
		||||
	mainWindow.webContents.send('tray', messageCount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function createMainWindow() {
 | 
			
		||||
	const win = new electron.BrowserWindow({
 | 
			
		||||
		// This settings needs to be saved in config
 | 
			
		||||
@@ -184,11 +117,11 @@ function createMainWindow() {
 | 
			
		||||
		icon: iconPath(),
 | 
			
		||||
		minWidth: 600,
 | 
			
		||||
		minHeight: 400,
 | 
			
		||||
		titleBarStyle: 'hidden-inset',
 | 
			
		||||
		webPreferences: {
 | 
			
		||||
			preload: path.join(__dirname, '../renderer/js/preload.js'),
 | 
			
		||||
			plugins: true,
 | 
			
		||||
			allowDisplayingInsecureContent: true,
 | 
			
		||||
			nodeIntegration: false
 | 
			
		||||
			nodeIntegration: true
 | 
			
		||||
		},
 | 
			
		||||
		show: false
 | 
			
		||||
	});
 | 
			
		||||
@@ -197,9 +130,7 @@ function createMainWindow() {
 | 
			
		||||
		win.show();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	serverError(targetURL());
 | 
			
		||||
 | 
			
		||||
	win.loadURL(targetURL(), {
 | 
			
		||||
	win.loadURL(mainURL, {
 | 
			
		||||
		userAgent: isUserAgent + ' ' + win.webContents.getUserAgent()
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
@@ -241,12 +172,6 @@ function createMainWindow() {
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Stop page to update it's title
 | 
			
		||||
	win.on('page-title-updated', (e, title) => {
 | 
			
		||||
		e.preventDefault();
 | 
			
		||||
		updateDockBadge(title);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	//  To destroy tray icon when navigate to a new URL
 | 
			
		||||
	win.webContents.on('will-navigate', e => {
 | 
			
		||||
		if (e) {
 | 
			
		||||
@@ -257,30 +182,6 @@ function createMainWindow() {
 | 
			
		||||
	return win;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO - fix certificate errors
 | 
			
		||||
 | 
			
		||||
// app.commandLine.appendSwitch('ignore-certificate-errors', 'true');
 | 
			
		||||
 | 
			
		||||
// For self-signed certificate
 | 
			
		||||
ipc.on('certificate-err', (e, domain) => {
 | 
			
		||||
	const detail = `URL: ${domain} \n Error: Self-Signed Certificate`;
 | 
			
		||||
	dialog.showMessageBox(mainWindow, {
 | 
			
		||||
		title: 'Certificate error',
 | 
			
		||||
		message: `Do you trust certificate from ${domain}?`,
 | 
			
		||||
		// eslint-disable-next-line object-shorthand
 | 
			
		||||
		detail: detail,
 | 
			
		||||
		type: 'warning',
 | 
			
		||||
		buttons: ['Yes', 'No'],
 | 
			
		||||
		cancelId: 1
 | 
			
		||||
		// eslint-disable-next-line object-shorthand
 | 
			
		||||
	}, response => {
 | 
			
		||||
		if (response === 0) {
 | 
			
		||||
			// eslint-disable-next-line object-shorthand
 | 
			
		||||
			db.push('/domain', domain);
 | 
			
		||||
			mainWindow.loadURL(domain);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
// eslint-disable-next-line max-params
 | 
			
		||||
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
 | 
			
		||||
	event.preventDefault();
 | 
			
		||||
@@ -310,36 +211,22 @@ app.on('ready', () => {
 | 
			
		||||
 | 
			
		||||
	// TODO - use global shortcut instead
 | 
			
		||||
	electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
 | 
			
		||||
		mainWindow.reload();
 | 
			
		||||
		mainWindow.webContents.send('destroytray');
 | 
			
		||||
		page.send('reload');
 | 
			
		||||
		// page.send('destroytray');
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
 | 
			
		||||
		if (page.canGoBack()) {
 | 
			
		||||
			page.goBack();
 | 
			
		||||
		}
 | 
			
		||||
		page.send('back');
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
 | 
			
		||||
		if (page.canGoForward()) {
 | 
			
		||||
			page.goForward();
 | 
			
		||||
		}
 | 
			
		||||
		page.send('forward');
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	page.on('dom-ready', () => {
 | 
			
		||||
		page.insertCSS(fs.readFileSync(path.join(__dirname, '../renderer/css/preload.css'), 'utf8'));
 | 
			
		||||
		mainWindow.show();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	page.on('new-window', (event, url) => {
 | 
			
		||||
		if (linkIsInternal(checkWindowURL(), url) && url.match(skipImages) === null) {
 | 
			
		||||
			event.preventDefault();
 | 
			
		||||
			return mainWindow.loadURL(url);
 | 
			
		||||
		}
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		electron.shell.openExternal(url);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	page.once('did-frame-finish-load', () => {
 | 
			
		||||
		const checkOS = isWindowsOrmacOS();
 | 
			
		||||
		if (checkOS && !isDev) {
 | 
			
		||||
@@ -352,27 +239,20 @@ app.on('ready', () => {
 | 
			
		||||
		mainWindow.webContents.send('destroytray');
 | 
			
		||||
	});
 | 
			
		||||
	checkConnection();
 | 
			
		||||
 | 
			
		||||
	ipc.on('reload-main', () => {
 | 
			
		||||
		page.reload();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	ipc.on('update-badge', (event, messageCount) => {
 | 
			
		||||
		if (process.platform === 'darwin') {
 | 
			
		||||
			app.setBadgeCount(messageCount);
 | 
			
		||||
		}
 | 
			
		||||
		page.send('tray', messageCount);
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.on('will-quit', () => {
 | 
			
		||||
	// Unregister all the shortcuts so that they don't interfare with other apps
 | 
			
		||||
	electronLocalshortcut.unregisterAll(mainWindow);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipc.on('new-domain', (e, domain) => {
 | 
			
		||||
	// MainWindow.loadURL(domain);
 | 
			
		||||
	if (!mainWindow) {
 | 
			
		||||
		mainWindow = createMainWindow();
 | 
			
		||||
		mainWindow.loadURL(domain);
 | 
			
		||||
		mainWindow.webContents.send('destroytray');
 | 
			
		||||
	} else if (mainWindow.isMinimized()) {
 | 
			
		||||
		mainWindow.webContents.send('destroytray');
 | 
			
		||||
		mainWindow.loadURL(domain);
 | 
			
		||||
		mainWindow.show();
 | 
			
		||||
	} else {
 | 
			
		||||
		mainWindow.webContents.send('destroytray');
 | 
			
		||||
		mainWindow.loadURL(domain);
 | 
			
		||||
		serverError(domain);
 | 
			
		||||
	}
 | 
			
		||||
	targetLink = domain;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,8 @@ const app = electron.app;
 | 
			
		||||
const BrowserWindow = electron.BrowserWindow;
 | 
			
		||||
const shell = electron.shell;
 | 
			
		||||
const appName = app.getName();
 | 
			
		||||
//	Const tray = require('./tray');
 | 
			
		||||
 | 
			
		||||
const {addDomain, about} = require('./windowmanager');
 | 
			
		||||
const {about} = require('./windowmanager');
 | 
			
		||||
 | 
			
		||||
function sendAction(action) {
 | 
			
		||||
	const win = BrowserWindow.getAllWindows()[0];
 | 
			
		||||
@@ -35,8 +34,7 @@ const viewSubmenu = [
 | 
			
		||||
		label: 'Reload',
 | 
			
		||||
		click(item, focusedWindow) {
 | 
			
		||||
			if (focusedWindow) {
 | 
			
		||||
				focusedWindow.reload();
 | 
			
		||||
				focusedWindow.webContents.send('destroytray');
 | 
			
		||||
				sendAction('reload');
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
@@ -136,10 +134,12 @@ const darwinTpl = [
 | 
			
		||||
				type: 'separator'
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				label: 'Change Zulip Server',
 | 
			
		||||
				label: 'Manage Zulip Servers',
 | 
			
		||||
				accelerator: 'Cmd+,',
 | 
			
		||||
				click() {
 | 
			
		||||
					addDomain();
 | 
			
		||||
				click(item, focusedWindow) {
 | 
			
		||||
					if (focusedWindow) {
 | 
			
		||||
						sendAction('open-settings');
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
@@ -269,10 +269,12 @@ const otherTpl = [
 | 
			
		||||
				type: 'separator'
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				label: 'Change Zulip Server',
 | 
			
		||||
				label: 'Manage Zulip Servers',
 | 
			
		||||
				accelerator: 'Ctrl+,',
 | 
			
		||||
				click() {
 | 
			
		||||
					addDomain();
 | 
			
		||||
				click(item, focusedWindow) {
 | 
			
		||||
					if (focusedWindow) {
 | 
			
		||||
						sendAction('open-settings');
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
 
 | 
			
		||||
@@ -89,11 +89,6 @@ ipc.on('trayabout', event => {
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipc.on('traychangeserver', event => {
 | 
			
		||||
	if (event) {
 | 
			
		||||
		addDomain();
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
module.exports = {
 | 
			
		||||
	addDomain,
 | 
			
		||||
	about
 | 
			
		||||
 
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
body{
 | 
			
		||||
	background-color: #6BB6C7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 35%;
 | 
			
		||||
    width: 300px;
 | 
			
		||||
    left: 9%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.close {
 | 
			
		||||
    background: transparent url('../img/close.png') no-repeat 4px 4px;
 | 
			
		||||
    background-size: 24px 24px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    height: 32px;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 6px;
 | 
			
		||||
    text-indent: -10000px;
 | 
			
		||||
    top: 6px;
 | 
			
		||||
    width: 32px;
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
    -webkit-app-region: no-drag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="text"] {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    appearance: none;
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
    border-radius: none;
 | 
			
		||||
    color: #646464;
 | 
			
		||||
}
 | 
			
		||||
input[type="text"]:focus {
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form input[type="text"] {
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  border: solid 1px #dcdcdc;
 | 
			
		||||
  transition: box-shadow 0.3s, border 0.3s;
 | 
			
		||||
}
 | 
			
		||||
.form input[type="text"]:focus,
 | 
			
		||||
.form input[type="text"].focus {
 | 
			
		||||
  border: solid 1px #707070;
 | 
			
		||||
  box-shadow: 0 0 5px 1px #969696;
 | 
			
		||||
}
 | 
			
		||||
button {
 | 
			
		||||
    border: none;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    padding: 12px 32px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    margin-left: 107px;
 | 
			
		||||
    margin-top: 24px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    background: #137b86;
 | 
			
		||||
}
 | 
			
		||||
button:focus {
 | 
			
		||||
    outline: 0;
 | 
			
		||||
}
 | 
			
		||||
#urladded {
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    font-family: 'opensans';
 | 
			
		||||
    top: -61%;
 | 
			
		||||
    left: 25%;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										162
									
								
								app/renderer/css/preference.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								app/renderer/css/preference.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
			
		||||
html, body {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    cursor: default;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    color: #333;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#sidebar {
 | 
			
		||||
    width: 80px;
 | 
			
		||||
    padding: 30px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#tabs-container {
 | 
			
		||||
    padding: 20px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab {
 | 
			
		||||
    padding: 5px 0;
 | 
			
		||||
    color: #999;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab.active {
 | 
			
		||||
    color: #464e5a;
 | 
			
		||||
    cursor: default;
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab.active::before {
 | 
			
		||||
    background: #464e5a;
 | 
			
		||||
    width: 3px;
 | 
			
		||||
    height: 16px;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: -8px;
 | 
			
		||||
    content: '';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#settings-header {
 | 
			
		||||
    font-size: 22px;
 | 
			
		||||
    color: #5c6166;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#settings-container {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    padding: 30px;
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-container {
 | 
			
		||||
    margin: 20px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.title {
 | 
			
		||||
    padding: 4px 0 6px 0;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    color: #000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.server-info-icon {
 | 
			
		||||
    background: #a4d3c4;
 | 
			
		||||
    background-size: 100%;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    width: 44px;
 | 
			
		||||
    height: 44px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-left {
 | 
			
		||||
    margin-right: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-right {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-row {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    line-height: 26px;
 | 
			
		||||
    height: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-key {
 | 
			
		||||
    width: 40px;
 | 
			
		||||
    margin-right: 20px;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-value {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-bottom: #ddd 1px solid;
 | 
			
		||||
    outline-width: 0;
 | 
			
		||||
    background: transparent;
 | 
			
		||||
    max-width: 500px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-value:focus {
 | 
			
		||||
    border-bottom: #b0d8ce 2px solid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.actions-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    color: #235d3a;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
    margin: 10px 0;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    margin-right: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action i {
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.settings-pane {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action:hover {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action.disabled:hover {
 | 
			
		||||
    cursor: default;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action.disabled {
 | 
			
		||||
    color: #999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info.active {
 | 
			
		||||
    background: #ecf4ef;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    margin: 10px 0 10px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hidden {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
/* We'll be overriding default styling so that app look more native * / 
 | 
			
		||||
							
								
								
									
										115
									
								
								app/renderer/css/servermanager.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								app/renderer/css/servermanager.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
html, body {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    cursor: default;    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background: #eee url(../img/loading.gif) no-repeat;
 | 
			
		||||
    background-size: 60px 60px;
 | 
			
		||||
    background-position: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#sidebar {
 | 
			
		||||
    background: #222c31;
 | 
			
		||||
    width: 50px;
 | 
			
		||||
    padding: 40px 0 20px 0;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    -webkit-app-region: drag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#webview {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-button {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-button i {
 | 
			
		||||
    color: #6c8592;
 | 
			
		||||
    font-size: 28px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-button:hover i {
 | 
			
		||||
    color: #98a9b3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#tabs-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin: 5px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab.active::before{
 | 
			
		||||
    content: "";
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    border-radius: 0 3px 3px 0;
 | 
			
		||||
    width: 4px;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    height: 31px;
 | 
			
		||||
    left: -8px;
 | 
			
		||||
    top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab .server-tab{
 | 
			
		||||
    background: #a4d3c4;
 | 
			
		||||
    background-size: 100%;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    width: 31px;
 | 
			
		||||
    height: 31px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin: 5px 0;
 | 
			
		||||
    z-index: 11;
 | 
			
		||||
    line-height: 31px;
 | 
			
		||||
    font-size: 32px;
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
    color: #194a2b;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    opacity: 0.6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab .server-tab:hover{
 | 
			
		||||
    opacity: 0.8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab .settings-tab{
 | 
			
		||||
    background: #eee;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab .settings-tab i{
 | 
			
		||||
    font-size: 28px;
 | 
			
		||||
    line-height: 44px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab.active .server-tab{
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
webview {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    transition: opacity 0.3s;
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
webview.loading {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transition: opacity 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
webview.disabled {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								app/renderer/img/loading.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/renderer/img/loading.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 22 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 40 KiB  | 
@@ -1,30 +0,0 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en" class="responsive desktop">
 | 
			
		||||
  <!--<![endif]-->
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width">
 | 
			
		||||
    <title>Login - Zulip</title>
 | 
			
		||||
    <link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
 | 
			
		||||
  </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <div class="center">
 | 
			
		||||
            <section class="server">
 | 
			
		||||
                <header>
 | 
			
		||||
                    <img src="../resources/zulip.png" id="logo"/>
 | 
			
		||||
                    <h1>Zulip Login</h1>
 | 
			
		||||
                </header>
 | 
			
		||||
                <div class="container">
 | 
			
		||||
                    <form id="frm-signInForm" class="form-large" onsubmit="addDomain(); return false">
 | 
			
		||||
                        <label for="url">
 | 
			
		||||
                            Zulip Server URL
 | 
			
		||||
                        </label>
 | 
			
		||||
                        <input type="text" id="url"  autofocus="autofocus" spellcheck="false" placeholder="Server URL">
 | 
			
		||||
                        <button type="submit" id="main" class="btn-primary btn-large" value="Submit">Connect</button>
 | 
			
		||||
                        <p id="error"></p>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
            </section>
 | 
			
		||||
        </div>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -1,80 +0,0 @@
 | 
			
		||||
const {app} = require('electron').remote;
 | 
			
		||||
const ipcRenderer = require('electron').ipcRenderer;
 | 
			
		||||
const JsonDB = require('node-json-db');
 | 
			
		||||
const request = require('request');
 | 
			
		||||
 | 
			
		||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
 | 
			
		||||
 | 
			
		||||
window.addDomain = function () {
 | 
			
		||||
	const el = sel => {
 | 
			
		||||
		return document.querySelector(sel);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const $el = {
 | 
			
		||||
		error: el('#error'),
 | 
			
		||||
		main: el('#main'),
 | 
			
		||||
		section: el('section')
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const event = sel => {
 | 
			
		||||
		return {
 | 
			
		||||
			on: (event, callback) => {
 | 
			
		||||
				document.querySelector(sel).addEventListener(event, callback);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const displayError = msg => {
 | 
			
		||||
		$el.error.innerText = msg;
 | 
			
		||||
		$el.error.classList.add('show');
 | 
			
		||||
		$el.section.classList.add('shake');
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	let newDomain = document.getElementById('url').value;
 | 
			
		||||
	newDomain = newDomain.replace(/^https?:\/\//, '');
 | 
			
		||||
	if (newDomain === '') {
 | 
			
		||||
		displayError('Please input a valid URL.');
 | 
			
		||||
	} else {
 | 
			
		||||
		el('#main').innerHTML = 'Checking...';
 | 
			
		||||
		if (newDomain.indexOf('localhost:') >= 0) {
 | 
			
		||||
			const domain = 'http://' + newDomain;
 | 
			
		||||
			const checkDomain = domain + '/static/audio/zulip.ogg';
 | 
			
		||||
			request(checkDomain, (error, response) => {
 | 
			
		||||
				if (!error && response.statusCode !== 404) {
 | 
			
		||||
					document.getElementById('main').innerHTML = 'Connect';
 | 
			
		||||
					db.push('/domain', domain);
 | 
			
		||||
					ipcRenderer.send('new-domain', domain);
 | 
			
		||||
				} else {
 | 
			
		||||
					$el.main.innerHTML = 'Connect';
 | 
			
		||||
					displayError('Not a valid Zulip local server');
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			//	});
 | 
			
		||||
		} else {
 | 
			
		||||
			const domain = 'https://' + newDomain;
 | 
			
		||||
			const checkDomain = domain + '/static/audio/zulip.ogg';
 | 
			
		||||
 | 
			
		||||
			request(checkDomain, (error, response) => {
 | 
			
		||||
				if (!error && response.statusCode !== 404) {
 | 
			
		||||
					$el.main.innerHTML = 'Connect';
 | 
			
		||||
					db.push('/domain', domain);
 | 
			
		||||
					ipcRenderer.send('new-domain', domain);
 | 
			
		||||
				} else if (error.toString().indexOf('Error: self signed certificate') >= 0) {
 | 
			
		||||
					$el.main.innerHTML = 'Connect';
 | 
			
		||||
					ipcRenderer.send('certificate-err', domain);
 | 
			
		||||
				} else {
 | 
			
		||||
					$el.main.innerHTML = 'Connect';
 | 
			
		||||
					displayError('Not a valid Zulip server');
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	event('#url').on('input', () => {
 | 
			
		||||
		el('#error').classList.remove('show');
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	event('section').on('animationend', function () {
 | 
			
		||||
		this.classList.remove('shake');
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										258
									
								
								app/renderer/js/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								app/renderer/js/main.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,258 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
require(__dirname + '/js/tray.js');
 | 
			
		||||
 | 
			
		||||
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
 | 
			
		||||
const {linkIsInternal, skipImages} = require(__dirname + '/../main/link-helper');
 | 
			
		||||
const {shell, ipcRenderer} = require('electron');
 | 
			
		||||
 | 
			
		||||
class ServerManagerView {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.$tabsContainer = document.getElementById('tabs-container');
 | 
			
		||||
 | 
			
		||||
		const $actionsContainer = document.getElementById('actions-container');
 | 
			
		||||
		this.$addServerButton = $actionsContainer.querySelector('#add-action');
 | 
			
		||||
		this.$settingsButton = $actionsContainer.querySelector('#settings-action');
 | 
			
		||||
		this.$content = document.getElementById('content');
 | 
			
		||||
 | 
			
		||||
		this.isLoading = false;
 | 
			
		||||
		this.settingsTabIndex = -1;
 | 
			
		||||
		this.activeTabIndex = -1;
 | 
			
		||||
		this.zoomFactors = [];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	init() {
 | 
			
		||||
		this.domainUtil = new DomainUtil();
 | 
			
		||||
		this.initTabs();
 | 
			
		||||
		this.initActions();
 | 
			
		||||
		this.registerIpcs();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	initTabs() {
 | 
			
		||||
		const servers = this.domainUtil.getDomains();
 | 
			
		||||
		if (servers.length > 0) {
 | 
			
		||||
			for (const server of servers) {
 | 
			
		||||
				this.initTab(server);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.activateTab(0);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.openSettings();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	initTab(tab) {
 | 
			
		||||
		const {
 | 
			
		||||
			url,
 | 
			
		||||
			icon
 | 
			
		||||
		} = tab;
 | 
			
		||||
		const tabTemplate = tab.template || `
 | 
			
		||||
				<div class="tab" domain="${url}">
 | 
			
		||||
					<div class="server-tab" style="background-image: url(${icon});"></div>
 | 
			
		||||
				</div>`;
 | 
			
		||||
		const $tab = this.insertNode(tabTemplate);
 | 
			
		||||
		const index = this.$tabsContainer.childNodes.length;
 | 
			
		||||
		this.$tabsContainer.appendChild($tab);
 | 
			
		||||
		$tab.addEventListener('click', this.activateTab.bind(this, index));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	initWebView(url, index, nodeIntegration = false) {
 | 
			
		||||
		const webViewTemplate = `
 | 
			
		||||
			<webview 
 | 
			
		||||
				id="webview-${index}"
 | 
			
		||||
				class="loading"
 | 
			
		||||
				src="${url}"
 | 
			
		||||
				${nodeIntegration ? 'nodeIntegration' : ''}
 | 
			
		||||
				disablewebsecurity
 | 
			
		||||
				preload="js/preload.js"
 | 
			
		||||
				webpreferences="allowRunningInsecureContent, javascript=yes">
 | 
			
		||||
			</webview>
 | 
			
		||||
		`;
 | 
			
		||||
		const $webView = this.insertNode(webViewTemplate);
 | 
			
		||||
		this.$content.appendChild($webView);
 | 
			
		||||
		this.isLoading = true;
 | 
			
		||||
		$webView.addEventListener('dom-ready', this.endLoading.bind(this, index));
 | 
			
		||||
 | 
			
		||||
		$webView.addEventListener('dom-ready', () => {
 | 
			
		||||
			// We need to wait until the page title is ready to get badge count
 | 
			
		||||
			setTimeout(() => this.updateBadge(index), 1000);
 | 
			
		||||
		});
 | 
			
		||||
		$webView.addEventListener('dom-ready', () => {
 | 
			
		||||
			$webView.focus()
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.registerListeners($webView, index);
 | 
			
		||||
		this.zoomFactors[index] = 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	startLoading(url, index) {
 | 
			
		||||
		const $activeWebView = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
		if ($activeWebView) {
 | 
			
		||||
			$activeWebView.classList.add('disabled');
 | 
			
		||||
		}
 | 
			
		||||
		const $webView = document.getElementById(`webview-${index}`);
 | 
			
		||||
		if ($webView === null) {
 | 
			
		||||
			this.initWebView(url, index, this.settingsTabIndex === index);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.updateBadge(index);
 | 
			
		||||
			$webView.classList.remove('disabled');
 | 
			
		||||
			$webView.focus()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	endLoading(index) {
 | 
			
		||||
		const $webView = document.getElementById(`webview-${index}`);
 | 
			
		||||
		this.isLoading = false;
 | 
			
		||||
		$webView.classList.remove('loading');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	initActions() {
 | 
			
		||||
		this.$addServerButton.addEventListener('click', this.openSettings.bind(this));
 | 
			
		||||
		this.$settingsButton.addEventListener('click', this.openSettings.bind(this));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	openSettings() {
 | 
			
		||||
		if (this.settingsTabIndex !== -1) {
 | 
			
		||||
			this.activateTab(this.settingsTabIndex);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		const url = 'file://' + __dirname + '/preference.html';
 | 
			
		||||
 | 
			
		||||
		const settingsTabTemplate = `
 | 
			
		||||
				<div class="tab" domain="${url}">
 | 
			
		||||
					<div class="server-tab settings-tab">
 | 
			
		||||
						<i class="material-icons md-48">settings</i>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>`;
 | 
			
		||||
		this.initTab({
 | 
			
		||||
			alias: 'Settings',
 | 
			
		||||
			url,
 | 
			
		||||
			template: settingsTabTemplate
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.settingsTabIndex = this.$tabsContainer.childNodes.length - 1;
 | 
			
		||||
		this.activateTab(this.settingsTabIndex);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	activateTab(index) {
 | 
			
		||||
		if (this.isLoading) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (this.activeTabIndex !== -1) {
 | 
			
		||||
			if (this.activeTabIndex === index) {
 | 
			
		||||
				return;
 | 
			
		||||
			} else {
 | 
			
		||||
				this.getTabAt(this.activeTabIndex).classList.remove('active');
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const $tab = this.getTabAt(index);
 | 
			
		||||
		$tab.classList.add('active');
 | 
			
		||||
 | 
			
		||||
		const domain = $tab.getAttribute('domain');
 | 
			
		||||
		this.startLoading(domain, index);
 | 
			
		||||
		this.activeTabIndex = index;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	insertNode(html) {
 | 
			
		||||
		const wrapper = document.createElement('div');
 | 
			
		||||
		wrapper.innerHTML = html;
 | 
			
		||||
		return wrapper.firstElementChild;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getTabAt(index) {
 | 
			
		||||
		return this.$tabsContainer.childNodes[index];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	updateBadge (index) {
 | 
			
		||||
		const $activeWebView = document.getElementById(`webview-${index}`);
 | 
			
		||||
		const title = $activeWebView.getTitle();
 | 
			
		||||
		let messageCount = (/\(([0-9]+)\)/).exec(title);
 | 
			
		||||
		messageCount = messageCount ? Number(messageCount[1]) : 0;
 | 
			
		||||
		ipcRenderer.send('update-badge', messageCount);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	registerListeners($webView, index) {
 | 
			
		||||
		$webView.addEventListener('new-window', event => {
 | 
			
		||||
			const {url} = event;
 | 
			
		||||
			const domainPrefix = this.domainUtil.getDomain(this.activeTabIndex).url;
 | 
			
		||||
			if (linkIsInternal(domainPrefix, url) && url.match(skipImages) === null) {
 | 
			
		||||
				event.preventDefault();
 | 
			
		||||
				return $webView.loadURL(url);
 | 
			
		||||
			}
 | 
			
		||||
			event.preventDefault();
 | 
			
		||||
			shell.openExternal(url);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		$webView.addEventListener('page-title-updated', event => {
 | 
			
		||||
			const {title} = event;
 | 
			
		||||
			if (title.indexOf('Zulip') === -1) {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	registerIpcs() {
 | 
			
		||||
		ipcRenderer.on('reload', () => {
 | 
			
		||||
			const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
			activeWebview.reload();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		ipcRenderer.on('back', () => {
 | 
			
		||||
			const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
			if (activeWebview.canGoBack()) {
 | 
			
		||||
				activeWebview.goBack();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		ipcRenderer.on('forward', () => {
 | 
			
		||||
			const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
			if (activeWebview.canGoForward()) {
 | 
			
		||||
				activeWebview.goForward();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Handle zooming functionality
 | 
			
		||||
		ipcRenderer.on('zoomIn', () => {
 | 
			
		||||
			const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
			this.zoomFactors[this.activeTabIndex] += 0.1;
 | 
			
		||||
			activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		ipcRenderer.on('zoomOut', () => {
 | 
			
		||||
			const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
			this.zoomFactors[this.activeTabIndex] -= 0.1;
 | 
			
		||||
			activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		ipcRenderer.on('zoomActualSize', () => {
 | 
			
		||||
			const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
			this.zoomFactors[this.activeTabIndex] = 1;
 | 
			
		||||
			activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		ipcRenderer.on('log-out', () => {
 | 
			
		||||
			const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
			activeWebview.executeJavaScript('logout()');
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		ipcRenderer.on('shortcut', () => {
 | 
			
		||||
			const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`);
 | 
			
		||||
			activeWebview.executeJavaScript('shortcut()');
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		ipcRenderer.on('open-settings', () => {
 | 
			
		||||
			if (this.settingsTabIndex === -1) {
 | 
			
		||||
				this.openSettings();
 | 
			
		||||
			} else {
 | 
			
		||||
				this.activateTab(this.settingsTabIndex);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.onload = () => {
 | 
			
		||||
	const serverManagerView = new ServerManagerView();
 | 
			
		||||
	serverManagerView.init();
 | 
			
		||||
};
 | 
			
		||||
@@ -1,69 +0,0 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
// eslint-disable-next-line import/no-extraneous-dependencies
 | 
			
		||||
const {remote} = require('electron');
 | 
			
		||||
 | 
			
		||||
const prefWindow = remote.getCurrentWindow();
 | 
			
		||||
 | 
			
		||||
document.getElementById('close-button').addEventListener('click', () => {
 | 
			
		||||
	prefWindow.close();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
document.addEventListener('keydown', event => {
 | 
			
		||||
	if (event.key === 'Escape' || event.keyCode === 27) {
 | 
			
		||||
		prefWindow.close();
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
// eslint-disable-next-line no-unused-vars
 | 
			
		||||
window.prefDomain = function () {
 | 
			
		||||
	const request = require('request');
 | 
			
		||||
	// eslint-disable-next-line import/no-extraneous-dependencies
 | 
			
		||||
	const ipcRenderer = require('electron').ipcRenderer;
 | 
			
		||||
	const JsonDB = require('node-json-db');
 | 
			
		||||
	// eslint-disable-next-line import/no-extraneous-dependencies
 | 
			
		||||
	const {app} = require('electron').remote;
 | 
			
		||||
 | 
			
		||||
	const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
 | 
			
		||||
 | 
			
		||||
	let newDomain = document.getElementById('url').value;
 | 
			
		||||
	newDomain = newDomain.replace(/^https?:\/\//, '');
 | 
			
		||||
	newDomain = newDomain.replace(/^http?:\/\//, '');
 | 
			
		||||
 | 
			
		||||
	if (newDomain === '') {
 | 
			
		||||
		document.getElementById('urladded').innerHTML = 'Please input a value';
 | 
			
		||||
	} else {
 | 
			
		||||
		document.getElementById('main').innerHTML = 'Checking...';
 | 
			
		||||
		if (newDomain.indexOf('localhost:') >= 0) {
 | 
			
		||||
			const domain = 'http://' + newDomain;
 | 
			
		||||
			const checkDomain = domain + '/static/audio/zulip.ogg';
 | 
			
		||||
			request(checkDomain, (error, response) => {
 | 
			
		||||
				if (!error && response.statusCode !== 404) {
 | 
			
		||||
					document.getElementById('main').innerHTML = 'Switch';
 | 
			
		||||
					document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
 | 
			
		||||
					db.push('/domain', domain);
 | 
			
		||||
					ipcRenderer.send('new-domain', domain);
 | 
			
		||||
				} else {
 | 
			
		||||
					document.getElementById('main').innerHTML = 'Switch';
 | 
			
		||||
					document.getElementById('urladded').innerHTML = 'Not a valid Zulip Local Server.';
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		} else {
 | 
			
		||||
			const domain = 'https://' + newDomain;
 | 
			
		||||
			const checkDomain = domain + '/static/audio/zulip.ogg';
 | 
			
		||||
			request(checkDomain, (error, response) => {
 | 
			
		||||
				if (!error && response.statusCode !== 404) {
 | 
			
		||||
					document.getElementById('main').innerHTML = 'Switch';
 | 
			
		||||
					document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
 | 
			
		||||
					db.push('/domain', domain);
 | 
			
		||||
					ipcRenderer.send('new-domain', domain);
 | 
			
		||||
				} else if (error.toString().indexOf('Error: self signed certificate') >= 0) {
 | 
			
		||||
					document.getElementById('main').innerHTML = 'Switch';
 | 
			
		||||
					ipcRenderer.send('certificate-err', domain);
 | 
			
		||||
					document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
 | 
			
		||||
				} else {
 | 
			
		||||
					document.getElementById('main').innerHTML = 'Switch';
 | 
			
		||||
					document.getElementById('urladded').innerHTML = 'Not a valid Zulip Server.';
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										143
									
								
								app/renderer/js/preference.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								app/renderer/js/preference.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
'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.domainUtil = new DomainUtil();
 | 
			
		||||
		this.initServers();
 | 
			
		||||
		this.initActions();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	initServers() {
 | 
			
		||||
		const servers = this.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', () => {
 | 
			
		||||
			this.domainUtil.removeDomain(index);
 | 
			
		||||
			this.initServers();
 | 
			
		||||
			alert('Success. Reload to apply changes.');
 | 
			
		||||
			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 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>
 | 
			
		||||
				</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', () => {
 | 
			
		||||
			this.domainUtil.checkDomain(this.$newServerUrl.value).then(domain => {
 | 
			
		||||
				const server = {
 | 
			
		||||
					alias: this.$newServerAlias.value,
 | 
			
		||||
					url: domain,
 | 
			
		||||
					icon: this.$newServerIcon.value
 | 
			
		||||
				};
 | 
			
		||||
				this.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.');
 | 
			
		||||
				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();
 | 
			
		||||
};
 | 
			
		||||
@@ -1,56 +1,15 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
const ipcRenderer = require('electron').ipcRenderer;
 | 
			
		||||
const {webFrame} = require('electron');
 | 
			
		||||
const {spellChecker} = require('./spellchecker');
 | 
			
		||||
 | 
			
		||||
const _setImmediate = setImmediate;
 | 
			
		||||
const _clearImmediate = clearImmediate;
 | 
			
		||||
process.once('loaded', () => {
 | 
			
		||||
	global.setImmediate = _setImmediate;
 | 
			
		||||
	global.clearImmediate = _clearImmediate;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line import/no-unassigned-import
 | 
			
		||||
require('./domain');
 | 
			
		||||
// eslint-disable-next-line import/no-unassigned-import
 | 
			
		||||
require('./tray.js');
 | 
			
		||||
// Calling Tray.js in renderer process everytime app window loads
 | 
			
		||||
 | 
			
		||||
// Handle zooming functionality
 | 
			
		||||
const zoomIn = () => {
 | 
			
		||||
	webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const zoomOut = () => {
 | 
			
		||||
	webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const zoomActualSize = () => {
 | 
			
		||||
	webFrame.setZoomFactor(1);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Get zooming actions from main process
 | 
			
		||||
ipcRenderer.on('zoomIn', () => {
 | 
			
		||||
	zoomIn();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipcRenderer.on('zoomOut', () => {
 | 
			
		||||
	zoomOut();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipcRenderer.on('zoomActualSize', () => {
 | 
			
		||||
	zoomActualSize();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipcRenderer.on('log-out', () => {
 | 
			
		||||
const logout = () => {
 | 
			
		||||
	// Create the menu for the below
 | 
			
		||||
	document.querySelector('.dropdown-toggle').click();
 | 
			
		||||
 | 
			
		||||
	const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
 | 
			
		||||
	nodes[nodes.length - 1].click();
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ipcRenderer.on('shortcut', () => {
 | 
			
		||||
const shortcut = () => {
 | 
			
		||||
	// Create the menu for the below
 | 
			
		||||
	const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
 | 
			
		||||
	// Additional check
 | 
			
		||||
@@ -60,6 +19,11 @@ ipcRenderer.on('shortcut', () => {
 | 
			
		||||
		// Atleast click the dropdown
 | 
			
		||||
		document.querySelector('.dropdown-toggle').click();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
process.once('loaded', () => {
 | 
			
		||||
	global.logout = logout;
 | 
			
		||||
	global.shortcut = shortcut;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// To prevent failing this script on linux we need to load it after the document loaded
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ const electron = require('electron');
 | 
			
		||||
 | 
			
		||||
const {ipcRenderer, remote} = electron;
 | 
			
		||||
 | 
			
		||||
const {Tray, Menu, nativeImage} = remote;
 | 
			
		||||
const {Tray, Menu, nativeImage, BrowserWindow} = remote;
 | 
			
		||||
 | 
			
		||||
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
 | 
			
		||||
 | 
			
		||||
@@ -102,6 +102,16 @@ const renderNativeImage = function (arg) {
 | 
			
		||||
		});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function sendAction(action) {
 | 
			
		||||
	const win = BrowserWindow.getAllWindows()[0];
 | 
			
		||||
 | 
			
		||||
	if (process.platform === 'darwin') {
 | 
			
		||||
		win.restore();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	win.webContents.send(action);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const createTray = function () {
 | 
			
		||||
	window.tray = new Tray(iconPath());
 | 
			
		||||
	const contextMenu = Menu.buildFromTemplate([{
 | 
			
		||||
@@ -114,9 +124,9 @@ const createTray = function () {
 | 
			
		||||
		type: 'separator'
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		label: 'Change Zulip server',
 | 
			
		||||
		label: 'Manage Zulip servers',
 | 
			
		||||
		click() {
 | 
			
		||||
			ipcRenderer.send('traychangeserver');
 | 
			
		||||
			sendAction('open-settings');
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
@@ -125,8 +135,7 @@ const createTray = function () {
 | 
			
		||||
	{
 | 
			
		||||
		label: 'Reload',
 | 
			
		||||
		click() {
 | 
			
		||||
			remote.getCurrentWindow().reload();
 | 
			
		||||
			window.tray.destroy();
 | 
			
		||||
			sendAction('reload');
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										72
									
								
								app/renderer/js/utils/domain-util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								app/renderer/js/utils/domain-util.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const {app} = require('electron').remote;
 | 
			
		||||
const JsonDB = require('node-json-db');
 | 
			
		||||
const request = require('request');
 | 
			
		||||
 | 
			
		||||
const defaultIconUrl = 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png';
 | 
			
		||||
class DomainUtil {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
 | 
			
		||||
		// Migrate from old schema
 | 
			
		||||
		if (this.db.getData('/').domain) {
 | 
			
		||||
			this.addDomain({
 | 
			
		||||
				alias: 'Zulip',
 | 
			
		||||
				url: this.db.getData('/domain')
 | 
			
		||||
			});
 | 
			
		||||
			this.db.delete('/domain');
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getDomains() {
 | 
			
		||||
		if (this.db.getData('/').domains === undefined) {
 | 
			
		||||
			return [];
 | 
			
		||||
		} else {
 | 
			
		||||
			return this.db.getData('/domains');
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getDomain(index) {
 | 
			
		||||
		return this.db.getData(`/domains[${index}]`);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	addDomain(server) {
 | 
			
		||||
		server.icon = server.icon || defaultIconUrl;
 | 
			
		||||
		this.db.push('/domains[]', server, true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	removeDomains() {
 | 
			
		||||
		this.db.delete('/domains');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	removeDomain(index) {
 | 
			
		||||
		this.db.delete(`/domains[${index}]`);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	checkDomain(domain) {
 | 
			
		||||
		const hasPrefix = (domain.indexOf('http') === 0);
 | 
			
		||||
		if (!hasPrefix) {
 | 
			
		||||
			domain = (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const checkDomain = domain + '/static/audio/zulip.ogg';
 | 
			
		||||
 | 
			
		||||
		return new Promise((resolve, reject) => {
 | 
			
		||||
			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}?`)) {
 | 
			
		||||
						resolve(domain);
 | 
			
		||||
					} else {
 | 
			
		||||
						reject('Untrusted Certificate.');
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					reject('Not a valid Zulip server');
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = DomainUtil;
 | 
			
		||||
							
								
								
									
										26
									
								
								app/renderer/main.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/renderer/main.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
<!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</title>
 | 
			
		||||
    <link rel="stylesheet" href="css/servermanager.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="tabs-container"></div>
 | 
			
		||||
        <div id="actions-container">
 | 
			
		||||
          <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>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </body>
 | 
			
		||||
  <script src="js/main.js"></script>  
 | 
			
		||||
</html>
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta charset="UTF-8">
 | 
			
		||||
		<link rel="stylesheet" href="css/pref.css">
 | 
			
		||||
	</head>
 | 
			
		||||
	<body>
 | 
			
		||||
		<div class="close" id="close-button">Close</div>
 | 
			
		||||
		<div class="form">
 | 
			
		||||
			<form onsubmit="prefDomain(); return false">
 | 
			
		||||
				<input id="url" type="text" placeholder="Server URL">
 | 
			
		||||
				<button type="submit" id="main" value="Submit">
 | 
			
		||||
				Switch</button>
 | 
			
		||||
			</form>
 | 
			
		||||
			<p id="urladded"><p>
 | 
			
		||||
			</div>
 | 
			
		||||
			<script src="js/pref.js"></script>
 | 
			
		||||
		</body>
 | 
			
		||||
	</html>
 | 
			
		||||
							
								
								
									
										45
									
								
								app/renderer/preference.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								app/renderer/preference.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
<!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 - 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>
 | 
			
		||||
  </body>
 | 
			
		||||
  <script src="js/preference.js"></script>  
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							@@ -19,7 +19,7 @@
 | 
			
		||||
    "url": "https://github.com/zulip/zulip-electron/issues"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "electron ./app/main",
 | 
			
		||||
    "start": "electron app --disable-http-cache",
 | 
			
		||||
    "postinstall": "install-app-deps",
 | 
			
		||||
    "test": "gulp test && xo",
 | 
			
		||||
    "dev": "gulp dev",
 | 
			
		||||
@@ -75,7 +75,7 @@
 | 
			
		||||
    "win": {
 | 
			
		||||
      "target": "nsis",
 | 
			
		||||
      "icon": "build/icon.ico"
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "nsis": {
 | 
			
		||||
      "perMachine": true,
 | 
			
		||||
      "oneClick": false
 | 
			
		||||
@@ -104,7 +104,7 @@
 | 
			
		||||
    "esnext": true,
 | 
			
		||||
    "overrides": [
 | 
			
		||||
      {
 | 
			
		||||
        "files": "app/main/*.js",
 | 
			
		||||
        "files": "app*/**/*.js",
 | 
			
		||||
        "rules": {
 | 
			
		||||
          "max-lines": [
 | 
			
		||||
            "warn",
 | 
			
		||||
@@ -113,6 +113,10 @@
 | 
			
		||||
          "no-warning-comments": 0,
 | 
			
		||||
          "capitalized-comments": 0,
 | 
			
		||||
          "no-else-return": 0,
 | 
			
		||||
          "no-path-concat": 0,
 | 
			
		||||
          "no-alert": 0,
 | 
			
		||||
          "guard-for-in": 0,
 | 
			
		||||
          "prefer-promise-reject-errors": 0,
 | 
			
		||||
          "import/no-unresolved": 0,
 | 
			
		||||
          "import/no-extraneous-dependencies": 0
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ describe('application launch', function () {
 | 
			
		||||
  beforeEach(function () {
 | 
			
		||||
    this.app = new Application({
 | 
			
		||||
      path: require('electron'),
 | 
			
		||||
      args: [__dirname + '/../app/renderer/index.html']
 | 
			
		||||
      args: [__dirname + '/../app/renderer/main.html']
 | 
			
		||||
    })
 | 
			
		||||
    return this.app.start()
 | 
			
		||||
  })
 | 
			
		||||
@@ -20,7 +20,7 @@ describe('application launch', function () {
 | 
			
		||||
 | 
			
		||||
  it('shows an initial window', function () {
 | 
			
		||||
    return this.app.client.getWindowCount().then(function (count) {
 | 
			
		||||
      assert.equal(count, 1)
 | 
			
		||||
      assert.equal(count, 2)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user