mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-11-05 06:23:14 +00:00
Compare commits
123 Commits
v1.2.0-bet
...
v1.3.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58f97038b1 | ||
|
|
6030d4c62a | ||
|
|
206f726e4a | ||
|
|
9a36fffaac | ||
|
|
30af4277e0 | ||
|
|
98d23aaae1 | ||
|
|
20feb9bd38 | ||
|
|
56090151c2 | ||
|
|
84a69ce455 | ||
|
|
2424f7a995 | ||
|
|
c3c60c98d6 | ||
|
|
24017631c0 | ||
|
|
82a9d13a3c | ||
|
|
c064322234 | ||
|
|
32542d500a | ||
|
|
5e8a971789 | ||
|
|
a7a9e96a58 | ||
|
|
347f6e50eb | ||
|
|
b873b358fe | ||
|
|
caf4545902 | ||
|
|
9c2b7eeb27 | ||
|
|
d25d71cb91 | ||
|
|
289417e5a9 | ||
|
|
fd549e44a6 | ||
|
|
ae1ffe7ccc | ||
|
|
a04e14545e | ||
|
|
9b928a16b7 | ||
|
|
b54cedbdbb | ||
|
|
3f9ba0a2bb | ||
|
|
9e8ec3b6d4 | ||
|
|
c3072854fd | ||
|
|
197e9a0520 | ||
|
|
ff84792374 | ||
|
|
a0ae29b34a | ||
|
|
f880683d9c | ||
|
|
d80b4a813c | ||
|
|
3567f6be6c | ||
|
|
03358b1e50 | ||
|
|
b098f9e616 | ||
|
|
d2fc5bd5e8 | ||
|
|
f5e15e3c85 | ||
|
|
f7002ecdf3 | ||
|
|
48e5396092 | ||
|
|
6901b5f128 | ||
|
|
df0adb373d | ||
|
|
1c0cf148f8 | ||
|
|
c644fa2778 | ||
|
|
7962ccf19a | ||
|
|
3ad73a1eaa | ||
|
|
4445baafa9 | ||
|
|
63cfcbbaf1 | ||
|
|
769972fc4b | ||
|
|
2c8cf2b959 | ||
|
|
068fe249ea | ||
|
|
d8c08c1c5b | ||
|
|
56189806a9 | ||
|
|
0f6e48c65f | ||
|
|
fa50243dbb | ||
|
|
9e4e5e9bfd | ||
|
|
c413a65f07 | ||
|
|
53c91e890a | ||
|
|
6e3017d5e7 | ||
|
|
1e57daa8bf | ||
|
|
f1ed6695fb | ||
|
|
c5d9eceb6d | ||
|
|
f2a7ce188d | ||
|
|
3dd9c89a0e | ||
|
|
697948c9d8 | ||
|
|
5150b7c57c | ||
|
|
ce88da3de9 | ||
|
|
0617f41a2d | ||
|
|
975bcbbe31 | ||
|
|
e44af311ce | ||
|
|
297ada5565 | ||
|
|
1fe64cb8d7 | ||
|
|
6557a7d606 | ||
|
|
9a0f6648ff | ||
|
|
3cd4890e60 | ||
|
|
dc287e7e57 | ||
|
|
ac0d998804 | ||
|
|
1d38ebdf05 | ||
|
|
d70783600d | ||
|
|
527196cdbb | ||
|
|
ffeb960851 | ||
|
|
a36b39ec73 | ||
|
|
2deb63b557 | ||
|
|
fcd97f3a32 | ||
|
|
67dc1e2a11 | ||
|
|
c37ae73392 | ||
|
|
9e667e3cb0 | ||
|
|
505fea0e91 | ||
|
|
70ff8db756 | ||
|
|
d6975f7b2a | ||
|
|
6cbea1acba | ||
|
|
d4decfb6af | ||
|
|
5bbf710529 | ||
|
|
b057bffe42 | ||
|
|
4e04c85258 | ||
|
|
bfbd9d4578 | ||
|
|
6594da6ddd | ||
|
|
38abf2deab | ||
|
|
0004152e18 | ||
|
|
e8be57d710 | ||
|
|
64e8419410 | ||
|
|
f35a3df63b | ||
|
|
9e15ed2699 | ||
|
|
cdc99cda26 | ||
|
|
7e08af5ced | ||
|
|
0ee85bea16 | ||
|
|
592584fcf4 | ||
|
|
8e1b7a0289 | ||
|
|
715cf8d86f | ||
|
|
0dc20cc66c | ||
|
|
c860832a73 | ||
|
|
27a29aeba6 | ||
|
|
d0bd7e1f1c | ||
|
|
72974b075f | ||
|
|
947bab657f | ||
|
|
5a2975ca4d | ||
|
|
499743e99d | ||
|
|
6bd4c44893 | ||
|
|
5b82a82313 | ||
|
|
0208e407f0 |
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,10 +1,26 @@
|
|||||||
node_modules
|
# Dependency directories
|
||||||
npm-debug.log
|
node_modules/
|
||||||
domain.json
|
|
||||||
dist
|
# npm cache directory
|
||||||
config.gypi
|
.npm
|
||||||
|
|
||||||
|
# Compiled binary build directory
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
// osx garbage
|
// osx garbage
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# miscellaneous
|
||||||
.idea
|
.idea
|
||||||
|
config.gypi
|
||||||
@@ -4,18 +4,14 @@ const electron = require('electron');
|
|||||||
const {app} = require('electron');
|
const {app} = require('electron');
|
||||||
const ipc = require('electron').ipcMain;
|
const ipc = require('electron').ipcMain;
|
||||||
const electronLocalshortcut = require('electron-localshortcut');
|
const electronLocalshortcut = require('electron-localshortcut');
|
||||||
const Configstore = require('electron-config');
|
|
||||||
const isDev = require('electron-is-dev');
|
const isDev = require('electron-is-dev');
|
||||||
|
const windowStateKeeper = require('electron-window-state');
|
||||||
const appMenu = require('./menu');
|
const appMenu = require('./menu');
|
||||||
const {appUpdater} = require('./autoupdater');
|
const {appUpdater} = require('./autoupdater');
|
||||||
|
|
||||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||||
require('electron-debug')();
|
require('electron-debug')();
|
||||||
|
|
||||||
const conf = new Configstore();
|
|
||||||
|
|
||||||
// Setting userAgent so that server-side code can identify the desktop app
|
|
||||||
|
|
||||||
// Prevent window being garbage collected
|
// Prevent window being garbage collected
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
|
|
||||||
@@ -49,12 +45,19 @@ const iconPath = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function createMainWindow() {
|
function createMainWindow() {
|
||||||
|
// Load the previous state with fallback to defaults
|
||||||
|
const mainWindowState = windowStateKeeper({
|
||||||
|
defaultWidth: 1000,
|
||||||
|
defaultHeight: 600
|
||||||
|
});
|
||||||
const win = new electron.BrowserWindow({
|
const win = new electron.BrowserWindow({
|
||||||
// This settings needs to be saved in config
|
// This settings needs to be saved in config
|
||||||
title: 'Zulip',
|
title: 'Zulip',
|
||||||
width: conf.get('width') || 1000,
|
|
||||||
height: conf.get('height') || 600,
|
|
||||||
icon: iconPath(),
|
icon: iconPath(),
|
||||||
|
x: mainWindowState.x,
|
||||||
|
y: mainWindowState.y,
|
||||||
|
width: mainWindowState.width,
|
||||||
|
height: mainWindowState.height,
|
||||||
minWidth: 600,
|
minWidth: 600,
|
||||||
minHeight: 500,
|
minHeight: 500,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
@@ -87,45 +90,18 @@ function createMainWindow() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unregister all the shortcuts so that they don't interfare with other apps
|
// Unregister all the shortcuts so that they don't interfare with other apps
|
||||||
electronLocalshortcut.unregisterAll(mainWindow);
|
electronLocalshortcut.unregisterAll(mainWindow);
|
||||||
});
|
});
|
||||||
|
|
||||||
win.setTitle('Zulip');
|
win.setTitle('Zulip');
|
||||||
|
|
||||||
// Let's save browser window position
|
win.on('enter-full-screen', () => {
|
||||||
if (conf.get('x') || conf.get('y')) {
|
win.webContents.send('enter-fullscreen');
|
||||||
win.setPosition(conf.get('x'), conf.get('y'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conf.get('maximize')) {
|
|
||||||
win.maximize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle sizing events so we can persist them.
|
|
||||||
win.on('maximize', () => {
|
|
||||||
conf.set('maximize', true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
win.on('unmaximize', () => {
|
win.on('leave-full-screen', () => {
|
||||||
conf.set('maximize', false);
|
win.webContents.send('leave-fullscreen');
|
||||||
});
|
|
||||||
|
|
||||||
win.on('resize', function () {
|
|
||||||
const size = this.getSize();
|
|
||||||
conf.set({
|
|
||||||
width: size[0],
|
|
||||||
height: size[1]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// On osx it's 'moved'
|
|
||||||
win.on('move', function () {
|
|
||||||
const pos = this.getPosition();
|
|
||||||
conf.set({
|
|
||||||
x: pos[0],
|
|
||||||
y: pos[1]
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// To destroy tray icon when navigate to a new URL
|
// To destroy tray icon when navigate to a new URL
|
||||||
@@ -135,17 +111,21 @@ function createMainWindow() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Let us register listeners on the window, so we can update the state
|
||||||
|
// automatically (the listeners will be removed when the window is closed)
|
||||||
|
// and restore the maximized or full screen state
|
||||||
|
mainWindowState.manage(win);
|
||||||
|
|
||||||
return win;
|
return win;
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerLocalShortcuts(page) {
|
function registerLocalShortcuts(page) {
|
||||||
// TODO - use global shortcut instead
|
// Somehow, reload action cannot be overwritten by the menu item
|
||||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
|
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
|
||||||
// page.send('reload');
|
page.send('reload-viewer');
|
||||||
mainWindow.reload();
|
|
||||||
page.send('destroytray');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also adding these shortcuts because some users might want to use it instead of CMD/Left-Right
|
||||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
|
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
|
||||||
page.send('back');
|
page.send('back');
|
||||||
});
|
});
|
||||||
@@ -173,7 +153,9 @@ app.on('activate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('ready', () => {
|
app.on('ready', () => {
|
||||||
electron.Menu.setApplicationMenu(appMenu);
|
appMenu.setMenu({
|
||||||
|
tabs: []
|
||||||
|
});
|
||||||
mainWindow = createMainWindow();
|
mainWindow = createMainWindow();
|
||||||
|
|
||||||
const page = mainWindow.webContents;
|
const page = mainWindow.webContents;
|
||||||
@@ -191,9 +173,9 @@ app.on('ready', () => {
|
|||||||
appUpdater();
|
appUpdater();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
electron.powerMonitor.on('resume', () => {
|
electron.powerMonitor.on('resume', () => {
|
||||||
mainWindow.reload();
|
page.send('reload-viewer');
|
||||||
page.send('destroytray');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('focus-app', () => {
|
ipc.on('focus-app', () => {
|
||||||
@@ -204,11 +186,10 @@ app.on('ready', () => {
|
|||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('reload-main', () => {
|
// Reload full app not just webview, useful in debugging
|
||||||
page.reload();
|
ipc.on('reload-full-app', () => {
|
||||||
|
mainWindow.reload();
|
||||||
page.send('destroytray');
|
page.send('destroytray');
|
||||||
electronLocalshortcut.unregisterAll(mainWindow);
|
|
||||||
registerLocalShortcuts(page);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('toggle-app', () => {
|
ipc.on('toggle-app', () => {
|
||||||
@@ -223,12 +204,30 @@ app.on('ready', () => {
|
|||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
app.setBadgeCount(messageCount);
|
app.setBadgeCount(messageCount);
|
||||||
}
|
}
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
if (!mainWindow.isFocused()) {
|
||||||
|
mainWindow.flashFrame(true);
|
||||||
|
}
|
||||||
|
if (messageCount === 0) {
|
||||||
|
mainWindow.setOverlayIcon(null, '');
|
||||||
|
} else {
|
||||||
|
page.send('render-taskbar-icon', messageCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
page.send('tray', messageCount);
|
page.send('tray', messageCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipc.on('update-taskbar-icon', (event, data, text) => {
|
||||||
|
const img = electron.nativeImage.createFromDataURL(data);
|
||||||
|
mainWindow.setOverlayIcon(img, text);
|
||||||
|
});
|
||||||
|
|
||||||
ipc.on('forward-message', (event, listener, ...params) => {
|
ipc.on('forward-message', (event, listener, ...params) => {
|
||||||
console.log(listener, ...params);
|
page.send(listener, ...params);
|
||||||
page.send(listener);
|
});
|
||||||
|
|
||||||
|
ipc.on('update-menu', (event, props) => {
|
||||||
|
appMenu.setMenu(props);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('register-server-tab-shortcut', (event, index) => {
|
ipc.on('register-server-tab-shortcut', (event, index) => {
|
||||||
@@ -237,6 +236,14 @@ app.on('ready', () => {
|
|||||||
page.send('switch-server-tab', index - 1);
|
page.send('switch-server-tab', index - 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipc.on('local-shortcuts', (event, enable) => {
|
||||||
|
if (enable) {
|
||||||
|
registerLocalShortcuts(page);
|
||||||
|
} else {
|
||||||
|
electronLocalshortcut.unregisterAll(mainWindow);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('will-quit', () => {
|
app.on('will-quit', () => {
|
||||||
|
|||||||
540
app/main/menu.js
540
app/main/menu.js
@@ -1,377 +1,381 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const electron = require('electron');
|
const {dialog, app, shell, BrowserWindow, Menu} = require('electron');
|
||||||
|
|
||||||
const {dialog} = require('electron');
|
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
|
||||||
|
|
||||||
const app = electron.app;
|
|
||||||
const BrowserWindow = electron.BrowserWindow;
|
|
||||||
const shell = electron.shell;
|
|
||||||
const appName = app.getName();
|
const appName = app.getName();
|
||||||
|
|
||||||
function sendAction(action) {
|
class AppMenu {
|
||||||
const win = BrowserWindow.getAllWindows()[0];
|
getHistorySubmenu() {
|
||||||
|
return [{
|
||||||
if (process.platform === 'darwin') {
|
label: 'Back',
|
||||||
win.restore();
|
accelerator: process.platform === 'darwin' ? 'Command+Left' : 'Alt+Left',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('back');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Forward',
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Command+Right' : 'Alt+Right',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('forward');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
win.webContents.send(action);
|
getViewSubmenu() {
|
||||||
}
|
return [{
|
||||||
|
label: 'Reload',
|
||||||
function clearCache() {
|
accelerator: 'CommandOrControl+R',
|
||||||
const win = BrowserWindow.getAllWindows()[0];
|
click(item, focusedWindow) {
|
||||||
const ses = win.webContents.session;
|
if (focusedWindow) {
|
||||||
ses.clearCache(() => {
|
AppMenu.sendAction('reload-viewer');
|
||||||
dialog.showMessageBox({type: 'info', buttons: [], message: 'Cache cleared!'});
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewSubmenu = [
|
|
||||||
{
|
|
||||||
label: 'Reload',
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
sendAction('reload');
|
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
},
|
label: 'Hard Reload',
|
||||||
{
|
accelerator: 'CommandOrControl+Shift+R',
|
||||||
type: 'separator'
|
click(item, focusedWindow) {
|
||||||
},
|
if (focusedWindow) {
|
||||||
{
|
AppMenu.sendAction('hard-reload');
|
||||||
role: 'togglefullscreen'
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Zoom In',
|
|
||||||
accelerator: 'CommandOrControl+=',
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
sendAction('zoomIn');
|
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
},
|
type: 'separator'
|
||||||
{
|
}, {
|
||||||
label: 'Zoom Out',
|
role: 'togglefullscreen'
|
||||||
accelerator: 'CommandOrControl+-',
|
}, {
|
||||||
click(item, focusedWindow) {
|
label: 'Zoom In',
|
||||||
if (focusedWindow) {
|
accelerator: 'CommandOrControl+=',
|
||||||
sendAction('zoomOut');
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('zoomIn');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
},
|
label: 'Zoom Out',
|
||||||
{
|
accelerator: 'CommandOrControl+-',
|
||||||
label: 'Actual Size',
|
click(item, focusedWindow) {
|
||||||
accelerator: 'CommandOrControl+0',
|
if (focusedWindow) {
|
||||||
click(item, focusedWindow) {
|
AppMenu.sendAction('zoomOut');
|
||||||
if (focusedWindow) {
|
}
|
||||||
sendAction('zoomActualSize');
|
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
},
|
label: 'Actual Size',
|
||||||
{
|
accelerator: 'CommandOrControl+0',
|
||||||
type: 'separator'
|
click(item, focusedWindow) {
|
||||||
},
|
if (focusedWindow) {
|
||||||
{
|
AppMenu.sendAction('zoomActualSize');
|
||||||
label: 'Toggle Tray Icon',
|
}
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
focusedWindow.webContents.send('toggletray');
|
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
},
|
type: 'separator'
|
||||||
{
|
}, {
|
||||||
label: 'Toggle DevTools for Zulip App',
|
label: 'Toggle Tray Icon',
|
||||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
click(item, focusedWindow) {
|
||||||
click(item, focusedWindow) {
|
if (focusedWindow) {
|
||||||
if (focusedWindow) {
|
focusedWindow.webContents.send('toggletray');
|
||||||
focusedWindow.webContents.toggleDevTools();
|
}
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
},
|
label: 'Toggle Sidebar',
|
||||||
{
|
accelerator: 'CommandOrControl+S',
|
||||||
label: 'Toggle DevTools for Active Tab',
|
click(item, focusedWindow) {
|
||||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
|
if (focusedWindow) {
|
||||||
click(item, focusedWindow) {
|
const newValue = !ConfigUtil.getConfigItem('show-sidebar');
|
||||||
if (focusedWindow) {
|
focusedWindow.webContents.send('toggle-sidebar', newValue);
|
||||||
sendAction('tab-devtools');
|
ConfigUtil.setConfigItem('show-sidebar', newValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
label: 'Toggle DevTools for Zulip App',
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
focusedWindow.webContents.toggleDevTools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'Toggle DevTools for Active Tab',
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('tab-devtools');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const helpSubmenu = [
|
getHelpSubmenu() {
|
||||||
{
|
return [{
|
||||||
label: `${appName} Website`,
|
label: `${appName} Website`,
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal('https://zulip.org');
|
shell.openExternal('https://zulipchat.com/help/');
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
|
||||||
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
|
enabled: false
|
||||||
enabled: false
|
}, {
|
||||||
},
|
label: 'Report an Issue...',
|
||||||
{
|
click() {
|
||||||
label: 'Report an Issue...',
|
const body = `
|
||||||
click() {
|
<!-- Please succinctly describe your issue and steps to reproduce it. -->
|
||||||
const body = `
|
-
|
||||||
<!-- Please succinctly describe your issue and steps to reproduce it. -->
|
${app.getName()} ${app.getVersion()}
|
||||||
-
|
Electron ${process.versions.electron}
|
||||||
${app.getName()} ${app.getVersion()}
|
${process.platform} ${process.arch} ${os.release()}`;
|
||||||
Electron ${process.versions.electron}
|
shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`);
|
||||||
${process.platform} ${process.arch} ${os.release()}`;
|
}
|
||||||
|
}];
|
||||||
shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const darwinTpl = [
|
getWindowSubmenu(tabs, activeTabIndex) {
|
||||||
|
const initialSubmenu = [{
|
||||||
|
role: 'minimize'
|
||||||
|
}, {
|
||||||
|
role: 'close'
|
||||||
|
}];
|
||||||
|
|
||||||
{
|
if (tabs.length > 0) {
|
||||||
label: `${app.getName()}`,
|
const ShortcutKey = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
|
||||||
submenu: [
|
initialSubmenu.push({
|
||||||
{
|
type: 'separator'
|
||||||
label: 'Zulip desktop',
|
});
|
||||||
|
for (let i = 0; i < tabs.length; i++) {
|
||||||
|
initialSubmenu.push({
|
||||||
|
label: tabs[i].webview.props.name,
|
||||||
|
accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`,
|
||||||
|
checked: tabs[i].props.index === activeTabIndex,
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('switch-server-tab', tabs[i].props.index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: 'radio'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialSubmenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDarwinTpl(props) {
|
||||||
|
const {tabs, activeTabIndex} = props;
|
||||||
|
|
||||||
|
return [{
|
||||||
|
label: `${app.getName()}`,
|
||||||
|
submenu: [{
|
||||||
|
label: 'Zulip Desktop',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('open-about');
|
AppMenu.sendAction('open-about');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
accelerator: 'Cmd+,',
|
accelerator: 'Cmd+,',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('open-settings');
|
AppMenu.sendAction('open-settings');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Keyboard Shortcuts',
|
||||||
label: 'Keyboard shortcuts',
|
|
||||||
accelerator: 'Cmd+K',
|
accelerator: 'Cmd+K',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('shortcut');
|
AppMenu.sendAction('shortcut');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Clear Cache',
|
label: 'Clear Cache',
|
||||||
click() {
|
click() {
|
||||||
clearCache();
|
AppMenu.clearCache();
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Log Out',
|
label: 'Log Out',
|
||||||
accelerator: 'Cmd+L',
|
accelerator: 'Cmd+L',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('log-out');
|
AppMenu.sendAction('log-out');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'services',
|
role: 'services',
|
||||||
submenu: []
|
submenu: []
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'hide'
|
role: 'hide'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'hideothers'
|
role: 'hideothers'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'unhide'
|
role: 'unhide'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'quit'
|
role: 'quit'
|
||||||
}
|
}]
|
||||||
]
|
}, {
|
||||||
},
|
label: 'Edit',
|
||||||
{
|
submenu: [{
|
||||||
label: 'Edit',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
role: 'undo'
|
role: 'undo'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'redo'
|
role: 'redo'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'cut'
|
role: 'cut'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'copy'
|
role: 'copy'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'paste'
|
role: 'paste'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'pasteandmatchstyle'
|
role: 'pasteandmatchstyle'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'delete'
|
role: 'delete'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'selectall'
|
role: 'selectall'
|
||||||
}
|
}]
|
||||||
]
|
}, {
|
||||||
},
|
label: 'View',
|
||||||
{
|
submenu: this.getViewSubmenu()
|
||||||
label: 'View',
|
}, {
|
||||||
submenu: viewSubmenu
|
label: 'History',
|
||||||
},
|
submenu: this.getHistorySubmenu()
|
||||||
{
|
}, {
|
||||||
role: 'window',
|
label: 'Window',
|
||||||
submenu: [
|
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
|
||||||
{
|
}, {
|
||||||
role: 'minimize'
|
role: 'help',
|
||||||
},
|
submenu: this.getHelpSubmenu()
|
||||||
{
|
}];
|
||||||
role: 'close'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'front'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'help',
|
|
||||||
submenu: helpSubmenu
|
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const otherTpl = [
|
getOtherTpl(props) {
|
||||||
{
|
const {tabs, activeTabIndex} = props;
|
||||||
label: 'File',
|
|
||||||
submenu: [
|
return [{
|
||||||
{
|
label: 'File',
|
||||||
label: 'Zulip desktop',
|
submenu: [{
|
||||||
|
label: 'Zulip Desktop',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('open-about');
|
AppMenu.sendAction('open-about');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
accelerator: 'Ctrl+,',
|
accelerator: 'Ctrl+,',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('open-settings');
|
AppMenu.sendAction('open-settings');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
label: 'Keyboard Shortcuts',
|
||||||
label: 'Keyboard shortcuts',
|
|
||||||
accelerator: 'Ctrl+K',
|
accelerator: 'Ctrl+K',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('shortcut');
|
AppMenu.sendAction('shortcut');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Clear Cache',
|
label: 'Clear Cache',
|
||||||
click() {
|
click() {
|
||||||
clearCache();
|
AppMenu.clearCache();
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Log Out',
|
label: 'Log Out',
|
||||||
accelerator: 'Ctrl+L',
|
accelerator: 'Ctrl+L',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
sendAction('log-out');
|
AppMenu.sendAction('log-out');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'quit',
|
role: 'quit',
|
||||||
accelerator: 'Ctrl+Q'
|
accelerator: 'Ctrl+Q'
|
||||||
}
|
}]
|
||||||
]
|
}, {
|
||||||
},
|
label: 'Edit',
|
||||||
{
|
submenu: [{
|
||||||
label: 'Edit',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
role: 'undo'
|
role: 'undo'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'redo'
|
role: 'redo'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'cut'
|
role: 'cut'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'copy'
|
role: 'copy'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'paste'
|
role: 'paste'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'pasteandmatchstyle'
|
role: 'pasteandmatchstyle'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'delete'
|
role: 'delete'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
role: 'selectall'
|
role: 'selectall'
|
||||||
}
|
}]
|
||||||
]
|
}, {
|
||||||
},
|
label: 'View',
|
||||||
{
|
submenu: this.getViewSubmenu()
|
||||||
label: 'View',
|
}, {
|
||||||
submenu: viewSubmenu
|
label: 'History',
|
||||||
},
|
submenu: this.getHistorySubmenu()
|
||||||
{
|
}, {
|
||||||
role: 'help',
|
label: 'Window',
|
||||||
submenu: helpSubmenu
|
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
|
||||||
|
}, {
|
||||||
|
role: 'help',
|
||||||
|
submenu: this.getHelpSubmenu()
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const tpl = process.platform === 'darwin' ? darwinTpl : otherTpl;
|
static sendAction(action, ...params) {
|
||||||
|
const win = BrowserWindow.getAllWindows()[0];
|
||||||
|
|
||||||
module.exports = electron.Menu.buildFromTemplate(tpl);
|
if (process.platform === 'darwin') {
|
||||||
|
win.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
win.webContents.send(action, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static clearCache() {
|
||||||
|
const win = BrowserWindow.getAllWindows()[0];
|
||||||
|
const ses = win.webContents.session;
|
||||||
|
ses.clearCache(() => {
|
||||||
|
dialog.showMessageBox({type: 'info', buttons: [], message: 'Cache cleared!'});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMenu(props) {
|
||||||
|
const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props);
|
||||||
|
const menu = Menu.buildFromTemplate(tpl);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new AppMenu();
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "zulip",
|
"name": "zulip",
|
||||||
"productName": "Zulip",
|
"productName": "Zulip",
|
||||||
"version": "1.2.0-beta",
|
"version": "1.3.0-beta",
|
||||||
"description": "Zulip Desktop App",
|
"description": "Zulip Desktop App",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"email": "<svnitakash@gmail.com>",
|
"email": "<svnitakash@gmail.com>",
|
||||||
"copyright": "©2017 Kandra Labs, Inc.",
|
"copyright": "©2017 Kandra Labs, Inc.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Akash Nimare",
|
"name": "Kandra Labs, Inc.",
|
||||||
"email": "svnitakash@gmail.com"
|
"email": "svnitakash@gmail.com"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -27,16 +27,15 @@
|
|||||||
"InstantMessaging"
|
"InstantMessaging"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-config": "0.2.1",
|
"electron-debug": "1.4.0",
|
||||||
"electron-debug": "1.1.0",
|
"electron-is-dev": "0.3.0",
|
||||||
"electron-is-dev": "0.1.2",
|
"electron-localshortcut": "2.0.2",
|
||||||
"electron-localshortcut": "1.0.0",
|
"electron-log": "2.2.7",
|
||||||
"electron-log": "1.3.0",
|
"electron-spellchecker": "1.2.0",
|
||||||
"electron-spellchecker": "1.0.8",
|
"electron-updater": "2.8.5",
|
||||||
"electron-updater": "1.11.2",
|
|
||||||
"https": "^1.0.0",
|
|
||||||
"node-json-db": "0.7.3",
|
"node-json-db": "0.7.3",
|
||||||
"request": "2.79.0",
|
"request": "2.81.0",
|
||||||
"wurl": "2.1.0"
|
"wurl": "2.5.0",
|
||||||
|
"electron-window-state": "4.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
/*******************
|
/*******************
|
||||||
* General rules *
|
* General rules *
|
||||||
*******************/
|
*******************/
|
||||||
html, body {
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
user-select:none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
@@ -28,40 +30,40 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Material Icons';
|
font-family: 'Material Icons';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Material Icons'),
|
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||||
local('MaterialIcons-Regular'),
|
|
||||||
url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************
|
/*******************
|
||||||
* Left Sidebar *
|
* Left Sidebar *
|
||||||
*******************/
|
*******************/
|
||||||
#tabs-container {
|
|
||||||
|
#tabs-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-icons {
|
.material-icons {
|
||||||
font-family: 'Material Icons';
|
font-family: 'Material Icons';
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
/* Preferred icon size */
|
/* Preferred icon size */
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
/* Support for all WebKit browsers. */
|
/* Support for all WebKit browsers. */
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
/* Support for Safari and Chrome. */
|
/* Support for Safari and Chrome. */
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-button {
|
.action-button {
|
||||||
@@ -71,6 +73,10 @@ html, body {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.action-button i {
|
.action-button i {
|
||||||
color: #6c8592;
|
color: #6c8592;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
@@ -80,56 +86,65 @@ html, body {
|
|||||||
color: #98a9b3;
|
color: #98a9b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab:first-child {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab.active::before {
|
.tab .server-icons {
|
||||||
content: "";
|
border-radius: 50%;
|
||||||
background: #fff;
|
width: 30px;
|
||||||
border-radius: 0 3px 3px 0;
|
padding: 3px;
|
||||||
width: 4px;
|
height: 30px;
|
||||||
position: absolute;
|
vertical-align: top;
|
||||||
height: 35px;
|
|
||||||
left: -10px;
|
|
||||||
top: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .server-tab {
|
.tab .server-tab {
|
||||||
background: #a4d3c4;
|
width: 100%;
|
||||||
background-size: 28px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
width: 35px;
|
|
||||||
height: 35px;
|
height: 35px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 5px 0;
|
margin: 5px 0 2px 0;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
line-height: 31px;
|
line-height: 31px;
|
||||||
color: #194a2b;
|
color: #eee;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
padding: 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .server-tab:hover {
|
.tab .server-tab:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .functional-tab {
|
.tab.functional-tab {
|
||||||
background: #eee;
|
height: 46px;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .functional-tab i {
|
.tab.functional-tab.active .server-tab {
|
||||||
|
padding: 2px 0;
|
||||||
|
height: 40px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.functional-tab .server-tab i {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab.active .server-tab {
|
.tab.active .server-tab {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
background-color: #648478;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .server-tab-badge.active {
|
.tab .server-tab-badge.active {
|
||||||
@@ -141,14 +156,15 @@ html, body {
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -6px;
|
right: 5px;
|
||||||
z-index: 15;
|
z-index: 15;
|
||||||
top: -2px;
|
top: 6px;
|
||||||
float: right;
|
float: right;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 17px;
|
line-height: 17px;
|
||||||
display: block;
|
display: block;
|
||||||
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .server-tab-badge {
|
.tab .server-tab-badge {
|
||||||
@@ -157,7 +173,7 @@ html, body {
|
|||||||
|
|
||||||
.tab .server-tab-badge.close-button {
|
.tab .server-tab-badge.close-button {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
padding: 0 0 0 1px;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .server-tab-badge.close-button i {
|
.tab .server-tab-badge.close-button i {
|
||||||
@@ -172,29 +188,134 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab .server-tab-shortcut {
|
.tab .server-tab-shortcut {
|
||||||
color: #eee;
|
color: #648478;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************
|
/*******************
|
||||||
* Webview Area *
|
* Webview Area *
|
||||||
*******************/
|
*******************/
|
||||||
|
|
||||||
|
#webviews-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
webview {
|
webview {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s ease-in;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webview.onload {
|
||||||
|
transition: opacity 1s cubic-bezier(0.95, 0.05, 0.795, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
webview.disabled {
|
webview.disabled {
|
||||||
flex: 0 1;
|
flex: 0 1;
|
||||||
height: 0;
|
height: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
webview:focus {
|
webview:focus {
|
||||||
outline: 0px solid transparent;
|
outline: 0px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Tooltip styling */
|
||||||
|
|
||||||
|
#reload-tooltip,
|
||||||
|
#setting-tooltip {
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: #222c31;
|
||||||
|
margin-left: 68px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 0px;
|
||||||
|
z-index: 1000;
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
width: 55px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reload-tooltip:after,
|
||||||
|
#setting-tooltip:after {
|
||||||
|
content: " ";
|
||||||
|
border-top: 8px solid transparent;
|
||||||
|
border-bottom: 8px solid transparent;
|
||||||
|
border-right: 8px solid #222c31;
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#collapse-button {
|
||||||
|
bottom: 30px;
|
||||||
|
left: 20px;
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #222c31;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: #999 1px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#collapse-button i {
|
||||||
|
color: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full screen Popup container */
|
||||||
|
.popup .popuptext {
|
||||||
|
visibility: hidden;
|
||||||
|
background-color: #555;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 9px 0;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
font-family: arial;
|
||||||
|
width: 240px;
|
||||||
|
top: 15px;
|
||||||
|
height: 20px;
|
||||||
|
left: 43%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .show {
|
||||||
|
visibility: visible;
|
||||||
|
animation: cssAnimation 0s ease-in 5s forwards;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cssAnimation {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,11 +106,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img.server-info-icon {
|
img.server-info-icon {
|
||||||
background: #a4d3c4;
|
width: 36px;
|
||||||
border-radius: 4px;
|
height: 36px;
|
||||||
width: 28px;
|
padding: 4px;
|
||||||
height: 28px;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-info-left {
|
.server-info-left {
|
||||||
@@ -124,19 +122,15 @@ img.server-info-icon {
|
|||||||
|
|
||||||
.server-info-row {
|
.server-info-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
line-height: 27px;
|
margin: 8px 0 0 0;
|
||||||
margin: 8px 0;
|
|
||||||
height: 27px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-info-key {
|
.server-info-alias {
|
||||||
width: 40px;
|
|
||||||
margin-right: 20px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: right;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-info-value {
|
.server-info-url {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -165,6 +159,7 @@ img.server-info-icon {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action i {
|
.action i {
|
||||||
@@ -200,11 +195,8 @@ img.server-info-icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
height: 0 !important;
|
display: none;
|
||||||
width: 0 !important;
|
|
||||||
margin: 5px !important;
|
margin: 5px !important;
|
||||||
opacity: 0 !important;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.red {
|
.red {
|
||||||
@@ -229,4 +221,14 @@ img.server-info-icon {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
font-family: Courier New, Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.open-tab-button {
|
||||||
|
padding: 0 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
9
app/renderer/css/preload.css
Normal file
9
app/renderer/css/preload.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* Override css rules */
|
||||||
|
|
||||||
|
.portico-wrap>.header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portico-container>.footer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
@@ -4,11 +4,11 @@ const Tab = require(__dirname + '/../components/tab.js');
|
|||||||
|
|
||||||
class FunctionalTab extends Tab {
|
class FunctionalTab extends Tab {
|
||||||
template() {
|
template() {
|
||||||
return `<div class="tab">
|
return `<div class="tab functional-tab">
|
||||||
<div class="server-tab-badge close-button">
|
<div class="server-tab-badge close-button">
|
||||||
<i class="material-icons">close</i>
|
<i class="material-icons">close</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="server-tab functional-tab">
|
<div class="server-tab">
|
||||||
<i class="material-icons">${this.props.materialIcon}</i>
|
<i class="material-icons">${this.props.materialIcon}</i>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ class ServerTab extends Tab {
|
|||||||
template() {
|
template() {
|
||||||
return `<div class="tab">
|
return `<div class="tab">
|
||||||
<div class="server-tab-badge"></div>
|
<div class="server-tab-badge"></div>
|
||||||
<div class="server-tab" style="background-image: url('${this.props.icon}');"></div>
|
<div class="server-tab">
|
||||||
|
<img class="server-icons" src='${this.props.icon}'/>
|
||||||
|
</div>
|
||||||
<div class="server-tab-shortcut">${this.generateShortcutText()}</div>
|
<div class="server-tab-shortcut">${this.generateShortcutText()}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -39,17 +41,17 @@ class ServerTab extends Tab {
|
|||||||
|
|
||||||
const shownIndex = this.props.index + 1;
|
const shownIndex = this.props.index + 1;
|
||||||
|
|
||||||
let cmdKey = '';
|
let shortcutText = '';
|
||||||
|
|
||||||
if (SystemUtil.getOS() === 'Mac') {
|
if (SystemUtil.getOS() === 'Mac') {
|
||||||
cmdKey = '⌘';
|
shortcutText = `⌘ ${shownIndex}`;
|
||||||
} else {
|
} else {
|
||||||
cmdKey = '⌃';
|
shortcutText = `Ctrl+${shownIndex}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.send('register-server-tab-shortcut', shownIndex);
|
ipcRenderer.send('register-server-tab-shortcut', shownIndex);
|
||||||
|
|
||||||
return `${cmdKey} ${shownIndex}`;
|
return shortcutText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
|
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
|
||||||
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||||
const LinkUtil = require(__dirname + '/../utils/link-util.js');
|
const LinkUtil = require(__dirname + '/../utils/link-util.js');
|
||||||
const {app, dialog, shell} = require('electron').remote;
|
const {shell} = require('electron').remote;
|
||||||
const {ipcRenderer} = require('electron');
|
|
||||||
|
|
||||||
const BaseComponent = require(__dirname + '/../components/base.js');
|
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ class WebView extends BaseComponent {
|
|||||||
const {url} = event;
|
const {url} = event;
|
||||||
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
|
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
|
||||||
|
|
||||||
if (LinkUtil.isInternal(domainPrefix, url)) {
|
if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$el.loadURL(url);
|
this.$el.loadURL(url);
|
||||||
} else {
|
} else {
|
||||||
@@ -57,7 +59,10 @@ class WebView extends BaseComponent {
|
|||||||
this.props.onTitleChange();
|
this.props.onTitleChange();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$el.addEventListener('dom-ready', this.show.bind(this));
|
this.$el.addEventListener('dom-ready', () => {
|
||||||
|
this.$el.classList.add('onload');
|
||||||
|
this.show();
|
||||||
|
});
|
||||||
|
|
||||||
this.$el.addEventListener('did-fail-load', event => {
|
this.$el.addEventListener('did-fail-load', event => {
|
||||||
const {errorDescription} = event;
|
const {errorDescription} = event;
|
||||||
@@ -90,13 +95,23 @@ class WebView extends BaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$el.classList.remove('disabled');
|
this.$el.classList.remove('disabled');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$el.classList.remove('onload');
|
||||||
|
}, 1000);
|
||||||
this.focus();
|
this.focus();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.props.onTitleChange(this.$el.getTitle());
|
this.props.onTitleChange(this.$el.getTitle());
|
||||||
|
// Injecting preload css in webview to override some css rules
|
||||||
|
this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8'));
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.$el.focus();
|
// focus Webview and it's contents when Window regain focus.
|
||||||
|
const webContents = this.$el.getWebContents();
|
||||||
|
if (webContents && !webContents.isFocused()) {
|
||||||
|
this.$el.focus();
|
||||||
|
webContents.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
@@ -111,25 +126,6 @@ class WebView extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkConnectivity() {
|
|
||||||
return dialog.showMessageBox({
|
|
||||||
title: 'Internet connection problem',
|
|
||||||
message: 'No internet available! Try again?',
|
|
||||||
type: 'warning',
|
|
||||||
buttons: ['Try again', 'Close'],
|
|
||||||
defaultId: 0
|
|
||||||
}, index => {
|
|
||||||
if (index === 0) {
|
|
||||||
this.reload();
|
|
||||||
ipcRenderer.send('reload');
|
|
||||||
ipcRenderer.send('destroytray');
|
|
||||||
}
|
|
||||||
if (index === 1) {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomIn() {
|
zoomIn() {
|
||||||
this.zoomFactor += 0.1;
|
this.zoomFactor += 0.1;
|
||||||
this.$el.setZoomFactor(this.zoomFactor);
|
this.$el.setZoomFactor(this.zoomFactor);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
|
|||||||
const WebView = require(__dirname + '/js/components/webview.js');
|
const WebView = require(__dirname + '/js/components/webview.js');
|
||||||
const ServerTab = require(__dirname + '/js/components/server-tab.js');
|
const ServerTab = require(__dirname + '/js/components/server-tab.js');
|
||||||
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
|
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
|
||||||
|
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
|
||||||
|
|
||||||
class ServerManagerView {
|
class ServerManagerView {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -16,7 +17,15 @@ class ServerManagerView {
|
|||||||
const $actionsContainer = document.getElementById('actions-container');
|
const $actionsContainer = document.getElementById('actions-container');
|
||||||
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
|
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
|
||||||
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
|
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
|
||||||
this.$content = document.getElementById('content');
|
this.$webviewsContainer = document.getElementById('webviews-container');
|
||||||
|
|
||||||
|
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
|
||||||
|
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
|
||||||
|
this.$sidebar = document.getElementById('sidebar');
|
||||||
|
|
||||||
|
this.$fullscreenPopup = document.getElementById('fullscreen-popup');
|
||||||
|
this.$fullscreenEscapeKey = process.platform === 'darwin' ? '^⌘F' : 'F11';
|
||||||
|
this.$fullscreenPopup.innerHTML = `Press ${this.$fullscreenEscapeKey} to exit full screen`;
|
||||||
|
|
||||||
this.activeTabIndex = -1;
|
this.activeTabIndex = -1;
|
||||||
this.tabs = [];
|
this.tabs = [];
|
||||||
@@ -24,11 +33,17 @@ class ServerManagerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
this.initSidebar();
|
||||||
this.initTabs();
|
this.initTabs();
|
||||||
this.initActions();
|
this.initActions();
|
||||||
this.registerIpcs();
|
this.registerIpcs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initSidebar() {
|
||||||
|
const showSidebar = ConfigUtil.getConfigItem('show-sidebar', true);
|
||||||
|
this.toggleSidebar(showSidebar);
|
||||||
|
}
|
||||||
|
|
||||||
initTabs() {
|
initTabs() {
|
||||||
const servers = DomainUtil.getDomains();
|
const servers = DomainUtil.getDomains();
|
||||||
if (servers.length > 0) {
|
if (servers.length > 0) {
|
||||||
@@ -39,16 +54,19 @@ class ServerManagerView {
|
|||||||
} else {
|
} else {
|
||||||
this.openSettings('Servers');
|
this.openSettings('Servers');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send('local-shortcuts', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
initServer(server, index) {
|
initServer(server, index) {
|
||||||
this.tabs.push(new ServerTab({
|
this.tabs.push(new ServerTab({
|
||||||
|
role: 'server',
|
||||||
icon: server.icon,
|
icon: server.icon,
|
||||||
$root: this.$tabsContainer,
|
$root: this.$tabsContainer,
|
||||||
onClick: this.activateTab.bind(this, index),
|
onClick: this.activateTab.bind(this, index),
|
||||||
index,
|
index,
|
||||||
webview: new WebView({
|
webview: new WebView({
|
||||||
$root: this.$content,
|
$root: this.$webviewsContainer,
|
||||||
index,
|
index,
|
||||||
url: server.url,
|
url: server.url,
|
||||||
name: server.alias,
|
name: server.alias,
|
||||||
@@ -73,6 +91,18 @@ class ServerManagerView {
|
|||||||
this.$settingsButton.addEventListener('click', () => {
|
this.$settingsButton.addEventListener('click', () => {
|
||||||
this.openSettings('General');
|
this.openSettings('General');
|
||||||
});
|
});
|
||||||
|
this.$reloadButton.addEventListener('mouseover', () => {
|
||||||
|
this.$reloadTooltip.removeAttribute('style');
|
||||||
|
});
|
||||||
|
this.$reloadButton.addEventListener('mouseout', () => {
|
||||||
|
this.$reloadTooltip.style.display = 'none';
|
||||||
|
});
|
||||||
|
this.$settingsButton.addEventListener('mouseover', () => {
|
||||||
|
this.$settingsTooltip.removeAttribute('style');
|
||||||
|
});
|
||||||
|
this.$settingsButton.addEventListener('mouseout', () => {
|
||||||
|
this.$settingsTooltip.style.display = 'none';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openFunctionalTab(tabProps) {
|
openFunctionalTab(tabProps) {
|
||||||
@@ -84,12 +114,14 @@ class ServerManagerView {
|
|||||||
this.functionalTabs[tabProps.name] = this.tabs.length;
|
this.functionalTabs[tabProps.name] = this.tabs.length;
|
||||||
|
|
||||||
this.tabs.push(new FunctionalTab({
|
this.tabs.push(new FunctionalTab({
|
||||||
|
role: 'function',
|
||||||
materialIcon: tabProps.materialIcon,
|
materialIcon: tabProps.materialIcon,
|
||||||
$root: this.$tabsContainer,
|
$root: this.$tabsContainer,
|
||||||
|
index: this.functionalTabs[tabProps.name],
|
||||||
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
|
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
|
||||||
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
|
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
|
||||||
webview: new WebView({
|
webview: new WebView({
|
||||||
$root: this.$content,
|
$root: this.$webviewsContainer,
|
||||||
index: this.functionalTabs[tabProps.name],
|
index: this.functionalTabs[tabProps.name],
|
||||||
url: tabProps.url,
|
url: tabProps.url,
|
||||||
name: tabProps.name,
|
name: tabProps.name,
|
||||||
@@ -132,7 +164,7 @@ class ServerManagerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activateTab(index, hideOldTab = true) {
|
activateTab(index, hideOldTab = true) {
|
||||||
if (this.tabs[index].loading) {
|
if (this.tabs[index].webview.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,10 +178,15 @@ class ServerManagerView {
|
|||||||
|
|
||||||
this.activeTabIndex = index;
|
this.activeTabIndex = index;
|
||||||
this.tabs[index].activate();
|
this.tabs[index].activate();
|
||||||
|
|
||||||
|
ipcRenderer.send('update-menu', {
|
||||||
|
tabs: this.tabs,
|
||||||
|
activeTabIndex: this.activeTabIndex
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyTab(name, index) {
|
destroyTab(name, index) {
|
||||||
if (this.tabs[index].loading) {
|
if (this.tabs[index].webview.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +201,25 @@ class ServerManagerView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroyView() {
|
||||||
|
// Clear global variables
|
||||||
|
this.activeTabIndex = -1;
|
||||||
|
this.tabs = [];
|
||||||
|
this.functionalTabs = {};
|
||||||
|
|
||||||
|
// Clear DOM elements
|
||||||
|
this.$tabsContainer.innerHTML = '';
|
||||||
|
this.$webviewsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Destroy shortcuts
|
||||||
|
ipcRenderer.send('local-shortcuts', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadView() {
|
||||||
|
this.destroyView();
|
||||||
|
this.initTabs();
|
||||||
|
}
|
||||||
|
|
||||||
updateBadge() {
|
updateBadge() {
|
||||||
let messageCountAll = 0;
|
let messageCountAll = 0;
|
||||||
for (let i = 0; i < this.tabs.length; i++) {
|
for (let i = 0; i < this.tabs.length; i++) {
|
||||||
@@ -177,6 +233,14 @@ class ServerManagerView {
|
|||||||
ipcRenderer.send('update-badge', messageCountAll);
|
ipcRenderer.send('update-badge', messageCountAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleSidebar(show) {
|
||||||
|
if (show) {
|
||||||
|
this.$sidebar.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
this.$sidebar.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registerIpcs() {
|
registerIpcs() {
|
||||||
const webviewListeners = {
|
const webviewListeners = {
|
||||||
'webview-reload': 'reload',
|
'webview-reload': 'reload',
|
||||||
@@ -203,18 +267,68 @@ class ServerManagerView {
|
|||||||
ipcRenderer.on('open-settings', (event, settingNav) => {
|
ipcRenderer.on('open-settings', (event, settingNav) => {
|
||||||
this.openSettings(settingNav);
|
this.openSettings(settingNav);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('open-about', this.openAbout.bind(this));
|
ipcRenderer.on('open-about', this.openAbout.bind(this));
|
||||||
|
|
||||||
|
ipcRenderer.on('reload-viewer', this.reloadView.bind(this));
|
||||||
|
|
||||||
|
ipcRenderer.on('hard-reload', () => {
|
||||||
|
ipcRenderer.send('reload-full-app');
|
||||||
|
});
|
||||||
|
|
||||||
ipcRenderer.on('switch-server-tab', (event, index) => {
|
ipcRenderer.on('switch-server-tab', (event, index) => {
|
||||||
this.activateTab(index);
|
this.activateTab(index);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('toggle-sidebar', (event, show) => {
|
||||||
|
this.toggleSidebar(show);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('enter-fullscreen', () => {
|
||||||
|
this.$fullscreenPopup.classList.add('show');
|
||||||
|
this.$fullscreenPopup.classList.remove('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('leave-fullscreen', () => {
|
||||||
|
this.$fullscreenPopup.classList.remove('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
|
||||||
|
// Create a canvas from unread messagecounts
|
||||||
|
function createOverlayIcon(messageCount) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.height = 128;
|
||||||
|
canvas.width = 128;
|
||||||
|
canvas.style.letterSpacing = '-5px';
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.fillStyle = '#f42020';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(64, 64, 64, 64, 0, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
if (messageCount > 99) {
|
||||||
|
ctx.font = '65px Helvetica';
|
||||||
|
ctx.fillText('99+', 64, 85);
|
||||||
|
} else if (messageCount < 10) {
|
||||||
|
ctx.font = '90px Helvetica';
|
||||||
|
ctx.fillText(String(Math.min(99, messageCount)), 64, 96);
|
||||||
|
} else {
|
||||||
|
ctx.font = '85px Helvetica';
|
||||||
|
ctx.fillText(String(Math.min(99, messageCount)), 64, 90);
|
||||||
|
}
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
const serverManagerView = new ServerManagerView();
|
const serverManagerView = new ServerManagerView();
|
||||||
serverManagerView.init();
|
serverManagerView.init();
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('online', () => {
|
window.addEventListener('online', () => {
|
||||||
ipcRenderer.send('reload-main');
|
serverManagerView.reloadView();
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class NetworkTroubleshootingView {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.$reconnectButton.addEventListener('click', () => {
|
this.$reconnectButton.addEventListener('click', () => {
|
||||||
ipcRenderer.send('reload-main');
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class GeneralSection extends BaseComponent {
|
|||||||
<div class="setting-control"></div>
|
<div class="setting-control"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="title">App updates</div>
|
<div class="title">App Updates</div>
|
||||||
<div id="betaupdate-option-settings" class="settings-card">
|
<div id="betaupdate-option-settings" class="settings-card">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-description">Get Beta updates</div>
|
<div class="setting-description">Get beta updates</div>
|
||||||
<div class="setting-control"></div>
|
<div class="setting-control"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,6 +35,13 @@ class GeneralSection extends BaseComponent {
|
|||||||
<div class="setting-control"></div>
|
<div class="setting-control"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="title">User Interface</div>
|
||||||
|
<div id="ui-option-settings" class="settings-card">
|
||||||
|
<div class="setting-row" id="sidebar-option">
|
||||||
|
<div class="setting-description">Show sidebar (<span class="code">CmdOrCtrl+S</span>)</div>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -67,11 +74,16 @@ class GeneralSection extends BaseComponent {
|
|||||||
this.settingsOptionTemplate(silentOption);
|
this.settingsOptionTemplate(silentOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sidebarToggleTemplate(toggleOption) {
|
||||||
|
this.settingsOptionTemplate(toggleOption);
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.props.$root.innerHTML = this.template();
|
this.props.$root.innerHTML = this.template();
|
||||||
this.initTrayOption();
|
this.initTrayOption();
|
||||||
this.initUpdateOption();
|
this.initUpdateOption();
|
||||||
this.initSilentOption();
|
this.initSilentOption();
|
||||||
|
this.initSidebarToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
initTrayOption() {
|
initTrayOption() {
|
||||||
@@ -114,13 +126,30 @@ class GeneralSection extends BaseComponent {
|
|||||||
this.$silentOptionSettings.appendChild($silentOption);
|
this.$silentOptionSettings.appendChild($silentOption);
|
||||||
|
|
||||||
$silentOption.addEventListener('click', () => {
|
$silentOption.addEventListener('click', () => {
|
||||||
const newValue = !ConfigUtil.getConfigItem('silent');
|
const newValue = !ConfigUtil.getConfigItem('silent', true);
|
||||||
ConfigUtil.setConfigItem('silent', newValue);
|
ConfigUtil.setConfigItem('silent', newValue);
|
||||||
this.initSilentOption();
|
this.initSilentOption();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initSidebarToggle() {
|
||||||
|
this.$sidebarOptionSettings = document.querySelector('#ui-option-settings #sidebar-option .setting-control');
|
||||||
|
this.$sidebarOptionSettings.innerHTML = '';
|
||||||
|
|
||||||
|
const sidebarOption = ConfigUtil.getConfigItem('show-sidebar', true);
|
||||||
|
const $sidebarOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(sidebarOption));
|
||||||
|
this.$sidebarOptionSettings.appendChild($sidebarOption);
|
||||||
|
|
||||||
|
$sidebarOption.addEventListener('click', () => {
|
||||||
|
const newValue = !ConfigUtil.getConfigItem('show-sidebar');
|
||||||
|
ConfigUtil.setConfigItem('show-sidebar', newValue);
|
||||||
|
ipcRenderer.send('forward-message', 'toggle-sidebar', newValue);
|
||||||
|
this.initSidebarToggle();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleServerInfoChange() {
|
handleServerInfoChange() {
|
||||||
ipcRenderer.send('reload-main');
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,24 +12,11 @@ class NewServerForm extends BaseComponent {
|
|||||||
template() {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div class="settings-card" style="border: solid 1px #4CAF50;">
|
<div class="settings-card" style="border: solid 1px #4CAF50;">
|
||||||
<div class="server-info-left">
|
|
||||||
<img class="server-info-icon" src="${__dirname + '../../../../img/icon.png'}"/>
|
|
||||||
</div>
|
|
||||||
<div class="server-info-right">
|
<div class="server-info-right">
|
||||||
<div class="server-info-row">
|
<div class="server-info-row">
|
||||||
<span class="server-info-key">Name</span>
|
<input class="server-info-url" autofocus placeholder="Enter the url of your Zulip server..."/>
|
||||||
<input class="server-info-value" placeholder="(Required)"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="server-info-row">
|
<div class="server-info-row">
|
||||||
<span class="server-info-key">Url</span>
|
|
||||||
<input class="server-info-value" placeholder="(Required)"/>
|
|
||||||
</div>
|
|
||||||
<div class="server-info-row">
|
|
||||||
<span class="server-info-key">Icon</span>
|
|
||||||
<input class="server-info-value" placeholder="(Optional)"/>
|
|
||||||
</div>
|
|
||||||
<div class="server-info-row">
|
|
||||||
<span class="server-info-key"></span>
|
|
||||||
<div class="action green server-save-action">
|
<div class="action green server-save-action">
|
||||||
<i class="material-icons">check_box</i>
|
<i class="material-icons">check_box</i>
|
||||||
<span>Save</span>
|
<span>Save</span>
|
||||||
@@ -51,20 +38,13 @@ class NewServerForm extends BaseComponent {
|
|||||||
this.props.$root.innerHTML = '';
|
this.props.$root.innerHTML = '';
|
||||||
this.props.$root.appendChild(this.$newServerForm);
|
this.props.$root.appendChild(this.$newServerForm);
|
||||||
|
|
||||||
this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0];
|
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-url')[0];
|
||||||
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1];
|
|
||||||
this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initActions() {
|
initActions() {
|
||||||
this.$saveServerButton.addEventListener('click', () => {
|
this.$saveServerButton.addEventListener('click', () => {
|
||||||
DomainUtil.checkDomain(this.$newServerUrl.value).then(domain => {
|
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
|
||||||
const server = {
|
DomainUtil.addDomain(serverConf).then(() => {
|
||||||
alias: this.$newServerAlias.value,
|
|
||||||
url: domain,
|
|
||||||
icon: this.$newServerIcon.value
|
|
||||||
};
|
|
||||||
DomainUtil.addDomain(server).then(() => {
|
|
||||||
this.props.onChange(this.props.index);
|
this.props.onChange(this.props.index);
|
||||||
});
|
});
|
||||||
}, errorMessage => {
|
}, errorMessage => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const {dialog} = require('electron').remote;
|
const {dialog} = require('electron').remote;
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
const BaseComponent = require(__dirname + '/../../components/base.js');
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||||
@@ -18,19 +19,13 @@ class ServerInfoForm extends BaseComponent {
|
|||||||
</div>
|
</div>
|
||||||
<div class="server-info-right">
|
<div class="server-info-right">
|
||||||
<div class="server-info-row">
|
<div class="server-info-row">
|
||||||
<span class="server-info-key">Name</span>
|
<span class="server-info-alias">${this.props.server.alias}</span>
|
||||||
<input class="server-info-value" disabled value="${this.props.server.alias}"/>
|
<i class="material-icons open-tab-button">open_in_new</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="server-info-row">
|
<div class="server-info-row">
|
||||||
<span class="server-info-key">Url</span>
|
<input class="server-info-url" disabled value="${this.props.server.url}"/>
|
||||||
<input class="server-info-value" disabled value="${this.props.server.url}"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="server-info-row">
|
<div class="server-info-row">
|
||||||
<span class="server-info-key">Icon</span>
|
|
||||||
<input class="server-info-value" disabled value="${this.props.server.icon}"/>
|
|
||||||
</div>
|
|
||||||
<div class="server-info-row">
|
|
||||||
<span class="server-info-key"></span>
|
|
||||||
<div class="action red server-delete-action">
|
<div class="action red server-delete-action">
|
||||||
<i class="material-icons">indeterminate_check_box</i>
|
<i class="material-icons">indeterminate_check_box</i>
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
@@ -48,7 +43,9 @@ class ServerInfoForm extends BaseComponent {
|
|||||||
|
|
||||||
initForm() {
|
initForm() {
|
||||||
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
|
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
|
||||||
|
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0];
|
||||||
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
|
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
|
||||||
|
this.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0];
|
||||||
this.props.$root.appendChild(this.$serverInfoForm);
|
this.props.$root.appendChild(this.$serverInfoForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +63,14 @@ class ServerInfoForm extends BaseComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$openServerButton.addEventListener('click', () => {
|
||||||
|
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$serverInfoAlias.addEventListener('click', () => {
|
||||||
|
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class ServersSection extends BaseComponent {
|
|||||||
<div class="actions-container">
|
<div class="actions-container">
|
||||||
<div class="action green" id="new-server-action">
|
<div class="action green" id="new-server-action">
|
||||||
<i class="material-icons">add_box</i>
|
<i class="material-icons">add_box</i>
|
||||||
<span>New Server</span>
|
<span>Add Server</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="new-server-container" class="hidden"></div>
|
<div id="new-server-container" class="hidden"></div>
|
||||||
@@ -50,7 +50,7 @@ class ServersSection extends BaseComponent {
|
|||||||
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing servers';
|
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing servers';
|
||||||
this.initNewServerForm();
|
this.initNewServerForm();
|
||||||
|
|
||||||
for (const i in servers) {
|
for (let i = 0; i < servers.length; i++) {
|
||||||
new ServerInfoForm({
|
new ServerInfoForm({
|
||||||
$root: this.$serverInfoContainer,
|
$root: this.$serverInfoContainer,
|
||||||
server: servers[i],
|
server: servers[i],
|
||||||
@@ -68,15 +68,13 @@ class ServersSection extends BaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initActions() {
|
initActions() {
|
||||||
this.$newServerButton.addEventListener('click', () => {
|
this.$newServerContainer.classList.remove('hidden');
|
||||||
this.$newServerContainer.classList.remove('hidden');
|
this.$newServerButton.classList.remove('green');
|
||||||
this.$newServerButton.classList.remove('green');
|
this.$newServerButton.classList.add('grey');
|
||||||
this.$newServerButton.classList.add('grey');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleServerInfoChange() {
|
handleServerInfoChange() {
|
||||||
ipcRenderer.send('reload-main');
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// redirect users to network troubleshooting page
|
// redirect users to network troubleshooting page
|
||||||
document.querySelector('.restart_get_events_button').addEventListener('click', () => {
|
document.querySelector('.restart_get_events_button').addEventListener('click', () => {
|
||||||
ipcRenderer.send('reload-main');
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -180,18 +180,20 @@ ipcRenderer.on('tray', (event, arg) => {
|
|||||||
if (!window.tray) {
|
if (!window.tray) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// We don't want to create tray from unread messages on windows and macOS since these systems already have dock badges and taskbar overlay icon.
|
||||||
if (arg === 0) {
|
if (process.platform === 'linux') {
|
||||||
unread = arg;
|
if (arg === 0) {
|
||||||
// Message Count // console.log("message count is zero.");
|
unread = arg;
|
||||||
window.tray.setImage(iconPath());
|
// Message Count // console.log("message count is zero.");
|
||||||
window.tray.setToolTip('No unread messages');
|
window.tray.setImage(iconPath());
|
||||||
} else {
|
window.tray.setToolTip('No unread messages');
|
||||||
unread = arg;
|
} else {
|
||||||
renderNativeImage(arg).then(image => {
|
unread = arg;
|
||||||
window.tray.setImage(image);
|
renderNativeImage(arg).then(image => {
|
||||||
window.tray.setToolTip(arg + ' unread messages');
|
window.tray.setImage(image);
|
||||||
});
|
window.tray.setToolTip(arg + ' unread messages');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -204,10 +206,12 @@ function toggleTray() {
|
|||||||
ConfigUtil.setConfigItem('trayIcon', false);
|
ConfigUtil.setConfigItem('trayIcon', false);
|
||||||
} else {
|
} else {
|
||||||
createTray();
|
createTray();
|
||||||
renderNativeImage(unread).then(image => {
|
if (process.platform === 'linux') {
|
||||||
window.tray.setImage(image);
|
renderNativeImage(unread).then(image => {
|
||||||
window.tray.setToolTip(unread + ' unread messages');
|
window.tray.setImage(image);
|
||||||
});
|
window.tray.setToolTip(unread + ' unread messages');
|
||||||
|
});
|
||||||
|
}
|
||||||
ConfigUtil.setConfigItem('trayIcon', true);
|
ConfigUtil.setConfigItem('trayIcon', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const {app} = require('electron').remote;
|
const {app, dialog} = require('electron').remote;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const JsonDB = require('node-json-db');
|
const JsonDB = require('node-json-db');
|
||||||
@@ -8,7 +8,7 @@ const request = require('request');
|
|||||||
|
|
||||||
let instance = null;
|
let instance = null;
|
||||||
|
|
||||||
const defaultIconUrl = __dirname + '../../../img/icon.png';
|
const defaultIconUrl = '../renderer/img/icon.png';
|
||||||
|
|
||||||
class DomainUtil {
|
class DomainUtil {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -18,7 +18,7 @@ class DomainUtil {
|
|||||||
instance = this;
|
instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
this.reloadDB();
|
||||||
// Migrate from old schema
|
// Migrate from old schema
|
||||||
if (this.db.getData('/').domain) {
|
if (this.db.getData('/').domain) {
|
||||||
this.addDomain({
|
this.addDomain({
|
||||||
@@ -32,6 +32,7 @@ class DomainUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getDomains() {
|
getDomains() {
|
||||||
|
this.reloadDB();
|
||||||
if (this.db.getData('/').domains === undefined) {
|
if (this.db.getData('/').domains === undefined) {
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
@@ -40,6 +41,7 @@ class DomainUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getDomain(index) {
|
getDomain(index) {
|
||||||
|
this.reloadDB();
|
||||||
return this.db.getData(`/domains[${index}]`);
|
return this.db.getData(`/domains[${index}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,11 +51,13 @@ class DomainUtil {
|
|||||||
this.saveServerIcon(server.icon).then(localIconUrl => {
|
this.saveServerIcon(server.icon).then(localIconUrl => {
|
||||||
server.icon = localIconUrl;
|
server.icon = localIconUrl;
|
||||||
this.db.push('/domains[]', server, true);
|
this.db.push('/domains[]', server, true);
|
||||||
|
this.reloadDB();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
server.icon = defaultIconUrl;
|
server.icon = defaultIconUrl;
|
||||||
this.db.push('/domains[]', server, true);
|
this.db.push('/domains[]', server, true);
|
||||||
|
this.reloadDB();
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -61,10 +65,12 @@ class DomainUtil {
|
|||||||
|
|
||||||
removeDomains() {
|
removeDomains() {
|
||||||
this.db.delete('/domains');
|
this.db.delete('/domains');
|
||||||
|
this.reloadDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeDomain(index) {
|
removeDomain(index) {
|
||||||
this.db.delete(`/domains[${index}]`);
|
this.db.delete(`/domains[${index}]`);
|
||||||
|
this.reloadDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDomain(domain) {
|
checkDomain(domain) {
|
||||||
@@ -75,16 +81,42 @@ class DomainUtil {
|
|||||||
|
|
||||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
const checkDomain = domain + '/static/audio/zulip.ogg';
|
||||||
|
|
||||||
|
const serverConf = {
|
||||||
|
icon: defaultIconUrl,
|
||||||
|
url: domain,
|
||||||
|
alias: domain
|
||||||
|
};
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request(checkDomain, (error, response) => {
|
request(checkDomain, (error, response) => {
|
||||||
|
const certsError =
|
||||||
|
['Error: self signed certificate',
|
||||||
|
'Error: unable to verify the first certificate'
|
||||||
|
];
|
||||||
if (!error && response.statusCode !== 404) {
|
if (!error && response.statusCode !== 404) {
|
||||||
resolve(domain);
|
// Correct
|
||||||
} else if (error.toString().indexOf('Error: self signed certificate') >= 0 || 'Error: unable to verify the first certificate') {
|
this.getServerSettings(domain).then(serverSettings => {
|
||||||
if (window.confirm(`Do you trust certificate from ${domain}? \n ${error}`)) {
|
resolve(serverSettings);
|
||||||
resolve(domain);
|
}, () => {
|
||||||
} else {
|
resolve(serverConf);
|
||||||
reject('Untrusted Certificate.');
|
});
|
||||||
}
|
} else if (certsError.indexOf(error.toString()) >= 0) {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'question',
|
||||||
|
buttons: ['Yes', 'No'],
|
||||||
|
defaultId: 0,
|
||||||
|
message: `Do you trust certificate from ${domain}? \n ${error}`
|
||||||
|
}, response => {
|
||||||
|
if (response === 0) {
|
||||||
|
this.getServerSettings(domain).then(serverSettings => {
|
||||||
|
resolve(serverSettings);
|
||||||
|
}, () => {
|
||||||
|
resolve(serverConf);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject('Untrusted Certificate.');
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
reject('Not a valid Zulip server');
|
reject('Not a valid Zulip server');
|
||||||
}
|
}
|
||||||
@@ -92,6 +124,26 @@ class DomainUtil {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getServerSettings(domain) {
|
||||||
|
const serverSettingsUrl = domain + '/api/v1/server_settings';
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request(serverSettingsUrl, (error, response) => {
|
||||||
|
if (!error && response.statusCode === 200) {
|
||||||
|
const data = JSON.parse(response.body);
|
||||||
|
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
|
||||||
|
resolve({
|
||||||
|
icon: data.realm_uri + data.realm_icon,
|
||||||
|
url: data.realm_uri,
|
||||||
|
alias: data.realm_name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject('Zulip server version < 1.6.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
saveServerIcon(url) {
|
saveServerIcon(url) {
|
||||||
// The save will always succeed. If url is invalid, downgrade to default icon.
|
// The save will always succeed. If url is invalid, downgrade to default icon.
|
||||||
const dir = `${app.getPath('userData')}/server-icons`;
|
const dir = `${app.getPath('userData')}/server-icons`;
|
||||||
@@ -101,7 +153,7 @@ class DomainUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const filePath = `${dir}/${new Date().getMilliseconds()}${path.extname(url)}`;
|
const filePath = `${dir}/${new Date().getMilliseconds()}${path.extname(url).split('?')[0]}`;
|
||||||
const file = fs.createWriteStream(filePath);
|
const file = fs.createWriteStream(filePath);
|
||||||
try {
|
try {
|
||||||
request(url).on('response', response => {
|
request(url).on('response', response => {
|
||||||
@@ -122,6 +174,10 @@ class DomainUtil {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reloadDB() {
|
||||||
|
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new DomainUtil();
|
module.exports = new DomainUtil();
|
||||||
|
|||||||
@@ -19,12 +19,7 @@ class LinkUtil {
|
|||||||
const currentDomain = wurl('hostname', currentUrl);
|
const currentDomain = wurl('hostname', currentUrl);
|
||||||
const newDomain = wurl('hostname', newUrl);
|
const newDomain = wurl('hostname', newUrl);
|
||||||
|
|
||||||
const skipImages = '.jpg|.gif|.png|.jpeg|.JPG|.PNG';
|
return (currentDomain === newDomain) && newUrl.includes('/#narrow');
|
||||||
const skipPages = ['integrations', 'api'];
|
|
||||||
|
|
||||||
const getskipPagesUrl = newUrl.substring(8, newUrl.length);
|
|
||||||
|
|
||||||
return (currentDomain === newDomain) && !newUrl.match(skipImages) && !skipPages.includes(getskipPagesUrl.split('/')[1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,43 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="responsive desktop">
|
<html lang="en" class="responsive desktop">
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<head>
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<title>Zulip</title>
|
<meta name="viewport" content="width=device-width">
|
||||||
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
|
<title>Zulip</title>
|
||||||
</head>
|
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
|
||||||
<body>
|
</head>
|
||||||
<div id="content">
|
|
||||||
<div id="sidebar">
|
<body>
|
||||||
<div id="view-controls-container">
|
<div id="content">
|
||||||
<div id="tabs-container"></div>
|
<div class="popup">
|
||||||
<div id ="add-tab" class="tab">
|
<span class="popuptext hidden" id="fullscreen-popup"></span>
|
||||||
<div class="server-tab functional-tab" id="add-action">
|
</div>
|
||||||
<i class="material-icons">add</i>
|
<div id="sidebar">
|
||||||
</div>
|
<div id="view-controls-container">
|
||||||
</div>
|
<div id="tabs-container"></div>
|
||||||
</div>
|
<div id="add-tab" class="tab functional-tab">
|
||||||
<div id="actions-container">
|
<div class="server-tab" id="add-action">
|
||||||
<div class="action-button" id="reload-action">
|
<i class="material-icons">add</i>
|
||||||
<i class="material-icons md-48">refresh</i>
|
|
||||||
</div>
|
|
||||||
<div class="action-button" id="settings-action">
|
|
||||||
<i class="material-icons md-48">settings</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
<div id="actions-container">
|
||||||
<script src="js/main.js"></script>
|
<div class="action-button" id="reload-action">
|
||||||
</html>
|
<i class="material-icons md-48">refresh</i>
|
||||||
|
<span id="reload-tooltip" style="display:none">Reload</span>
|
||||||
|
</div>
|
||||||
|
<div class="action-button" id="settings-action">
|
||||||
|
<i class="material-icons md-48">settings</i>
|
||||||
|
<span id="setting-tooltip" style="display:none">Settings</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="main-container">
|
||||||
|
<div id="webviews-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -1,56 +1,84 @@
|
|||||||
# Development guide
|
# Development setup
|
||||||
|
|
||||||
|
This is a guide to running the Zulip desktop app from a source tree,
|
||||||
|
in order to contribute to developing it.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
|
To build and run the app from source, you'll need the following:
|
||||||
|
|
||||||
* [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
* [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||||
* [Node.js](https://nodejs.org) >= v6.9.0
|
* [Node.js](https://nodejs.org) >= v6.9.0
|
||||||
* [python](https://www.python.org/downloads/release/python-2713/) (v2.7.x recommended)
|
* [NPM](https://www.npmjs.com/get-npm) and
|
||||||
* [node-gyp](https://github.com/nodejs/node-gyp#installation)
|
[node-gyp](https://github.com/nodejs/node-gyp#installation),
|
||||||
|
if they don't come bundled with your Node.js installation
|
||||||
|
* [Python](https://www.python.org/downloads/release/python-2713/)
|
||||||
|
(v2.7.x recommended)
|
||||||
|
* A C++ compiler compatible with C++11
|
||||||
|
* Development headers for the libXext, libXtst, and libxkbfile libraries
|
||||||
|
|
||||||
|
### Debian/Ubuntu and friends
|
||||||
|
|
||||||
## System specific dependencies
|
On a system running Debian, Ubuntu, or another Debian-based Linux
|
||||||
|
distribution, you can install all dependencies through the package
|
||||||
|
manager (see [here][nodesource-install] for more on the first command):
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
Install following packages:
|
|
||||||
```sh
|
```sh
|
||||||
$ sudo apt-get install build-essential libxext-dev libxtst-dev libxkbfile-dev
|
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
|
||||||
|
$ sudo apt install git nodejs python build-essential libxext-dev libxtst-dev libxkbfile-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation
|
[nodesource-install]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
|
||||||
|
|
||||||
|
### Other OSes
|
||||||
|
|
||||||
|
Other developers run the app on Windows, macOS, and possibly other OSes.
|
||||||
|
PRs to add specific instructions to this doc are welcome!
|
||||||
|
|
||||||
|
On Windows, your C++ compiler should be Visual Studio 2015 or later.
|
||||||
|
|
||||||
|
## Download, build, and run
|
||||||
|
|
||||||
Clone the source locally:
|
Clone the source locally:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ git clone https://github.com/zulip/zulip-electron
|
$ git clone https://github.com/zulip/zulip-electron
|
||||||
$ cd zulip-electron
|
$ cd zulip-electron
|
||||||
```
|
```
|
||||||
|
|
||||||
Install project dependencies:
|
Install project dependencies:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ npm install
|
$ npm install
|
||||||
```
|
```
|
||||||
Start the app:
|
|
||||||
|
|
||||||
|
Start the app:
|
||||||
```sh
|
```sh
|
||||||
$ npm start
|
$ npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Start and watch changes
|
Start and watch changes:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ npm run dev
|
$ npm run dev
|
||||||
```
|
```
|
||||||
### Making a release
|
|
||||||
|
|
||||||
To package app into an installer use command:
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you have any problems running the app, see the [most common
|
||||||
|
issues](./troubleshooting.md).
|
||||||
|
|
||||||
|
## Making a release
|
||||||
|
|
||||||
|
To package the app into an installer:
|
||||||
```
|
```
|
||||||
npm run dist
|
npm run dist
|
||||||
```
|
```
|
||||||
It will start the packaging process for the operating system you are running this command on. The ready for distribution file (e.g. dmg, windows installer, deb package) will be outputted to the `dist` directory.
|
|
||||||
|
|
||||||
You can create a Windows installer only when running on Windows and similarly for Linux and OSX. So, to generate all three installers, you will need all three operating systems.
|
This command will produce distributable packages or installers for the
|
||||||
|
operating system you're running on:
|
||||||
|
* on Windows, a Windows installer file
|
||||||
|
* on macOS, a `.dmg` file
|
||||||
|
* on Linux, a plain `.zip` file as well as a `.deb` file and an
|
||||||
|
`AppImage` file.
|
||||||
|
To generate all three types, you will need all three operating
|
||||||
|
systems.
|
||||||
|
|
||||||
# Troubleshooting
|
The output files appear in the `dist/` directory.
|
||||||
If you have any problems running the app please see the [most common issues](./troubleshooting.md).
|
|
||||||
|
|||||||
2
docs/Home.md
Normal file
2
docs/Home.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Installation instructions
|
||||||
|
* [[Windows]]
|
||||||
1
docs/Windows.md
Normal file
1
docs/Windows.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
** Windows Set up instructions **
|
||||||
3
docs/_Footer.md
Normal file
3
docs/_Footer.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
### Want to contribute to this Wiki?
|
||||||
|
|
||||||
|
[Edit `/docs` files and send a pull request.](https://github.com/zulip/zulip-electron/tree/master/docs)
|
||||||
39
package.json
39
package.json
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "zulip",
|
"name": "zulip",
|
||||||
"productName": "Zulip",
|
"productName": "Zulip",
|
||||||
"version": "1.2.0-beta",
|
"version": "1.3.0-beta",
|
||||||
"main": "./app/main",
|
"main": "./app/main",
|
||||||
"description": "Zulip Desktop App",
|
"description": "Zulip Desktop App",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"email": "<svnitakash@gmail.com>",
|
"email": "<svnitakash@gmail.com>",
|
||||||
"copyright": "©2017 Kandra Labs, Inc.",
|
"copyright": "©2017 Kandra Labs, Inc.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Akash Nimare",
|
"name": "Kandra Labs, Inc.",
|
||||||
"email": "svnitakash@gmail.com"
|
"email": "svnitakash@gmail.com"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -20,13 +20,13 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron app --disable-http-cache",
|
"start": "electron app --disable-http-cache",
|
||||||
"postinstall": "install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"test": "xo",
|
"test": "xo",
|
||||||
"dev": "gulp dev",
|
"dev": "gulp dev",
|
||||||
"pack": "build --dir",
|
"pack": "electron-builder --dir",
|
||||||
"dist": "build",
|
"dist": "electron-builder",
|
||||||
"mas": "build --mac mas",
|
"mas": "electron-builder --mac mas",
|
||||||
"build:win": "build --win nsis-web --ia32 --x64",
|
"build:win": "electron-builder --win nsis-web --ia32 --x64",
|
||||||
"travis": "cd ./scripts && ./travis-build-test.sh"
|
"travis": "cd ./scripts && ./travis-build-test.sh"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
@@ -75,11 +75,13 @@
|
|||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": "nsis",
|
"target": "nsis",
|
||||||
"icon": "build/icon.ico"
|
"icon": "build/icon.ico",
|
||||||
|
"publisherName": "Kandra Labs, Inc."
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"perMachine": true,
|
"perMachine": true,
|
||||||
"oneClick": false
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -93,15 +95,15 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"assert": "1.4.1",
|
"assert": "1.4.1",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron-builder": "17.10.0",
|
"electron-builder": "19.19.1",
|
||||||
"electron": "1.6.8",
|
"electron": "1.6.11",
|
||||||
"electron-connect": "0.4.8",
|
"electron-connect": "0.6.2",
|
||||||
"gulp": "3.9.1",
|
"gulp": "3.9.1",
|
||||||
"gulp-mocha": "3.0.1",
|
"gulp-mocha": "4.3.1",
|
||||||
"chai-as-promised": "6.0.0",
|
"chai-as-promised": "7.1.1",
|
||||||
"chai": "^3.5.0",
|
"chai": "4.1.1",
|
||||||
"spectron": "3.6.4",
|
"spectron": "3.7.2",
|
||||||
"xo": "0.18.1"
|
"xo": "0.18.2"
|
||||||
},
|
},
|
||||||
"xo": {
|
"xo": {
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
@@ -127,7 +129,8 @@
|
|||||||
"guard-for-in": 0,
|
"guard-for-in": 0,
|
||||||
"prefer-promise-reject-errors": 0,
|
"prefer-promise-reject-errors": 0,
|
||||||
"import/no-unresolved": 0,
|
"import/no-unresolved": 0,
|
||||||
"import/no-extraneous-dependencies": 0
|
"import/no-extraneous-dependencies": 0,
|
||||||
|
"no-prototype-builtins": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,3 +3,9 @@
|
|||||||
* App icon will only show in the release version. The dev version will use the Electron icon
|
* App icon will only show in the release version. The dev version will use the Electron icon
|
||||||
* If you see issue, try deleting `node_modules` and `npm install`
|
* If you see issue, try deleting `node_modules` and `npm install`
|
||||||
* Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I`
|
* Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I`
|
||||||
|
|
||||||
|
### Error : ChecksumMismatchError
|
||||||
|
- Try deleteing `node_modules` && `app/node_modules` directories. Re-install dependencies using `npm install`.
|
||||||
|
|
||||||
|
### Error : Module version mismatch. Expected 50, got 51
|
||||||
|
- Make sure you have installed [node-gyp](https://github.com/nodejs/node-gyp#installation) dependencies properly.
|
||||||
|
|||||||
@@ -103,7 +103,20 @@ cleanUp()
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
# this function is called when user hits Ctrl-C
|
||||||
|
catchControl_c () {
|
||||||
|
echo -en "\n## Ctrl-C caught; Quitting \n"
|
||||||
|
# exit shell script
|
||||||
|
exit $?;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
envSetup $*
|
envSetup $*
|
||||||
gitCheckout
|
gitCheckout
|
||||||
npmInstallStart
|
npmInstallStart
|
||||||
cleanUp
|
cleanUp
|
||||||
|
|
||||||
|
# initialise trap to call catchControl_c function and trap keyboard interrupt (control-c)
|
||||||
|
trap catchControl_c SIGINT
|
||||||
|
sleep 1000
|
||||||
Reference in New Issue
Block a user