mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-11-01 12:33:31 +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