mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-10-25 09:03:42 +00:00
Compare commits
176 Commits
v1.1.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 | ||
|
|
e3a622fc07 | ||
|
|
f39839618d | ||
|
|
3f93a3346f | ||
|
|
9ab75b9800 | ||
|
|
f600c4db0e | ||
|
|
4e5816697e | ||
|
|
dc3c446a46 | ||
|
|
4cd8efa396 | ||
|
|
541ba335ae | ||
|
|
3e226400c4 | ||
|
|
890d7caea5 | ||
|
|
45e7993d0c | ||
|
|
8210a7c472 | ||
|
|
9e6bb1b48f | ||
|
|
d3c2da7961 | ||
|
|
a5017456f2 | ||
|
|
76cd62d0c8 | ||
|
|
6b68217494 | ||
|
|
8148d83448 | ||
|
|
a55cda3b1f | ||
|
|
e381960206 | ||
|
|
091b641abb | ||
|
|
af0ec80998 | ||
|
|
5653a38d9b | ||
|
|
01ea3beb99 | ||
|
|
cd387aaf9c | ||
|
|
24b5e0412b | ||
|
|
8174f7b37e | ||
|
|
d3a5eceaf9 | ||
|
|
31f285eba9 | ||
|
|
b1365f9669 | ||
|
|
21351125fa | ||
|
|
50b9ec7220 | ||
|
|
46cf5c9a5c | ||
|
|
35a42f3873 | ||
|
|
14b9fcf8d7 | ||
|
|
47cfe86a06 | ||
|
|
57ad2d63e0 | ||
|
|
2fcc5d9649 | ||
|
|
45523f41aa | ||
|
|
15b3af7b97 | ||
|
|
81d3aa8a1b | ||
|
|
321860a232 | ||
|
|
1c290fc2cd | ||
|
|
a74b17b989 | ||
|
|
ffba6b68f8 | ||
|
|
b553b29328 | ||
|
|
d7b44b23d1 | ||
|
|
b991fac136 | ||
|
|
cda8aa3b09 | ||
|
|
1e60643ae9 | ||
|
|
d9fbcaf38e | ||
|
|
9e5a67c36b |
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,10 +1,26 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
domain.json
|
||||
dist
|
||||
config.gypi
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# npm cache directory
|
||||
.npm
|
||||
|
||||
# Compiled binary build directory
|
||||
dist/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
// osx garbage
|
||||
*.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# miscellaneous
|
||||
.idea
|
||||
config.gypi
|
||||
@@ -2,12 +2,15 @@
|
||||
const {app, dialog} = require('electron');
|
||||
const {autoUpdater} = require('electron-updater');
|
||||
|
||||
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
|
||||
|
||||
function appUpdater() {
|
||||
// Log whats happening
|
||||
const log = require('electron-log');
|
||||
log.transports.file.level = 'info';
|
||||
autoUpdater.logger = log;
|
||||
autoUpdater.allowPrerelease = false;
|
||||
// Handle auto updates for beta/pre releases
|
||||
autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false;
|
||||
|
||||
// Ask the user if update is available
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
@@ -4,18 +4,14 @@ const electron = require('electron');
|
||||
const {app} = require('electron');
|
||||
const ipc = require('electron').ipcMain;
|
||||
const electronLocalshortcut = require('electron-localshortcut');
|
||||
const Configstore = require('electron-config');
|
||||
const isDev = require('electron-is-dev');
|
||||
const windowStateKeeper = require('electron-window-state');
|
||||
const appMenu = require('./menu');
|
||||
const {appUpdater} = require('./autoupdater');
|
||||
|
||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||
require('electron-debug')();
|
||||
|
||||
const conf = new Configstore();
|
||||
|
||||
// Setting userAgent so that server-side code can identify the desktop app
|
||||
|
||||
// Prevent window being garbage collected
|
||||
let mainWindow;
|
||||
|
||||
@@ -49,12 +45,19 @@ const iconPath = () => {
|
||||
};
|
||||
|
||||
function createMainWindow() {
|
||||
// Load the previous state with fallback to defaults
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1000,
|
||||
defaultHeight: 600
|
||||
});
|
||||
const win = new electron.BrowserWindow({
|
||||
// This settings needs to be saved in config
|
||||
title: 'Zulip',
|
||||
width: conf.get('width') || 1000,
|
||||
height: conf.get('height') || 600,
|
||||
icon: iconPath(),
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
height: mainWindowState.height,
|
||||
minWidth: 600,
|
||||
minHeight: 500,
|
||||
webPreferences: {
|
||||
@@ -86,43 +89,19 @@ function createMainWindow() {
|
||||
win.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister all the shortcuts so that they don't interfare with other apps
|
||||
electronLocalshortcut.unregisterAll(mainWindow);
|
||||
});
|
||||
|
||||
win.setTitle('Zulip');
|
||||
|
||||
// Let's save browser window position
|
||||
if (conf.get('x') || conf.get('y')) {
|
||||
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('enter-full-screen', () => {
|
||||
win.webContents.send('enter-fullscreen');
|
||||
});
|
||||
|
||||
win.on('unmaximize', () => {
|
||||
conf.set('maximize', false);
|
||||
});
|
||||
|
||||
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]
|
||||
});
|
||||
win.on('leave-full-screen', () => {
|
||||
win.webContents.send('leave-fullscreen');
|
||||
});
|
||||
|
||||
// To destroy tray icon when navigate to a new URL
|
||||
@@ -132,9 +111,30 @@ 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;
|
||||
}
|
||||
|
||||
function registerLocalShortcuts(page) {
|
||||
// Somehow, reload action cannot be overwritten by the menu item
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
|
||||
page.send('reload-viewer');
|
||||
});
|
||||
|
||||
// Also adding these shortcuts because some users might want to use it instead of CMD/Left-Right
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
|
||||
page.send('back');
|
||||
});
|
||||
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
|
||||
page.send('forward');
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
|
||||
event.preventDefault();
|
||||
@@ -153,25 +153,14 @@ app.on('activate', () => {
|
||||
});
|
||||
|
||||
app.on('ready', () => {
|
||||
electron.Menu.setApplicationMenu(appMenu);
|
||||
appMenu.setMenu({
|
||||
tabs: []
|
||||
});
|
||||
mainWindow = createMainWindow();
|
||||
|
||||
const page = mainWindow.webContents;
|
||||
|
||||
// TODO - use global shortcut instead
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
|
||||
// page.send('reload');
|
||||
mainWindow.reload();
|
||||
// page.send('destroytray');
|
||||
});
|
||||
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
|
||||
page.send('back');
|
||||
});
|
||||
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
|
||||
page.send('forward');
|
||||
});
|
||||
registerLocalShortcuts(page);
|
||||
|
||||
page.on('dom-ready', () => {
|
||||
mainWindow.show();
|
||||
@@ -184,9 +173,9 @@ app.on('ready', () => {
|
||||
appUpdater();
|
||||
}
|
||||
});
|
||||
|
||||
electron.powerMonitor.on('resume', () => {
|
||||
mainWindow.reload();
|
||||
mainWindow.webContents.send('destroytray');
|
||||
page.send('reload-viewer');
|
||||
});
|
||||
|
||||
ipc.on('focus-app', () => {
|
||||
@@ -197,8 +186,10 @@ app.on('ready', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
ipc.on('reload-main', () => {
|
||||
page.reload();
|
||||
// Reload full app not just webview, useful in debugging
|
||||
ipc.on('reload-full-app', () => {
|
||||
mainWindow.reload();
|
||||
page.send('destroytray');
|
||||
});
|
||||
|
||||
ipc.on('toggle-app', () => {
|
||||
@@ -213,8 +204,46 @@ app.on('ready', () => {
|
||||
if (process.platform === 'darwin') {
|
||||
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);
|
||||
});
|
||||
|
||||
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) => {
|
||||
page.send(listener, ...params);
|
||||
});
|
||||
|
||||
ipc.on('update-menu', (event, props) => {
|
||||
appMenu.setMenu(props);
|
||||
});
|
||||
|
||||
ipc.on('register-server-tab-shortcut', (event, index) => {
|
||||
electronLocalshortcut.register(mainWindow, `CommandOrControl+${index}`, () => {
|
||||
// Array index == Shown index - 1
|
||||
page.send('switch-server-tab', index - 1);
|
||||
});
|
||||
});
|
||||
|
||||
ipc.on('local-shortcuts', (event, enable) => {
|
||||
if (enable) {
|
||||
registerLocalShortcuts(page);
|
||||
} else {
|
||||
electronLocalshortcut.unregisterAll(mainWindow);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
|
||||
544
app/main/menu.js
544
app/main/menu.js
@@ -1,377 +1,381 @@
|
||||
'use strict';
|
||||
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();
|
||||
|
||||
function sendAction(action) {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
win.restore();
|
||||
class AppMenu {
|
||||
getHistorySubmenu() {
|
||||
return [{
|
||||
label: 'Back',
|
||||
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);
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
const ses = win.webContents.session;
|
||||
ses.clearCache(() => {
|
||||
dialog.showMessageBox({type: 'info', buttons: [], message: 'Cache cleared!'});
|
||||
});
|
||||
}
|
||||
|
||||
const viewSubmenu = [
|
||||
{
|
||||
label: 'Reload',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('reload');
|
||||
getViewSubmenu() {
|
||||
return [{
|
||||
label: 'Reload',
|
||||
accelerator: 'CommandOrControl+R',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
AppMenu.sendAction('reload-viewer');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'togglefullscreen'
|
||||
},
|
||||
{
|
||||
label: 'Zoom In',
|
||||
accelerator: 'CommandOrControl+=',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('zoomIn');
|
||||
}, {
|
||||
label: 'Hard Reload',
|
||||
accelerator: 'CommandOrControl+Shift+R',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
AppMenu.sendAction('hard-reload');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Zoom Out',
|
||||
accelerator: 'CommandOrControl+-',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('zoomOut');
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
role: 'togglefullscreen'
|
||||
}, {
|
||||
label: 'Zoom In',
|
||||
accelerator: 'CommandOrControl+=',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
AppMenu.sendAction('zoomIn');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Actual Size',
|
||||
accelerator: 'CommandOrControl+0',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('zoomActualSize');
|
||||
}, {
|
||||
label: 'Zoom Out',
|
||||
accelerator: 'CommandOrControl+-',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
AppMenu.sendAction('zoomOut');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Toggle Tray Icon',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.send('toggletray');
|
||||
}, {
|
||||
label: 'Actual Size',
|
||||
accelerator: 'CommandOrControl+0',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
AppMenu.sendAction('zoomActualSize');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Toggle DevTools for Zulip App',
|
||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'Toggle Tray Icon',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.send('toggletray');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Toggle DevTools for Active Tab',
|
||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('tab-devtools');
|
||||
}, {
|
||||
label: 'Toggle Sidebar',
|
||||
accelerator: 'CommandOrControl+S',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
const newValue = !ConfigUtil.getConfigItem('show-sidebar');
|
||||
focusedWindow.webContents.send('toggle-sidebar', newValue);
|
||||
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 = [
|
||||
{
|
||||
label: `${appName} Website`,
|
||||
click() {
|
||||
shell.openExternal('https://zulip.org');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
label: 'Report an Issue...',
|
||||
click() {
|
||||
const body = `
|
||||
<!-- Please succinctly describe your issue and steps to reproduce it. -->
|
||||
-
|
||||
${app.getName()} ${app.getVersion()}
|
||||
Electron ${process.versions.electron}
|
||||
${process.platform} ${process.arch} ${os.release()}`;
|
||||
|
||||
shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`);
|
||||
}
|
||||
getHelpSubmenu() {
|
||||
return [{
|
||||
label: `${appName} Website`,
|
||||
click() {
|
||||
shell.openExternal('https://zulipchat.com/help/');
|
||||
}
|
||||
}, {
|
||||
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
|
||||
enabled: false
|
||||
}, {
|
||||
label: 'Report an Issue...',
|
||||
click() {
|
||||
const body = `
|
||||
<!-- Please succinctly describe your issue and steps to reproduce it. -->
|
||||
-
|
||||
${app.getName()} ${app.getVersion()}
|
||||
Electron ${process.versions.electron}
|
||||
${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'
|
||||
}];
|
||||
|
||||
{
|
||||
label: `${app.getName()}`,
|
||||
submenu: [
|
||||
{
|
||||
label: 'Zulip desktop',
|
||||
if (tabs.length > 0) {
|
||||
const ShortcutKey = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
|
||||
initialSubmenu.push({
|
||||
type: 'separator'
|
||||
});
|
||||
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) {
|
||||
if (focusedWindow) {
|
||||
sendAction('open-about');
|
||||
AppMenu.sendAction('open-about');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Manage Zulip Servers',
|
||||
}, {
|
||||
label: 'Settings',
|
||||
accelerator: 'Cmd+,',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('open-settings');
|
||||
AppMenu.sendAction('open-settings');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Keyboard shortcuts',
|
||||
}, {
|
||||
label: 'Keyboard Shortcuts',
|
||||
accelerator: 'Cmd+K',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('shortcut');
|
||||
AppMenu.sendAction('shortcut');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
label: 'Clear Cache',
|
||||
click() {
|
||||
clearCache();
|
||||
AppMenu.clearCache();
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
label: 'Log Out',
|
||||
accelerator: 'Cmd+L',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('log-out');
|
||||
AppMenu.sendAction('log-out');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'services',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'hide'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'hideothers'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'unhide'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'quit'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{
|
||||
}]
|
||||
}, {
|
||||
label: 'Edit',
|
||||
submenu: [{
|
||||
role: 'undo'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'redo'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'pasteandmatchstyle'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'delete'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'selectall'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: viewSubmenu
|
||||
},
|
||||
{
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{
|
||||
role: 'minimize'
|
||||
},
|
||||
{
|
||||
role: 'close'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'front'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: helpSubmenu
|
||||
}]
|
||||
}, {
|
||||
label: 'View',
|
||||
submenu: this.getViewSubmenu()
|
||||
}, {
|
||||
label: 'History',
|
||||
submenu: this.getHistorySubmenu()
|
||||
}, {
|
||||
label: 'Window',
|
||||
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
|
||||
}, {
|
||||
role: 'help',
|
||||
submenu: this.getHelpSubmenu()
|
||||
}];
|
||||
}
|
||||
];
|
||||
|
||||
const otherTpl = [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Zulip desktop',
|
||||
getOtherTpl(props) {
|
||||
const {tabs, activeTabIndex} = props;
|
||||
|
||||
return [{
|
||||
label: 'File',
|
||||
submenu: [{
|
||||
label: 'Zulip Desktop',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('open-about');
|
||||
AppMenu.sendAction('open-about');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Manage Zulip Servers',
|
||||
}, {
|
||||
label: 'Settings',
|
||||
accelerator: 'Ctrl+,',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('open-settings');
|
||||
AppMenu.sendAction('open-settings');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Keyboard shortcuts',
|
||||
}, {
|
||||
label: 'Keyboard Shortcuts',
|
||||
accelerator: 'Ctrl+K',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('shortcut');
|
||||
AppMenu.sendAction('shortcut');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
label: 'Clear Cache',
|
||||
click() {
|
||||
clearCache();
|
||||
AppMenu.clearCache();
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
label: 'Log Out',
|
||||
accelerator: 'Ctrl+L',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('log-out');
|
||||
AppMenu.sendAction('log-out');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'quit',
|
||||
accelerator: 'Ctrl+Q'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{
|
||||
}]
|
||||
}, {
|
||||
label: 'Edit',
|
||||
submenu: [{
|
||||
role: 'undo'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'redo'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'pasteandmatchstyle'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'delete'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
}, {
|
||||
role: 'selectall'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: viewSubmenu
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: helpSubmenu
|
||||
}]
|
||||
}, {
|
||||
label: 'View',
|
||||
submenu: this.getViewSubmenu()
|
||||
}, {
|
||||
label: 'History',
|
||||
submenu: this.getHistorySubmenu()
|
||||
}, {
|
||||
label: 'Window',
|
||||
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",
|
||||
"productName": "Zulip",
|
||||
"version": "1.1.0-beta",
|
||||
"version": "1.3.0-beta",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
"email": "<svnitakash@gmail.com>",
|
||||
"copyright": "©2017 Kandra Labs, Inc.",
|
||||
"author": {
|
||||
"name": "Akash Nimare",
|
||||
"name": "Kandra Labs, Inc.",
|
||||
"email": "svnitakash@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
@@ -27,16 +27,15 @@
|
||||
"InstantMessaging"
|
||||
],
|
||||
"dependencies": {
|
||||
"electron-config": "0.2.1",
|
||||
"electron-debug": "1.1.0",
|
||||
"electron-is-dev": "0.1.2",
|
||||
"electron-localshortcut": "1.0.0",
|
||||
"electron-log": "1.3.0",
|
||||
"electron-spellchecker": "1.0.8",
|
||||
"electron-updater": "1.11.2",
|
||||
"https": "^1.0.0",
|
||||
"electron-debug": "1.4.0",
|
||||
"electron-is-dev": "0.3.0",
|
||||
"electron-localshortcut": "2.0.2",
|
||||
"electron-log": "2.2.7",
|
||||
"electron-spellchecker": "1.2.0",
|
||||
"electron-updater": "2.8.5",
|
||||
"node-json-db": "0.7.3",
|
||||
"request": "2.79.0",
|
||||
"wurl": "2.1.0"
|
||||
"request": "2.81.0",
|
||||
"wurl": "2.5.0",
|
||||
"electron-window-state": "4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/*******************
|
||||
* General rules *
|
||||
*******************/
|
||||
html, body {
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
cursor: default;
|
||||
user-select:none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#content {
|
||||
@@ -24,43 +26,44 @@ html, body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
-webkit-app-region: drag;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
|
||||
/*******************
|
||||
* Left Sidebar *
|
||||
*******************/
|
||||
#tabs-container {
|
||||
|
||||
#tabs-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
/* Preferred icon size */
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
/* Preferred icon size */
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
@@ -70,6 +73,10 @@ html, body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-button i {
|
||||
color: #6c8592;
|
||||
font-size: 28px;
|
||||
@@ -79,54 +86,65 @@ html, body {
|
||||
color: #98a9b3;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
margin: 5px 0;
|
||||
cursor: pointer;
|
||||
.tab:first-child {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tab.active::before {
|
||||
content: "";
|
||||
background: #fff;
|
||||
border-radius: 0 3px 3px 0;
|
||||
width: 4px;
|
||||
position: absolute;
|
||||
height: 35px;
|
||||
left: -10px;
|
||||
top: 5px;
|
||||
.tab {
|
||||
position: relative;
|
||||
margin: 2px 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab .server-icons {
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
padding: 3px;
|
||||
height: 30px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tab .server-tab {
|
||||
background: #a4d3c4;
|
||||
background-size: 100%;
|
||||
border-radius: 4px;
|
||||
width: 35px;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
position: relative;
|
||||
margin: 5px 0;
|
||||
margin: 5px 0 2px 0;
|
||||
z-index: 11;
|
||||
line-height: 31px;
|
||||
color: #194a2b;
|
||||
color: #eee;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
opacity: 0.6;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.tab .server-tab:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tab .functional-tab {
|
||||
background: #eee;
|
||||
.tab.functional-tab {
|
||||
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;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.tab.active .server-tab {
|
||||
opacity: 1;
|
||||
background-color: #648478;
|
||||
}
|
||||
|
||||
.tab .server-tab-badge.active {
|
||||
@@ -138,14 +156,15 @@ html, body {
|
||||
font-size: 10px;
|
||||
font-family: sans-serif;
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
right: 5px;
|
||||
z-index: 15;
|
||||
top: -2px;
|
||||
top: 6px;
|
||||
float: right;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 17px;
|
||||
display: block;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.tab .server-tab-badge {
|
||||
@@ -154,7 +173,7 @@ html, body {
|
||||
|
||||
.tab .server-tab-badge.close-button {
|
||||
width: 16px;
|
||||
padding: 0 0 0 1px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tab .server-tab-badge.close-button i {
|
||||
@@ -162,23 +181,141 @@ html, body {
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
#add-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab .server-tab-shortcut {
|
||||
color: #648478;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
font-family: sans-serif;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
/*******************
|
||||
* Webview Area *
|
||||
*******************/
|
||||
|
||||
#webviews-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
webview {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
transition: opacity 0.3s ease-in;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
webview.onload {
|
||||
transition: opacity 1s cubic-bezier(0.95, 0.05, 0.795, 0.035);
|
||||
}
|
||||
|
||||
webview.disabled {
|
||||
flex: 0 1;
|
||||
height: 0;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
transition: opacity 0.3s ease-out;
|
||||
}
|
||||
|
||||
webview:focus {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,35 @@ body {
|
||||
cursor: default;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
/* Preferred icon size */
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
#content {
|
||||
@@ -22,23 +50,23 @@ body {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#tabs-container {
|
||||
#nav-container {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
.nav {
|
||||
padding: 5px 0;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
.nav.active {
|
||||
color: #464e5a;
|
||||
cursor: default;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab.active::before {
|
||||
.nav.active::before {
|
||||
background: #464e5a;
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
@@ -59,57 +87,62 @@ body {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.server-info-container {
|
||||
#new-server-container {
|
||||
margin: 20px 0;
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 4px 0 6px 0;
|
||||
font-size: 18px;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
color: #1e1e1e;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
padding: 4px 0 6px 0;
|
||||
font-weight: bold;
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
img.server-info-icon {
|
||||
background: #a4d3c4;
|
||||
background-size: 100%;
|
||||
border-radius: 4px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.server-info-left {
|
||||
margin-right: 20px;
|
||||
margin: 10px 20px 0 0;
|
||||
}
|
||||
|
||||
.server-info-right {
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.server-info-row {
|
||||
display: flex;
|
||||
line-height: 26px;
|
||||
height: 40px;
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
|
||||
.server-info-key {
|
||||
width: 40px;
|
||||
margin-right: 20px;
|
||||
text-align: right;
|
||||
.server-info-alias {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.server-info-value {
|
||||
.server-info-url {
|
||||
flex-grow: 1;
|
||||
font-size: 14px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
border-bottom: #ddd 1px solid;
|
||||
border-bottom: #ededed 1px solid;
|
||||
outline-width: 0;
|
||||
background: transparent;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.server-info-value:focus {
|
||||
border-bottom: #b0d8ce 2px solid;
|
||||
border-bottom: #388E3C 1px solid;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
@@ -124,12 +157,15 @@ img.server-info-icon {
|
||||
.action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
padding: 0 10px;
|
||||
border-radius: 2px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.action i {
|
||||
margin-right: 5px;
|
||||
font-size: 18px;
|
||||
line-height: 27px;
|
||||
}
|
||||
|
||||
.settings-pane {
|
||||
@@ -148,34 +184,51 @@ img.server-info-icon {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.server-info.active {
|
||||
background: #ecf4ef;
|
||||
}
|
||||
|
||||
.server-info {
|
||||
.settings-card {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
margin: 10px 0 10px 0;
|
||||
padding: 16px 30px;
|
||||
margin: 10px 0 20px 0;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
width: 540px;
|
||||
box-shadow: 1px 2px 4px #bcbcbc;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
margin: 5px !important;
|
||||
}
|
||||
|
||||
.save-server-button {
|
||||
display: inline-block;
|
||||
.red {
|
||||
color: #ef5350;
|
||||
background: #ffebee;
|
||||
border: 1px solid #ef5350;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: #388E3C;
|
||||
background: #E8F5E9;
|
||||
border: 1px solid #388E3C;
|
||||
}
|
||||
|
||||
.grey {
|
||||
color: #9E9E9E;
|
||||
background: #FAFAFA;
|
||||
border: 1px solid #9E9E9E;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: Courier New, Courier, monospace;
|
||||
}
|
||||
|
||||
i.open-tab-button {
|
||||
padding: 0 5px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
padding: 7px 14px;
|
||||
background-color: #52c2af;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.save-server-button:hover {
|
||||
background-color: #32a692;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
template() {
|
||||
return `<div class="tab">
|
||||
return `<div class="tab functional-tab">
|
||||
<div class="server-tab-badge close-button">
|
||||
<i class="material-icons">close</i>
|
||||
</div>
|
||||
<div class="server-tab functional-tab">
|
||||
<div class="server-tab">
|
||||
<i class="material-icons">${this.props.materialIcon}</i>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
const Tab = require(__dirname + '/../components/tab.js');
|
||||
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
class ServerTab extends Tab {
|
||||
template() {
|
||||
return `<div class="tab">
|
||||
<div class="server-tab-badge"></div>
|
||||
<div class="server-tab" style="background-image: url(${this.props.icon});"></div>
|
||||
<div class="server-tab">
|
||||
<img class="server-icons" src='${this.props.icon}'/>
|
||||
</div>
|
||||
<div class="server-tab-shortcut">${this.generateShortcutText()}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -26,6 +32,27 @@ class ServerTab extends Tab {
|
||||
this.$badge.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
generateShortcutText() {
|
||||
// Only provide shortcuts for server [0..10]
|
||||
if (this.props.index >= 10) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const shownIndex = this.props.index + 1;
|
||||
|
||||
let shortcutText = '';
|
||||
|
||||
if (SystemUtil.getOS() === 'Mac') {
|
||||
shortcutText = `⌘ ${shownIndex}`;
|
||||
} else {
|
||||
shortcutText = `Ctrl+${shownIndex}`;
|
||||
}
|
||||
|
||||
ipcRenderer.send('register-server-tab-shortcut', shownIndex);
|
||||
|
||||
return shortcutText;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ServerTab;
|
||||
|
||||
@@ -43,7 +43,4 @@ class Tab extends BaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
Tab.SERVER_TAB = 0;
|
||||
Tab.SETTINGS_TAB = 1;
|
||||
|
||||
module.exports = Tab;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
|
||||
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||
const LinkUtil = require(__dirname + '/../utils/link-util.js');
|
||||
const {app, dialog, shell} = require('electron').remote;
|
||||
const {ipcRenderer} = require('electron');
|
||||
const {shell} = require('electron').remote;
|
||||
|
||||
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||
|
||||
@@ -25,7 +27,7 @@ class WebView extends BaseComponent {
|
||||
src="${this.props.url}"
|
||||
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
|
||||
disablewebsecurity
|
||||
preload="js/preload.js"
|
||||
${this.props.preload ? 'preload="js/preload.js"' : ''}
|
||||
webpreferences="allowRunningInsecureContent, javascript=yes">
|
||||
</webview>`;
|
||||
}
|
||||
@@ -42,7 +44,7 @@ class WebView extends BaseComponent {
|
||||
const {url} = event;
|
||||
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
|
||||
|
||||
if (LinkUtil.isInternal(domainPrefix, url)) {
|
||||
if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) {
|
||||
event.preventDefault();
|
||||
this.$el.loadURL(url);
|
||||
} else {
|
||||
@@ -57,7 +59,10 @@ class WebView extends BaseComponent {
|
||||
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 => {
|
||||
const {errorDescription} = event;
|
||||
@@ -90,13 +95,23 @@ class WebView extends BaseComponent {
|
||||
}
|
||||
|
||||
this.$el.classList.remove('disabled');
|
||||
setTimeout(() => {
|
||||
this.$el.classList.remove('onload');
|
||||
}, 1000);
|
||||
this.focus();
|
||||
this.loading = false;
|
||||
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() {
|
||||
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() {
|
||||
@@ -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() {
|
||||
this.zoomFactor += 0.1;
|
||||
this.$el.setZoomFactor(this.zoomFactor);
|
||||
@@ -173,6 +169,10 @@ class WebView extends BaseComponent {
|
||||
this.hide();
|
||||
this.$el.reload();
|
||||
}
|
||||
|
||||
send(...param) {
|
||||
this.$el.send(...param);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebView;
|
||||
|
||||
@@ -7,16 +7,25 @@ const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
|
||||
const WebView = require(__dirname + '/js/components/webview.js');
|
||||
const ServerTab = require(__dirname + '/js/components/server-tab.js');
|
||||
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
|
||||
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
|
||||
|
||||
class ServerManagerView {
|
||||
constructor() {
|
||||
this.$addServerButton = document.getElementById('add-tab');
|
||||
this.$tabsContainer = document.getElementById('tabs-container');
|
||||
|
||||
const $actionsContainer = document.getElementById('actions-container');
|
||||
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
|
||||
this.$addServerButton = $actionsContainer.querySelector('#add-action');
|
||||
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
|
||||
this.$content = document.getElementById('content');
|
||||
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.tabs = [];
|
||||
@@ -24,11 +33,17 @@ class ServerManagerView {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initSidebar();
|
||||
this.initTabs();
|
||||
this.initActions();
|
||||
this.registerIpcs();
|
||||
}
|
||||
|
||||
initSidebar() {
|
||||
const showSidebar = ConfigUtil.getConfigItem('show-sidebar', true);
|
||||
this.toggleSidebar(showSidebar);
|
||||
}
|
||||
|
||||
initTabs() {
|
||||
const servers = DomainUtil.getDomains();
|
||||
if (servers.length > 0) {
|
||||
@@ -37,17 +52,21 @@ class ServerManagerView {
|
||||
}
|
||||
this.activateTab(0);
|
||||
} else {
|
||||
this.openSettings();
|
||||
this.openSettings('Servers');
|
||||
}
|
||||
|
||||
ipcRenderer.send('local-shortcuts', true);
|
||||
}
|
||||
|
||||
initServer(server, index) {
|
||||
this.tabs.push(new ServerTab({
|
||||
role: 'server',
|
||||
icon: server.icon,
|
||||
$root: this.$tabsContainer,
|
||||
onClick: this.activateTab.bind(this, index),
|
||||
index,
|
||||
webview: new WebView({
|
||||
$root: this.$content,
|
||||
$root: this.$webviewsContainer,
|
||||
index,
|
||||
url: server.url,
|
||||
name: server.alias,
|
||||
@@ -56,7 +75,8 @@ class ServerManagerView {
|
||||
},
|
||||
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
||||
onTitleChange: this.updateBadge.bind(this),
|
||||
nodeIntegration: false
|
||||
nodeIntegration: false,
|
||||
preload: true
|
||||
})
|
||||
}));
|
||||
}
|
||||
@@ -65,12 +85,28 @@ class ServerManagerView {
|
||||
this.$reloadButton.addEventListener('click', () => {
|
||||
this.tabs[this.activeTabIndex].webview.reload();
|
||||
});
|
||||
this.$addServerButton.addEventListener('click', this.openSettings.bind(this));
|
||||
this.$settingsButton.addEventListener('click', this.openSettings.bind(this));
|
||||
this.$addServerButton.addEventListener('click', () => {
|
||||
this.openSettings('Servers');
|
||||
});
|
||||
this.$settingsButton.addEventListener('click', () => {
|
||||
this.openSettings('General');
|
||||
});
|
||||
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) {
|
||||
if (this.functionalTabs[tabProps.name]) {
|
||||
if (this.functionalTabs[tabProps.name] !== undefined) {
|
||||
this.activateTab(this.functionalTabs[tabProps.name]);
|
||||
return;
|
||||
}
|
||||
@@ -78,12 +114,14 @@ class ServerManagerView {
|
||||
this.functionalTabs[tabProps.name] = this.tabs.length;
|
||||
|
||||
this.tabs.push(new FunctionalTab({
|
||||
role: 'function',
|
||||
materialIcon: tabProps.materialIcon,
|
||||
$root: this.$tabsContainer,
|
||||
index: this.functionalTabs[tabProps.name],
|
||||
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
|
||||
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
|
||||
webview: new WebView({
|
||||
$root: this.$content,
|
||||
$root: this.$webviewsContainer,
|
||||
index: this.functionalTabs[tabProps.name],
|
||||
url: tabProps.url,
|
||||
name: tabProps.name,
|
||||
@@ -92,19 +130,21 @@ class ServerManagerView {
|
||||
},
|
||||
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
||||
onTitleChange: this.updateBadge.bind(this),
|
||||
nodeIntegration: true
|
||||
nodeIntegration: true,
|
||||
preload: false
|
||||
})
|
||||
}));
|
||||
|
||||
this.activateTab(this.functionalTabs[tabProps.name]);
|
||||
}
|
||||
|
||||
openSettings() {
|
||||
openSettings(nav = 'General') {
|
||||
this.openFunctionalTab({
|
||||
name: 'Settings',
|
||||
materialIcon: 'settings',
|
||||
url: `file://${__dirname}/preference.html`
|
||||
url: `file://${__dirname}/preference.html#${nav}`
|
||||
});
|
||||
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
|
||||
}
|
||||
|
||||
openAbout() {
|
||||
@@ -124,7 +164,7 @@ class ServerManagerView {
|
||||
}
|
||||
|
||||
activateTab(index, hideOldTab = true) {
|
||||
if (this.tabs[index].loading) {
|
||||
if (this.tabs[index].webview.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,10 +178,15 @@ class ServerManagerView {
|
||||
|
||||
this.activeTabIndex = index;
|
||||
this.tabs[index].activate();
|
||||
|
||||
ipcRenderer.send('update-menu', {
|
||||
tabs: this.tabs,
|
||||
activeTabIndex: this.activeTabIndex
|
||||
});
|
||||
}
|
||||
|
||||
destroyTab(name, index) {
|
||||
if (this.tabs[index].loading) {
|
||||
if (this.tabs[index].webview.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,7 +195,29 @@ class ServerManagerView {
|
||||
delete this.tabs[index];
|
||||
delete this.functionalTabs[name];
|
||||
|
||||
this.activateTab(0, false);
|
||||
// Issue #188: If the functional tab was not focused, do not activate another tab.
|
||||
if (this.activeTabIndex === index) {
|
||||
this.activateTab(0, false);
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -166,6 +233,14 @@ class ServerManagerView {
|
||||
ipcRenderer.send('update-badge', messageCountAll);
|
||||
}
|
||||
|
||||
toggleSidebar(show) {
|
||||
if (show) {
|
||||
this.$sidebar.classList.remove('hidden');
|
||||
} else {
|
||||
this.$sidebar.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
registerIpcs() {
|
||||
const webviewListeners = {
|
||||
'webview-reload': 'reload',
|
||||
@@ -189,16 +264,71 @@ class ServerManagerView {
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.on('open-settings', this.openSettings.bind(this));
|
||||
ipcRenderer.on('open-settings', (event, settingNav) => {
|
||||
this.openSettings(settingNav);
|
||||
});
|
||||
|
||||
ipcRenderer.on('open-about', this.openAbout.bind(this));
|
||||
|
||||
ipcRenderer.on('reload-viewer', this.reloadView.bind(this));
|
||||
|
||||
ipcRenderer.on('hard-reload', () => {
|
||||
ipcRenderer.send('reload-full-app');
|
||||
});
|
||||
|
||||
ipcRenderer.on('switch-server-tab', (event, 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 = () => {
|
||||
const serverManagerView = new ServerManagerView();
|
||||
serverManagerView.init();
|
||||
};
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
ipcRenderer.send('reload-main');
|
||||
});
|
||||
window.addEventListener('online', () => {
|
||||
serverManagerView.reloadView();
|
||||
});
|
||||
};
|
||||
|
||||
22
app/renderer/js/notification.js
Normal file
22
app/renderer/js/notification.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
const {remote} = require('electron');
|
||||
|
||||
const ConfigUtil = require(__dirname + '/utils/config-util.js');
|
||||
|
||||
const app = remote.app;
|
||||
|
||||
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
|
||||
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
|
||||
app.setAppUserModelId('org.zulip.zulip-electron');
|
||||
|
||||
const NativeNotification = window.Notification;
|
||||
|
||||
class SilentNotification extends NativeNotification {
|
||||
constructor(title, opts) {
|
||||
opts.silent = ConfigUtil.getConfigItem('silent') || false;
|
||||
super(title, opts);
|
||||
}
|
||||
}
|
||||
|
||||
window.Notification = SilentNotification;
|
||||
@@ -9,7 +9,7 @@ class NetworkTroubleshootingView {
|
||||
|
||||
init() {
|
||||
this.$reconnectButton.addEventListener('click', () => {
|
||||
ipcRenderer.send('reload-main');
|
||||
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
|
||||
|
||||
class PreferenceView {
|
||||
constructor() {
|
||||
this.$newServerButton = document.getElementById('new-server-action');
|
||||
this.$saveServerButton = document.getElementById('save-server-action');
|
||||
this.$reloadServerButton = document.getElementById('reload-server-action');
|
||||
this.$serverInfoContainer = document.querySelector('.server-info-container');
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initServers();
|
||||
this.initActions();
|
||||
}
|
||||
|
||||
initServers() {
|
||||
const servers = DomainUtil.getDomains();
|
||||
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
|
||||
|
||||
this.initNewServerForm();
|
||||
|
||||
for (const i in servers) {
|
||||
this.initServer(servers[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
initServer(server, index) {
|
||||
const {
|
||||
alias,
|
||||
url,
|
||||
icon
|
||||
} = server;
|
||||
const serverInfoTemplate = `
|
||||
<div class="server-info">
|
||||
<div class="server-info-left">
|
||||
<img class="server-info-icon" src="${icon}"/>
|
||||
</div>
|
||||
<div class="server-info-right">
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Name</span>
|
||||
<input class="server-info-value" disabled value="${alias}"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Url</span>
|
||||
<input class="server-info-value" disabled value="${url}"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Icon</span>
|
||||
<input class="server-info-value" disabled value="${icon}"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Actions</span>
|
||||
<div class="action server-info-value" id="delete-server-action-${index}">
|
||||
<i class="material-icons">indeterminate_check_box</i>
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
this.$serverInfoContainer.appendChild(this.insertNode(serverInfoTemplate));
|
||||
document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => {
|
||||
DomainUtil.removeDomain(index);
|
||||
this.initServers();
|
||||
// alert('Success. Reload to apply changes.');
|
||||
ipcRenderer.send('reload-main');
|
||||
this.$reloadServerButton.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
initNewServerForm() {
|
||||
const newServerFormTemplate = `
|
||||
<div class="server-info active hidden">
|
||||
<div class="server-info-left">
|
||||
<img class="server-info-icon" src="https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png"/>
|
||||
</div>
|
||||
<div class="server-info-right">
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Name</span>
|
||||
<input id="server-info-name" class="server-info-value" placeholder="(Required)"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Url</span>
|
||||
<input id="server-info-url" spellcheck="false" class="server-info-value" placeholder="(Required)"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Icon</span>
|
||||
<input id="server-info-icon" class="server-info-value" placeholder="(Optional)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
this.$serverInfoContainer.appendChild(this.insertNode(newServerFormTemplate));
|
||||
|
||||
this.$newServerForm = document.querySelector('.server-info.active');
|
||||
this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0];
|
||||
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1];
|
||||
this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2];
|
||||
}
|
||||
|
||||
initActions() {
|
||||
this.$newServerButton.addEventListener('click', () => {
|
||||
this.$newServerForm.classList.remove('hidden');
|
||||
this.$saveServerButton.classList.remove('hidden');
|
||||
this.$newServerButton.classList.add('hidden');
|
||||
});
|
||||
this.$saveServerButton.addEventListener('click', () => {
|
||||
DomainUtil.checkDomain(this.$newServerUrl.value).then(domain => {
|
||||
const server = {
|
||||
alias: this.$newServerAlias.value,
|
||||
url: domain,
|
||||
icon: this.$newServerIcon.value
|
||||
};
|
||||
DomainUtil.addDomain(server);
|
||||
this.$saveServerButton.classList.add('hidden');
|
||||
this.$newServerButton.classList.remove('hidden');
|
||||
this.$newServerForm.classList.add('hidden');
|
||||
|
||||
this.initServers();
|
||||
// alert('Success. Reload to apply changes.');
|
||||
ipcRenderer.send('reload-main');
|
||||
this.$reloadServerButton.classList.remove('hidden');
|
||||
}, errorMessage => {
|
||||
alert(errorMessage);
|
||||
});
|
||||
});
|
||||
this.$reloadServerButton.addEventListener('click', () => {
|
||||
ipcRenderer.send('reload-main');
|
||||
});
|
||||
}
|
||||
insertNode(html) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html;
|
||||
return wrapper.firstElementChild;
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
const preferenceView = new PreferenceView();
|
||||
preferenceView.init();
|
||||
};
|
||||
156
app/renderer/js/pages/preference/general-section.js
Normal file
156
app/renderer/js/pages/preference/general-section.js
Normal file
@@ -0,0 +1,156 @@
|
||||
'use strict';
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
|
||||
|
||||
class GeneralSection extends BaseComponent {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
template() {
|
||||
return `
|
||||
<div class="settings-pane" id="server-settings-pane">
|
||||
<div class="title">Tray Options</div>
|
||||
<div id="tray-option-settings" class="settings-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-description">Show app icon in system tray</div>
|
||||
<div class="setting-control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title">App Updates</div>
|
||||
<div id="betaupdate-option-settings" class="settings-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-description">Get beta updates</div>
|
||||
<div class="setting-control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title">Desktop Notification</div>
|
||||
<div id="silent-option-settings" class="settings-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-description">Mute all sounds from Zulip (requires reload)</div>
|
||||
<div class="setting-control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div 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>
|
||||
`;
|
||||
}
|
||||
|
||||
settingsOptionTemplate(settingOption) {
|
||||
if (settingOption) {
|
||||
return `
|
||||
<div class="action green">
|
||||
<span>On</span>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
return `
|
||||
<div class="action red">
|
||||
<span>Off</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
trayOptionTemplate(trayOption) {
|
||||
this.settingsOptionTemplate(trayOption);
|
||||
}
|
||||
|
||||
updateOptionTemplate(updateOption) {
|
||||
this.settingsOptionTemplate(updateOption);
|
||||
}
|
||||
|
||||
silentOptionTemplate(silentOption) {
|
||||
this.settingsOptionTemplate(silentOption);
|
||||
}
|
||||
|
||||
sidebarToggleTemplate(toggleOption) {
|
||||
this.settingsOptionTemplate(toggleOption);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.props.$root.innerHTML = this.template();
|
||||
this.initTrayOption();
|
||||
this.initUpdateOption();
|
||||
this.initSilentOption();
|
||||
this.initSidebarToggle();
|
||||
}
|
||||
|
||||
initTrayOption() {
|
||||
this.$trayOptionSettings = document.querySelector('#tray-option-settings .setting-control');
|
||||
this.$trayOptionSettings.innerHTML = '';
|
||||
|
||||
const trayOption = ConfigUtil.getConfigItem('trayIcon', true);
|
||||
const $trayOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(trayOption));
|
||||
this.$trayOptionSettings.appendChild($trayOption);
|
||||
|
||||
$trayOption.addEventListener('click', () => {
|
||||
const newValue = !ConfigUtil.getConfigItem('trayIcon');
|
||||
ConfigUtil.setConfigItem('trayIcon', newValue);
|
||||
ipcRenderer.send('forward-message', 'toggletray');
|
||||
this.initTrayOption();
|
||||
});
|
||||
}
|
||||
|
||||
initUpdateOption() {
|
||||
this.$updateOptionSettings = document.querySelector('#betaupdate-option-settings .setting-control');
|
||||
this.$updateOptionSettings.innerHTML = '';
|
||||
|
||||
const updateOption = ConfigUtil.getConfigItem('betaUpdate', false);
|
||||
const $updateOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(updateOption));
|
||||
this.$updateOptionSettings.appendChild($updateOption);
|
||||
|
||||
$updateOption.addEventListener('click', () => {
|
||||
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
|
||||
ConfigUtil.setConfigItem('betaUpdate', newValue);
|
||||
this.initUpdateOption();
|
||||
});
|
||||
}
|
||||
|
||||
initSilentOption() {
|
||||
this.$silentOptionSettings = document.querySelector('#silent-option-settings .setting-control');
|
||||
this.$silentOptionSettings.innerHTML = '';
|
||||
|
||||
const silentOption = ConfigUtil.getConfigItem('silent', false);
|
||||
const $silentOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(silentOption));
|
||||
this.$silentOptionSettings.appendChild($silentOption);
|
||||
|
||||
$silentOption.addEventListener('click', () => {
|
||||
const newValue = !ConfigUtil.getConfigItem('silent', true);
|
||||
ConfigUtil.setConfigItem('silent', newValue);
|
||||
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() {
|
||||
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GeneralSection;
|
||||
67
app/renderer/js/pages/preference/nav.js
Normal file
67
app/renderer/js/pages/preference/nav.js
Normal file
@@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||
|
||||
class PreferenceNav extends BaseComponent {
|
||||
constructor(props) {
|
||||
super();
|
||||
|
||||
this.props = props;
|
||||
|
||||
this.navItems = ['General', 'Servers'];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
template() {
|
||||
let navItemsTemplate = '';
|
||||
for (const navItem of this.navItems) {
|
||||
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${navItem}</div>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div>
|
||||
<div id="settings-header">Settings</div>
|
||||
<div id="nav-container">${navItemsTemplate}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.$el = this.generateNodeFromTemplate(this.template());
|
||||
this.props.$root.appendChild(this.$el);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
for (const navItem of this.navItems) {
|
||||
const $item = document.getElementById(`nav-${navItem}`);
|
||||
$item.addEventListener('click', () => {
|
||||
this.props.onItemSelected(navItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
select(navItemToSelect) {
|
||||
for (const navItem of this.navItems) {
|
||||
if (navItem === navItemToSelect) {
|
||||
this.activate(navItem);
|
||||
} else {
|
||||
this.deactivate(navItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activate(navItem) {
|
||||
const $item = document.getElementById(`nav-${navItem}`);
|
||||
$item.classList.add('active');
|
||||
}
|
||||
|
||||
deactivate(navItem) {
|
||||
const $item = document.getElementById(`nav-${navItem}`);
|
||||
$item.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PreferenceNav;
|
||||
57
app/renderer/js/pages/preference/new-server-form.js
Normal file
57
app/renderer/js/pages/preference/new-server-form.js
Normal file
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||
|
||||
class NewServerForm extends BaseComponent {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
template() {
|
||||
return `
|
||||
<div class="settings-card" style="border: solid 1px #4CAF50;">
|
||||
<div class="server-info-right">
|
||||
<div class="server-info-row">
|
||||
<input class="server-info-url" autofocus placeholder="Enter the url of your Zulip server..."/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<div class="action green server-save-action">
|
||||
<i class="material-icons">check_box</i>
|
||||
<span>Save</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initForm();
|
||||
this.initActions();
|
||||
}
|
||||
|
||||
initForm() {
|
||||
this.$newServerForm = this.generateNodeFromTemplate(this.template());
|
||||
this.$saveServerButton = this.$newServerForm.getElementsByClassName('server-save-action')[0];
|
||||
this.props.$root.innerHTML = '';
|
||||
this.props.$root.appendChild(this.$newServerForm);
|
||||
|
||||
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-url')[0];
|
||||
}
|
||||
|
||||
initActions() {
|
||||
this.$saveServerButton.addEventListener('click', () => {
|
||||
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
|
||||
DomainUtil.addDomain(serverConf).then(() => {
|
||||
this.props.onChange(this.props.index);
|
||||
});
|
||||
}, errorMessage => {
|
||||
alert(errorMessage);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NewServerForm;
|
||||
68
app/renderer/js/pages/preference/preference.js
Normal file
68
app/renderer/js/pages/preference/preference.js
Normal file
@@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const BaseComponent = require(__dirname + '/js/components/base.js');
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
const Nav = require(__dirname + '/js/pages/preference/nav.js');
|
||||
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
|
||||
const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js');
|
||||
|
||||
class PreferenceView extends BaseComponent {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.$sidebarContainer = document.getElementById('sidebar');
|
||||
this.$settingsContainer = document.getElementById('settings-container');
|
||||
}
|
||||
|
||||
init() {
|
||||
this.nav = new Nav({
|
||||
$root: this.$sidebarContainer,
|
||||
onItemSelected: this.handleNavigation.bind(this)
|
||||
});
|
||||
|
||||
this.setDefaultView();
|
||||
this.registerIpcs();
|
||||
}
|
||||
|
||||
setDefaultView() {
|
||||
let nav = 'General';
|
||||
const hasTag = window.location.hash;
|
||||
if (hasTag) {
|
||||
nav = hasTag.substring(1);
|
||||
}
|
||||
this.handleNavigation(nav);
|
||||
}
|
||||
|
||||
handleNavigation(navItem) {
|
||||
this.nav.select(navItem);
|
||||
switch (navItem) {
|
||||
case 'Servers': {
|
||||
this.section = new ServersSection({
|
||||
$root: this.$settingsContainer
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'General': {
|
||||
this.section = new GeneralSection({
|
||||
$root: this.$settingsContainer
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
this.section.init();
|
||||
window.location.hash = `#${navItem}`;
|
||||
}
|
||||
|
||||
registerIpcs() {
|
||||
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
|
||||
this.handleNavigation(navItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
const preferenceView = new PreferenceView();
|
||||
preferenceView.init();
|
||||
};
|
||||
77
app/renderer/js/pages/preference/server-info-form.js
Normal file
77
app/renderer/js/pages/preference/server-info-form.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
const {dialog} = require('electron').remote;
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||
|
||||
class ServerInfoForm extends BaseComponent {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
template() {
|
||||
return `
|
||||
<div class="settings-card">
|
||||
<div class="server-info-left">
|
||||
<img class="server-info-icon" src="${this.props.server.icon}"/>
|
||||
</div>
|
||||
<div class="server-info-right">
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-alias">${this.props.server.alias}</span>
|
||||
<i class="material-icons open-tab-button">open_in_new</i>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<input class="server-info-url" disabled value="${this.props.server.url}"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<div class="action red server-delete-action">
|
||||
<i class="material-icons">indeterminate_check_box</i>
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initForm();
|
||||
this.initActions();
|
||||
}
|
||||
|
||||
initForm() {
|
||||
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
|
||||
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[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);
|
||||
}
|
||||
|
||||
initActions() {
|
||||
this.$deleteServerButton.addEventListener('click', () => {
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
buttons: ['YES', 'NO'],
|
||||
defaultId: 0,
|
||||
message: 'Are you sure you want to delete this server?'
|
||||
}, response => {
|
||||
if (response === 0) {
|
||||
DomainUtil.removeDomain(this.props.index);
|
||||
this.props.onChange(this.props.index);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ServerInfoForm;
|
||||
81
app/renderer/js/pages/preference/servers-section.js
Normal file
81
app/renderer/js/pages/preference/servers-section.js
Normal file
@@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||
const ServerInfoForm = require(__dirname + '/server-info-form.js');
|
||||
const NewServerForm = require(__dirname + '/new-server-form.js');
|
||||
|
||||
class ServersSection extends BaseComponent {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
template() {
|
||||
return `
|
||||
<div class="settings-pane" id="server-settings-pane">
|
||||
<div class="title">Manage Servers</div>
|
||||
<div class="actions-container">
|
||||
<div class="action green" id="new-server-action">
|
||||
<i class="material-icons">add_box</i>
|
||||
<span>Add Server</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="new-server-container" class="hidden"></div>
|
||||
<div class="sub-title" id="existing-servers"></div>
|
||||
<div id="server-info-container"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initServers();
|
||||
this.initActions();
|
||||
}
|
||||
|
||||
initServers() {
|
||||
this.props.$root.innerHTML = '';
|
||||
|
||||
const servers = DomainUtil.getDomains();
|
||||
this.props.$root.innerHTML = this.template();
|
||||
this.$serverInfoContainer = document.getElementById('server-info-container');
|
||||
this.$existingServers = document.getElementById('existing-servers');
|
||||
this.$newServerContainer = document.getElementById('new-server-container');
|
||||
this.$newServerButton = document.getElementById('new-server-action');
|
||||
|
||||
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
|
||||
// Show Existing servers if servers are there otherwise hide it
|
||||
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing servers';
|
||||
this.initNewServerForm();
|
||||
|
||||
for (let i = 0; i < servers.length; i++) {
|
||||
new ServerInfoForm({
|
||||
$root: this.$serverInfoContainer,
|
||||
server: servers[i],
|
||||
index: i,
|
||||
onChange: this.handleServerInfoChange.bind(this)
|
||||
}).init();
|
||||
}
|
||||
}
|
||||
|
||||
initNewServerForm() {
|
||||
new NewServerForm({
|
||||
$root: this.$newServerContainer,
|
||||
onChange: this.handleServerInfoChange.bind(this)
|
||||
}).init();
|
||||
}
|
||||
|
||||
initActions() {
|
||||
this.$newServerContainer.classList.remove('hidden');
|
||||
this.$newServerButton.classList.remove('green');
|
||||
this.$newServerButton.classList.add('grey');
|
||||
}
|
||||
|
||||
handleServerInfoChange() {
|
||||
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ServersSection;
|
||||
@@ -1,6 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
const {spellChecker} = require('./spellchecker');
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
require('./notification');
|
||||
|
||||
const logout = () => {
|
||||
// Create the menu for the below
|
||||
@@ -34,6 +37,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// redirect users to network troubleshooting page
|
||||
document.querySelector('.restart_get_events_button').addEventListener('click', () => {
|
||||
ipcRenderer.send('reload-main');
|
||||
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
|
||||
|
||||
function spellChecker() {
|
||||
|
||||
@@ -9,6 +9,8 @@ const {Tray, Menu, nativeImage, BrowserWindow} = remote;
|
||||
|
||||
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
|
||||
|
||||
const ConfigUtil = require(__dirname + '/utils/config-util.js');
|
||||
|
||||
const iconPath = () => {
|
||||
if (process.platform === 'linux') {
|
||||
return APP_ICON + 'linux.png';
|
||||
@@ -45,7 +47,8 @@ const config = {
|
||||
|
||||
const renderCanvas = function (arg) {
|
||||
config.unreadCount = arg;
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
return new Promise(resolve => {
|
||||
const SIZE = config.size * config.pixelRatio;
|
||||
const PADDING = SIZE * 0.05;
|
||||
const CENTER = SIZE / 2;
|
||||
@@ -83,8 +86,6 @@ const renderCanvas = function (arg) {
|
||||
}
|
||||
|
||||
resolve(canvas);
|
||||
} else {
|
||||
reject(canvas);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -135,7 +136,7 @@ const createTray = function () {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Manage Zulip servers',
|
||||
label: 'Settings',
|
||||
click() {
|
||||
ipcRenderer.send('focus-app');
|
||||
sendAction('open-settings');
|
||||
@@ -161,6 +162,10 @@ const createTray = function () {
|
||||
};
|
||||
|
||||
ipcRenderer.on('destroytray', event => {
|
||||
if (!window.tray) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.tray.destroy();
|
||||
if (window.tray.isDestroyed()) {
|
||||
window.tray = null;
|
||||
@@ -172,35 +177,47 @@ ipcRenderer.on('destroytray', event => {
|
||||
});
|
||||
|
||||
ipcRenderer.on('tray', (event, arg) => {
|
||||
if (arg === 0) {
|
||||
unread = arg;
|
||||
// Message Count // console.log("message count is zero.");
|
||||
window.tray.setImage(iconPath());
|
||||
window.tray.setToolTip('No unread messages');
|
||||
} else {
|
||||
unread = arg;
|
||||
renderNativeImage(arg).then(image => {
|
||||
window.tray.setImage(image);
|
||||
window.tray.setToolTip(arg + ' unread messages');
|
||||
});
|
||||
if (!window.tray) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('toggletray', event => {
|
||||
if (event) {
|
||||
if (window.tray) {
|
||||
window.tray.destroy();
|
||||
if (window.tray.isDestroyed()) {
|
||||
window.tray = null;
|
||||
}
|
||||
// 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 (process.platform === 'linux') {
|
||||
if (arg === 0) {
|
||||
unread = arg;
|
||||
// Message Count // console.log("message count is zero.");
|
||||
window.tray.setImage(iconPath());
|
||||
window.tray.setToolTip('No unread messages');
|
||||
} else {
|
||||
createTray();
|
||||
renderNativeImage(unread).then(image => {
|
||||
unread = arg;
|
||||
renderNativeImage(arg).then(image => {
|
||||
window.tray.setImage(image);
|
||||
window.tray.setToolTip(unread + ' unread messages');
|
||||
window.tray.setToolTip(arg + ' unread messages');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
createTray();
|
||||
function toggleTray() {
|
||||
if (window.tray) {
|
||||
window.tray.destroy();
|
||||
if (window.tray.isDestroyed()) {
|
||||
window.tray = null;
|
||||
}
|
||||
ConfigUtil.setConfigItem('trayIcon', false);
|
||||
} else {
|
||||
createTray();
|
||||
if (process.platform === 'linux') {
|
||||
renderNativeImage(unread).then(image => {
|
||||
window.tray.setImage(image);
|
||||
window.tray.setToolTip(unread + ' unread messages');
|
||||
});
|
||||
}
|
||||
ConfigUtil.setConfigItem('trayIcon', true);
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on('toggletray', toggleTray);
|
||||
|
||||
if (ConfigUtil.getConfigItem('trayIcon', true)) {
|
||||
createTray();
|
||||
}
|
||||
|
||||
53
app/renderer/js/utils/config-util.js
Normal file
53
app/renderer/js/utils/config-util.js
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const process = require('process');
|
||||
const JsonDB = require('node-json-db');
|
||||
|
||||
let instance = null;
|
||||
let app = null;
|
||||
|
||||
/* To make the util runnable in both main and renderer process */
|
||||
if (process.type === 'renderer') {
|
||||
app = require('electron').remote.app;
|
||||
} else {
|
||||
app = require('electron').app;
|
||||
}
|
||||
|
||||
class ConfigUtil {
|
||||
constructor() {
|
||||
if (instance) {
|
||||
return instance;
|
||||
} else {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
this.reloadDB();
|
||||
return instance;
|
||||
}
|
||||
|
||||
getConfigItem(key, defaultValue = null) {
|
||||
const value = this.db.getData('/')[key];
|
||||
if (value === undefined) {
|
||||
this.setConfigItem(key, defaultValue);
|
||||
return defaultValue;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
setConfigItem(key, value) {
|
||||
this.db.push(`/${key}`, value, true);
|
||||
this.reloadDB();
|
||||
}
|
||||
|
||||
removeConfigItem(key) {
|
||||
this.db.delete(`/${key}`);
|
||||
this.reloadDB();
|
||||
}
|
||||
|
||||
reloadDB() {
|
||||
this.db = new JsonDB(app.getPath('userData') + '/settings.json', true, true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ConfigUtil();
|
||||
@@ -1,12 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const {app} = require('electron').remote;
|
||||
const {app, dialog} = require('electron').remote;
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const JsonDB = require('node-json-db');
|
||||
const request = require('request');
|
||||
|
||||
let instance = null;
|
||||
|
||||
const defaultIconUrl = __dirname + '../../../img/icon.png';
|
||||
const defaultIconUrl = '../renderer/img/icon.png';
|
||||
|
||||
class DomainUtil {
|
||||
constructor() {
|
||||
if (instance) {
|
||||
@@ -15,7 +18,7 @@ class DomainUtil {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
||||
this.reloadDB();
|
||||
// Migrate from old schema
|
||||
if (this.db.getData('/').domain) {
|
||||
this.addDomain({
|
||||
@@ -29,6 +32,7 @@ class DomainUtil {
|
||||
}
|
||||
|
||||
getDomains() {
|
||||
this.reloadDB();
|
||||
if (this.db.getData('/').domains === undefined) {
|
||||
return [];
|
||||
} else {
|
||||
@@ -37,20 +41,36 @@ class DomainUtil {
|
||||
}
|
||||
|
||||
getDomain(index) {
|
||||
this.reloadDB();
|
||||
return this.db.getData(`/domains[${index}]`);
|
||||
}
|
||||
|
||||
addDomain(server) {
|
||||
server.icon = server.icon || defaultIconUrl;
|
||||
this.db.push('/domains[]', server, true);
|
||||
return new Promise(resolve => {
|
||||
if (server.icon) {
|
||||
this.saveServerIcon(server.icon).then(localIconUrl => {
|
||||
server.icon = localIconUrl;
|
||||
this.db.push('/domains[]', server, true);
|
||||
this.reloadDB();
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
server.icon = defaultIconUrl;
|
||||
this.db.push('/domains[]', server, true);
|
||||
this.reloadDB();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeDomains() {
|
||||
this.db.delete('/domains');
|
||||
this.reloadDB();
|
||||
}
|
||||
|
||||
removeDomain(index) {
|
||||
this.db.delete(`/domains[${index}]`);
|
||||
this.reloadDB();
|
||||
}
|
||||
|
||||
checkDomain(domain) {
|
||||
@@ -61,22 +81,103 @@ class DomainUtil {
|
||||
|
||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
||||
|
||||
const serverConf = {
|
||||
icon: defaultIconUrl,
|
||||
url: domain,
|
||||
alias: domain
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(checkDomain, (error, response) => {
|
||||
const certsError =
|
||||
['Error: self signed certificate',
|
||||
'Error: unable to verify the first certificate'
|
||||
];
|
||||
if (!error && response.statusCode !== 404) {
|
||||
resolve(domain);
|
||||
} else if (error.toString().indexOf('Error: self signed certificate') >= 0) {
|
||||
if (window.confirm(`Do you trust certificate from ${domain}?`)) {
|
||||
resolve(domain);
|
||||
} else {
|
||||
reject('Untrusted Certificate.');
|
||||
}
|
||||
// Correct
|
||||
this.getServerSettings(domain).then(serverSettings => {
|
||||
resolve(serverSettings);
|
||||
}, () => {
|
||||
resolve(serverConf);
|
||||
});
|
||||
} 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 {
|
||||
reject('Not a valid Zulip server');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
// The save will always succeed. If url is invalid, downgrade to default icon.
|
||||
const dir = `${app.getPath('userData')}/server-icons`;
|
||||
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const filePath = `${dir}/${new Date().getMilliseconds()}${path.extname(url).split('?')[0]}`;
|
||||
const file = fs.createWriteStream(filePath);
|
||||
try {
|
||||
request(url).on('response', response => {
|
||||
response.on('error', err => {
|
||||
console.log(err);
|
||||
resolve(defaultIconUrl);
|
||||
});
|
||||
response.pipe(file).on('finish', () => {
|
||||
resolve(filePath);
|
||||
});
|
||||
}).on('error', err => {
|
||||
console.log(err);
|
||||
resolve(defaultIconUrl);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
resolve(defaultIconUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reloadDB() {
|
||||
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DomainUtil();
|
||||
|
||||
@@ -19,10 +19,7 @@ class LinkUtil {
|
||||
const currentDomain = wurl('hostname', currentUrl);
|
||||
const newDomain = wurl('hostname', newUrl);
|
||||
|
||||
const skipImages = '.jpg|.gif|.png|.jpeg|.JPG|.PNG';
|
||||
|
||||
// We'll be needing this to open images in default browser
|
||||
return (currentDomain === newDomain) && !newUrl.match(skipImages);
|
||||
return (currentDomain === newDomain) && newUrl.includes('/#narrow');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="responsive desktop">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Zulip</title>
|
||||
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div id="sidebar">
|
||||
<div id="tabs-container"></div>
|
||||
<div id="actions-container">
|
||||
<div class="action-button" id="reload-action">
|
||||
<i class="material-icons md-48">refresh</i>
|
||||
</div>
|
||||
<div class="action-button" id="add-action">
|
||||
<i class="material-icons md-48">add_circle</i>
|
||||
</div>
|
||||
<div class="action-button" id="settings-action">
|
||||
<i class="material-icons md-48">settings</i>
|
||||
</div>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Zulip</title>
|
||||
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="content">
|
||||
<div class="popup">
|
||||
<span class="popuptext hidden" id="fullscreen-popup"></span>
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
<div id="view-controls-container">
|
||||
<div id="tabs-container"></div>
|
||||
<div id="add-tab" class="tab functional-tab">
|
||||
<div class="server-tab" id="add-action">
|
||||
<i class="material-icons">add</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="js/main.js"></script>
|
||||
</html>
|
||||
<div id="actions-container">
|
||||
<div class="action-button" id="reload-action">
|
||||
<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>
|
||||
@@ -5,40 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Zulip - Settings</title>
|
||||
<link rel="stylesheet" href="css/preference.css" type="text/css" media="screen">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div id="sidebar">
|
||||
<div id="settings-header">Settings</div>
|
||||
<div id="tabs-container">
|
||||
<div class="tab" id="general-settings">General</div>
|
||||
<div class="tab active" id="server-settings">Servers</div>
|
||||
<div class="tab" id="about-settings">About</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-container">
|
||||
<div class="settings-pane" id="server-settings-pane">
|
||||
<div class="title">Manage Servers</div>
|
||||
<div class="actions-container">
|
||||
<div class="action" id="new-server-action">
|
||||
<i class="material-icons">add_box</i>
|
||||
<span>New Server</span>
|
||||
</div>
|
||||
<div class="action hidden" id="save-server-action">
|
||||
<button class="save-server-button">Save</button>
|
||||
</div>
|
||||
<div class="action hidden" id="reload-server-action">
|
||||
<i class="material-icons">refresh</i>
|
||||
<span>Reload to Apply Changes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="server-info-container">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sidebar"></div>
|
||||
<div id="settings-container"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="js/pages/preference.js"></script>
|
||||
<script src="js/pages/preference/preference.js"></script>
|
||||
</html>
|
||||
|
||||
@@ -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
|
||||
|
||||
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)
|
||||
* [Node.js](https://nodejs.org) >= v6.9.0
|
||||
* [python](https://www.python.org/downloads/release/python-2713/) (v2.7.x recommended)
|
||||
* [node-gyp](https://github.com/nodejs/node-gyp#installation)
|
||||
* [NPM](https://www.npmjs.com/get-npm) and
|
||||
[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
|
||||
$ 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:
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/zulip/zulip-electron
|
||||
$ cd zulip-electron
|
||||
```
|
||||
|
||||
Install project dependencies:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
```
|
||||
Start the app:
|
||||
|
||||
Start the app:
|
||||
```sh
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Start and watch changes
|
||||
|
||||
Start and watch changes:
|
||||
```sh
|
||||
$ 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
|
||||
```
|
||||
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
|
||||
If you have any problems running the app please see the [most common issues](./troubleshooting.md).
|
||||
The output files appear in the `dist/` directory.
|
||||
|
||||
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)
|
||||
26
docs/desktop-release.md
Normal file
26
docs/desktop-release.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# New release checklist -
|
||||
|
||||
## We need to cross check following things before pushing a new release + after updating electron version. This is just to make sure that nothing gets broken.
|
||||
## - Desktop notifications
|
||||
## - Spellchecker
|
||||
## - Auto updates
|
||||
**Check for the logs in -**
|
||||
- **on Linux:** `~/.config/Zulip/log.log`
|
||||
- **on OS X:** `~/Library/Logs/Zulip/log.log`
|
||||
- **on Windows:** `%USERPROFILE%\AppData\Roaming\Zulip\log.log`
|
||||
## - All the installer i.e.
|
||||
- Linux (.deb, AppImage)
|
||||
- Mac - (.dmg)
|
||||
- Windows - (web installer for 32/64bit)
|
||||
## - Check for errors in console (if any)
|
||||
## - Code signing verification on Mac and Windows
|
||||
## - Tray and menu options
|
||||
# We need to cross check all these things on -
|
||||
- Windows 7
|
||||
- Windows 8
|
||||
- Windows 10
|
||||
- Ubuntu 14.04/16.04
|
||||
- macOSX
|
||||
|
||||
|
||||
|
||||
40
package.json
40
package.json
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "1.1.0-beta",
|
||||
"version": "1.3.0-beta",
|
||||
"main": "./app/main",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
"email": "<svnitakash@gmail.com>",
|
||||
"copyright": "©2017 Kandra Labs, Inc.",
|
||||
"author": {
|
||||
"name": "Akash Nimare",
|
||||
"name": "Kandra Labs, Inc.",
|
||||
"email": "svnitakash@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
@@ -20,13 +20,13 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron app --disable-http-cache",
|
||||
"postinstall": "install-app-deps",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"test": "xo",
|
||||
"dev": "gulp dev",
|
||||
"pack": "build --dir",
|
||||
"dist": "build",
|
||||
"mas": "build --mac mas",
|
||||
"build:win": "build --win nsis-web --ia32 --x64",
|
||||
"pack": "electron-builder --dir",
|
||||
"dist": "electron-builder",
|
||||
"mas": "electron-builder --mac mas",
|
||||
"build:win": "electron-builder --win nsis-web --ia32 --x64",
|
||||
"travis": "cd ./scripts && ./travis-build-test.sh"
|
||||
},
|
||||
"build": {
|
||||
@@ -34,6 +34,7 @@
|
||||
"asar": true,
|
||||
"files": [
|
||||
"**/*",
|
||||
"!docs${/*}",
|
||||
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
|
||||
],
|
||||
"copyright": "©2017 Kandra Labs, Inc.",
|
||||
@@ -74,11 +75,13 @@
|
||||
},
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"icon": "build/icon.ico"
|
||||
"icon": "build/icon.ico",
|
||||
"publisherName": "Kandra Labs, Inc."
|
||||
},
|
||||
"nsis": {
|
||||
"perMachine": true,
|
||||
"oneClick": false
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
@@ -92,15 +95,15 @@
|
||||
"devDependencies": {
|
||||
"assert": "1.4.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron-builder": "17.10.0",
|
||||
"electron": "1.6.8",
|
||||
"electron-connect": "0.4.8",
|
||||
"electron-builder": "19.19.1",
|
||||
"electron": "1.6.11",
|
||||
"electron-connect": "0.6.2",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-mocha": "3.0.1",
|
||||
"chai-as-promised": "6.0.0",
|
||||
"chai": "^3.5.0",
|
||||
"spectron": "3.6.4",
|
||||
"xo": "0.18.1"
|
||||
"gulp-mocha": "4.3.1",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai": "4.1.1",
|
||||
"spectron": "3.7.2",
|
||||
"xo": "0.18.2"
|
||||
},
|
||||
"xo": {
|
||||
"parserOptions": {
|
||||
@@ -126,7 +129,8 @@
|
||||
"guard-for-in": 0,
|
||||
"prefer-promise-reject-errors": 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
|
||||
* 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`
|
||||
|
||||
### 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 $*
|
||||
gitCheckout
|
||||
npmInstallStart
|
||||
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