Compare commits

..

14 Commits

Author SHA1 Message Date
Akash Nimare
208826e0bb setting-page: Update input styling. 2018-03-14 23:32:16 +05:30
Akash Nimare
25875a9a35 setting-page: Style add new server page [WIP] #340. 2018-03-14 05:04:37 +05:30
Akash Nimare
2da195f6f6 setting-page: Don't show add server nav item in setting page.
I think it makes sense to hide this nav item since
we already have the big + button to trigger this in left-sidebar.
Might want to revist this in future.

WIP #340.
2018-03-14 02:25:43 +05:30
Akash Nimare
5ba6ae777e setting-page: Rename Server nav item.
WIP #340.
2018-03-14 02:13:16 +05:30
Akash Nimare
2c212f7035 setting-page: Fix webview showing outline.
WIP #340.
2018-03-14 00:22:16 +05:30
Akash Nimare
0fe62b2023 main-window: Update default height, width of main window. 2018-03-11 09:18:34 +05:30
Akash Nimare
f186c200e2 setting-page: Make add new server page responsive. 2018-03-11 09:15:03 +05:30
Akash Nimare
20d1c06dc5 setting-page: Minor styling updates. 2018-03-11 08:27:00 +05:30
Akash Nimare
2c21aea643 setting-page: Update styling for setting page. 2018-03-11 07:30:47 +05:30
Akash Nimare
1a3511c28c setting-page: Refactor code for setting-page + update UI.
WIP #340.
2018-03-11 05:34:04 +05:30
Akash Nimare
4c4de820e2 setting-page: Styling new server page WIP #340. 2018-03-11 03:35:25 +05:30
Akash Nimare
ea77c5f003 setting-page: Add new server page basic positioning.
WIP #340.
2018-03-11 03:02:01 +05:30
Akash Nimare
d22d26cfdb setting-page: Add a template for new server page.
WIP #340.
2018-03-11 01:04:12 +05:30
Akash Nimare
fc12e7a00e setting-page: Create new setting nav item for connected organizations.
WIP #340.
2018-03-11 00:33:35 +05:30
72 changed files with 5389 additions and 8702 deletions

17
.gitattributes vendored
View File

@@ -1,16 +1 @@
* text=auto eol=lf
package-lock.json binary
app/package-lock.json binary
*.gif binary
*.jpg binary
*.jpeg binary
*.eot binary
*.woff binary
*.woff2 binary
*.svg binary
*.ttf binary
*.png binary
*.otf binary
*.tif binary
*.ogg binary
* text=auto

9
.gitignore vendored
View File

@@ -7,13 +7,6 @@ node_modules/
# Compiled binary build directory
dist/
#snap generated files
snap/parts
snap/prime
snap/snap
snap/stage
snap/*.snap
# Logs
logs
*.log
@@ -33,6 +26,6 @@ yarn-error.log*
config.gypi
# Test generated files
# tests/package.json
tests/package.json
.python-version

View File

@@ -15,7 +15,7 @@ addons:
language: node_js
node_js:
- '8'
- '6'
before_install:
- ./scripts/travis-xvfb.sh

View File

@@ -12,7 +12,7 @@ Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If yo
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/16-desktop).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/electron).
## Issue
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-electron/issues/new).

View File

@@ -2,26 +2,31 @@
[![Build Status](https://travis-ci.org/zulip/zulip-electron.svg?branch=master)](https://travis-ci.org/zulip/zulip-electron)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/zulip/zulip-electron?branch=master&svg=true)](https://ci.appveyor.com/project/akashnimare/zulip-electron/branch/master)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://chat.zulip.org)
Desktop client for Zulip. Available for Mac, Linux, and Windows.
Desktop client for Zulip. Available for Mac, Linux and Windows.
<img src="http://i.imgur.com/ChzTq4F.png"/>
# Download
Please see the [installation guide](https://zulipchat.com/help/desktop-app-install-guide).
Please see [installation guide](https://zulipchat.com/help/desktop-app-install-guide).
# Features
* Sign in to multiple organizations
* Desktop notifications with inline reply
* Tray/dock integration
* Multi-language spell checker
* Automatic updates
* Sign in to multiple teams
* Desktop Notifications with inline reply support
* Multilanguage SpellChecker
* OSX/Win/Linux installers
* Automatic Updates (macOS/Windows/Linux)
* Keyboard shortcuts
# Development
Please see our [development guide](./development.md) to get started and run app locally.
# Contribute
First, join us on the [Zulip community server](https://zulip.readthedocs.io/en/latest/contributing/chat-zulip-org.html)!
Also see our [contribution guidelines](./CONTRIBUTING.md) and our [development guide](./development.md).
If you want to contribute please make sure to read [our documentation about contributing](./CONTRIBUTING.md) first.
* [Issue Tracker](https://github.com/zulip/zulip-electron/issues)
* [Source Code](https://github.com/zulip/zulip-electron/)
# License
Released under the [Apache-2.0](./LICENSE) license.

View File

@@ -1,11 +1,11 @@
'use strict';
const { app, dialog, shell } = require('electron');
const { app, dialog } = require('electron');
const { autoUpdater } = require('electron-updater');
const isDev = require('electron-is-dev');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
function appUpdater(updateFromMenu = false) {
function appUpdater() {
// Don't initiate auto-updates in development
if (isDev) {
return;
@@ -17,8 +17,6 @@ function appUpdater(updateFromMenu = false) {
return;
}
let updateAvailable = false;
// Create Logs directory
const LogsDir = `${app.getPath('userData')}/Logs`;
@@ -30,58 +28,7 @@ function appUpdater(updateFromMenu = false) {
autoUpdater.logger = log;
// Handle auto updates for beta/pre releases
const isBetaUpdate = ConfigUtil.getConfigItem('betaUpdate');
autoUpdater.allowPrerelease = isBetaUpdate || false;
const eventsListenerRemove = ['update-available', 'update-not-available'];
autoUpdater.on('update-available', info => {
if (updateFromMenu) {
dialog.showMessageBox({
message: `A new version ${info.version}, of Zulip Desktop is available`,
detail: 'The update will be downloaded in the background. You will be notified when it is ready to be installed.'
});
updateAvailable = true;
// This is to prevent removal of 'update-downloaded' and 'error' event listener.
eventsListenerRemove.forEach(event => {
autoUpdater.removeAllListeners(event);
});
}
});
autoUpdater.on('update-not-available', () => {
if (updateFromMenu) {
dialog.showMessageBox({
message: 'No updates available',
detail: `You are running the latest version of Zulip Desktop.\nVersion: ${app.getVersion()}`
});
// Remove all autoUpdator listeners so that next time autoUpdator is manually called these
// listeners don't trigger multiple times.
autoUpdater.removeAllListeners();
}
});
autoUpdater.on('error', error => {
if (updateFromMenu) {
const messageText = (updateAvailable) ? ('Unable to download the updates') : ('Unable to check for updates');
dialog.showMessageBox({
type: 'error',
buttons: ['Manual Download', 'Cancel'],
message: messageText,
detail: (error).toString() + `\n\nThe latest version of Zulip Desktop is available at -\nhttps://zulipchat.com/apps/.\n
Current Version: ${app.getVersion()}`
}, response => {
if (response === 0) {
shell.openExternal('https://zulipchat.com/apps/');
}
});
// Remove all autoUpdator listeners so that next time autoUpdator is manually called these
// listeners don't trigger multiple times.
autoUpdater.removeAllListeners();
}
});
autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false;
// Ask the user if update is available
// eslint-disable-next-line no-unused-vars

View File

@@ -0,0 +1,16 @@
'use strict';
const { crashReporter } = require('electron');
const crashHandler = () => {
crashReporter.start({
productName: 'zulip-electron',
companyName: 'Kandra Labs, Inc.',
submitURL: 'https://zulip-sentry.herokuapp.com/crashreport',
autoSubmit: true
});
};
module.exports = {
crashHandler
};

View File

@@ -1,12 +1,11 @@
'use strict';
const path = require('path');
const fs = require('fs');
const electron = require('electron');
const windowStateKeeper = require('electron-window-state');
const isDev = require('electron-is-dev');
const appMenu = require('./menu');
const { appUpdater } = require('./autoupdater');
const { crashHandler } = require('./crash-reporter');
const { setAutoLaunch } = require('./startup');
@@ -14,8 +13,6 @@ const { app, ipcMain } = electron;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
const ProxyUtil = require('./../renderer/js/utils/proxy-util.js');
const { sentryInit } = require('./../renderer/js/utils/sentry-util.js');
// Adds debug features like hotkeys for triggering dev tools and reload
// in development mode
@@ -32,19 +29,18 @@ let isQuitting = false;
// Load this url in main window
const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
const singleInstanceLock = app.requestSingleInstanceLock();
if (singleInstanceLock) {
app.on('second-instance', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
const isAlreadyRunning = app.makeSingleInstance(() => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
});
} else {
app.quit();
mainWindow.show();
}
});
if (isAlreadyRunning) {
return app.quit();
}
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
@@ -57,8 +53,7 @@ function createMainWindow() {
// Load the previous state with fallback to defaults
const mainWindowState = windowStateKeeper({
defaultWidth: 1100,
defaultHeight: 720,
path: `${app.getPath('userData')}/config`
defaultHeight: 720
});
// Let's keep the window position global so that we can access it in other process
@@ -76,8 +71,8 @@ function createMainWindow() {
minHeight: 400,
webPreferences: {
plugins: true,
nodeIntegration: true,
partition: 'persist:webviewsession'
allowDisplayingInsecureContent: true,
nodeIntegration: true
},
show: false
});
@@ -86,6 +81,14 @@ function createMainWindow() {
win.webContents.send('focus');
});
win.once('ready-to-show', () => {
if (ConfigUtil.getConfigItem('startMinimized')) {
win.minimize();
} else {
win.show();
}
});
win.loadURL(mainURL);
// Keep the app running in background on close event
@@ -129,10 +132,6 @@ function createMainWindow() {
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
// Temporary fix for Electron render colors differently
// More info here - https://github.com/electron/electron/issues/10732
app.commandLine.appendSwitch('force-color-profile', 'srgb');
// eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
@@ -151,37 +150,20 @@ app.on('ready', () => {
});
mainWindow = createMainWindow();
// Auto-hide menu bar on Windows + Linux
if (process.platform !== 'darwin') {
const shouldHideMenu = ConfigUtil.getConfigItem('autoHideMenubar') || false;
mainWindow.setAutoHideMenuBar(shouldHideMenu);
mainWindow.setMenuBarVisibility(!shouldHideMenu);
}
// Initialize sentry for main process
sentryInit();
const isSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
if (isSystemProxy) {
ProxyUtil.resolveSystemProxy(mainWindow);
}
const page = mainWindow.webContents;
page.on('dom-ready', () => {
if (ConfigUtil.getConfigItem('startMinimized')) {
mainWindow.hide();
mainWindow.minimize();
} else {
mainWindow.show();
}
});
page.once('did-frame-finish-load', () => {
// Initiate auto-updates on MacOS and Windows
if (ConfigUtil.getConfigItem('autoUpdate')) {
appUpdater();
}
// Initate auto-updates on MacOS and Windows
appUpdater();
crashHandler();
});
// Temporarily remove this event
@@ -198,32 +180,6 @@ app.on('ready', () => {
app.quit();
});
// Code to show pdf in a new BrowserWindow (currently commented out due to bug-upstream)
// ipcMain.on('pdf-view', (event, url) => {
// // Paddings for pdfWindow so that it fits into the main browserWindow
// const paddingWidth = 55;
// const paddingHeight = 22;
// // Get the config of main browserWindow
// const mainWindowState = global.mainWindowState;
// // Window to view the pdf file
// const pdfWindow = new electron.BrowserWindow({
// x: mainWindowState.x + paddingWidth,
// y: mainWindowState.y + paddingHeight,
// width: mainWindowState.width - paddingWidth,
// height: mainWindowState.height - paddingHeight,
// webPreferences: {
// plugins: true,
// partition: 'persist:webviewsession'
// }
// });
// pdfWindow.loadURL(url);
// // We don't want to have the menu bar in pdf window
// pdfWindow.setMenu(null);
// });
// Reload full app not just webview, useful in debugging
ipcMain.on('reload-full-app', () => {
mainWindow.reload();
@@ -237,10 +193,10 @@ app.on('ready', () => {
});
ipcMain.on('toggle-app', () => {
if (!mainWindow.isVisible() || mainWindow.isMinimized()) {
mainWindow.show();
} else {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
}
});
@@ -248,11 +204,6 @@ app.on('ready', () => {
BadgeSettings.updateBadge(badgeCount, mainWindow);
});
ipcMain.on('toggle-menubar', (event, showMenubar) => {
mainWindow.setAutoHideMenuBar(showMenubar);
mainWindow.setMenuBarVisibility(!showMenubar);
});
ipcMain.on('update-badge', (event, messageCount) => {
badgeCount = messageCount;
BadgeSettings.updateBadge(badgeCount, mainWindow);
@@ -269,81 +220,11 @@ app.on('ready', () => {
ipcMain.on('update-menu', (event, props) => {
appMenu.setMenu(props);
const activeTab = props.tabs[props.activeTabIndex];
if (activeTab) {
mainWindow.setTitle(`Zulip - ${activeTab.webview.props.name}`);
}
});
ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => {
setAutoLaunch(AutoLaunchValue);
});
ipcMain.on('downloadFile', (event, url, downloadPath) => {
page.downloadURL(url);
page.session.once('will-download', (event, item) => {
const filePath = path.join(downloadPath, item.getFilename());
const getTimeStamp = () => {
const date = new Date();
return date.getTime();
};
const formatFile = filePath => {
const fileExtension = path.extname(filePath);
const baseName = path.basename(filePath, fileExtension);
return `${baseName}-${getTimeStamp()}${fileExtension}`;
};
// Update the name and path of the file if it already exists
const updatedFilePath = path.join(downloadPath, formatFile(filePath));
const setFilePath = fs.existsSync(filePath) ? updatedFilePath : filePath;
item.setSavePath(setFilePath);
item.on('updated', (event, state) => {
switch (state) {
case 'interrupted': {
// Can interrupted to due to network error, cancel download then
console.log('Download interrupted, cancelling and fallback to dialog download.');
item.cancel();
break;
}
case 'progressing': {
if (item.isPaused()) {
item.cancel();
}
// This event can also be used to show progress in percentage in future.
break;
}
default: {
console.info('Unknown updated state of download item');
}
}
});
item.once('done', (event, state) => {
const getFileName = fs.existsSync(filePath) ? formatFile(filePath) : item.getFilename();
if (state === 'completed') {
page.send('downloadFileCompleted', item.getSavePath(), getFileName);
} else {
console.log('Download failed state: ', state);
page.send('downloadFileFailed');
}
// To stop item for listening to updated events of this file
item.removeAllListeners('updated');
});
});
});
ipcMain.on('realm-name-changed', (event, serverURL, realmName) => {
page.send('update-realm-name', serverURL, realmName);
});
ipcMain.on('realm-icon-changed', (event, serverURL, iconURL) => {
page.send('update-realm-icon', serverURL, iconURL);
});
});
app.on('before-quit', () => {

View File

@@ -4,31 +4,20 @@ const { Notification } = require('electron');
const request = require('request');
const semver = require('semver');
const ConfigUtil = require('../renderer/js/utils/config-util');
const ProxyUtil = require('../renderer/js/utils/proxy-util');
const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
const Logger = require('../renderer/js/utils/logger-util');
const logger = new Logger({
file: 'linux-update-util.log',
timestamp: true
});
function linuxUpdateNotification() {
let url = 'https://api.github.com/repos/zulip/zulip-electron/releases';
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const options = {
url,
headers: {'User-Agent': 'request'},
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : '',
ecdhCurve: 'auto'
headers: {'User-Agent': 'request'}
};
request(options, (error, response, body) => {
if (error) {
logger.error('Linux update error.');
logger.error(error);
console.log('Error:', error);
return;
}
if (response.statusCode < 400) {
@@ -43,7 +32,7 @@ function linuxUpdateNotification() {
}
}
} else {
logger.log('Linux update response status: ', response.statusCode);
console.log('Status:', response.statusCode);
}
});
}

View File

@@ -1,23 +1,15 @@
'use strict';
const os = require('os');
const path = require('path');
const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
const { app, shell, BrowserWindow, Menu } = require('electron');
const fs = require('fs-extra');
const AdmZip = require('adm-zip');
const { appUpdater } = require('./autoupdater');
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/../renderer/js/utils/dnd-util.js');
const Logger = require(__dirname + '/../renderer/js/utils/logger-util.js');
const appName = app.getName();
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class AppMenu {
getHistorySubmenu() {
return [{
@@ -39,65 +31,6 @@ class AppMenu {
}];
}
getToolsSubmenu() {
return [{
label: `Check for Updates`,
click() {
AppMenu.checkForUpdate();
}
},
{
label: `Release Notes`,
click() {
shell.openExternal(`https://github.com/zulip/zulip-electron/releases/tag/v${app.getVersion()}`);
}
}, {
type: 'separator'
}, {
label: 'Factory Reset',
accelerator: process.platform === 'darwin' ? 'Command+Shift+D' : 'Ctrl+Shift+D',
click() {
AppMenu.resetAppSettings();
}
}, {
label: 'Download App Logs',
click() {
const zip = new AdmZip();
let date = new Date();
date = date.toLocaleDateString().replace(/\//g, '-');
// Create a zip file of all the logs and config data
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/Logs`);
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/config`);
// Put the log file in downloads folder
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${date}.zip`;
zip.writeZip(logFilePath);
// Open and select the log file
shell.showItemInFolder(logFilePath);
}
}, {
type: 'separator'
}, {
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');
}
}
}];
}
getViewSubmenu() {
return [{
label: 'Reload',
@@ -154,7 +87,7 @@ class AppMenu {
}
}, {
label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+Shift+S',
accelerator: 'CommandOrControl+S',
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
@@ -162,36 +95,57 @@ class AppMenu {
ConfigUtil.setConfigItem('showSidebar', 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');
}
}
}];
}
getHelpSubmenu() {
return [
{
label: `${appName + ' Desktop'} v${app.getVersion()}`,
label: `${appName + ' Desktop-'} v${app.getVersion()}`,
enabled: false
},
{
label: 'About Zulip',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
label: `What's New...`,
click() {
shell.openExternal(`https://github.com/zulip/zulip-electron/releases/tag/v${app.getVersion()}`);
}
},
{
label: `Help Center`,
label: `${appName} Help`,
click() {
shell.openExternal('https://zulipchat.com/help/');
}
}, {
label: 'Report an Issue',
label: 'Show App Logs',
click() {
// the goal is to notify the main.html BrowserWindow
// which may not be the focused window.
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send('open-feedback-modal');
});
shell.openItem(app.getPath('userData'));
}
}, {
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)}`);
}
}];
}
@@ -232,12 +186,21 @@ class AppMenu {
}
getDarwinTpl(props) {
const { tabs, activeTabIndex, enableMenu } = props;
const { tabs, activeTabIndex } = props;
return [{
label: `${app.getName()}`,
submenu: [{
label: 'Desktop Settings',
label: 'About Zulip',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
}
}, {
type: 'separator'
}, {
label: 'Desktop App Settings',
accelerator: 'Cmd+,',
click(item, focusedWindow) {
if (focusedWindow) {
@@ -247,7 +210,6 @@ class AppMenu {
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Cmd+Shift+K',
enabled: enableMenu,
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
@@ -256,16 +218,14 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: 'Toggle Do Not Disturb',
accelerator: 'Command+Shift+M',
label: 'Reset App Settings',
accelerator: 'Command+Shift+D',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
AppMenu.resetAppSettings();
}
}, {
label: 'Log Out',
accelerator: 'Cmd+L',
enabled: enableMenu,
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('log-out');
@@ -305,6 +265,8 @@ class AppMenu {
role: 'paste'
}, {
role: 'pasteandmatchstyle'
}, {
role: 'delete'
}, {
role: 'selectall'
}]
@@ -317,9 +279,6 @@ class AppMenu {
}, {
label: 'Window',
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
}, {
label: 'Tools',
submenu: this.getToolsSubmenu()
}, {
role: 'help',
submenu: this.getHelpSubmenu()
@@ -327,12 +286,21 @@ class AppMenu {
}
getOtherTpl(props) {
const { tabs, activeTabIndex, enableMenu } = props;
const { tabs, activeTabIndex } = props;
return [{
label: '&File',
label: 'File',
submenu: [{
label: 'Desktop Settings',
label: 'About Zulip',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
}
}, {
type: 'separator'
}, {
label: 'Desktop App Settings',
accelerator: 'Ctrl+,',
click(item, focusedWindow) {
if (focusedWindow) {
@@ -344,7 +312,6 @@ class AppMenu {
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Ctrl+Shift+K',
enabled: enableMenu,
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
@@ -353,16 +320,14 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: 'Toggle Do Not Disturb',
accelerator: 'Ctrl+Shift+M',
label: 'Reset App Settings',
accelerator: 'Ctrl+Shift+D',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
AppMenu.resetAppSettings();
}
}, {
label: 'Log Out',
accelerator: 'Ctrl+L',
enabled: enableMenu,
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('log-out');
@@ -375,7 +340,7 @@ class AppMenu {
accelerator: 'Ctrl+Q'
}]
}, {
label: '&Edit',
label: 'Edit',
submenu: [{
role: 'undo'
}, {
@@ -390,25 +355,23 @@ class AppMenu {
role: 'paste'
}, {
role: 'pasteandmatchstyle'
}, {
role: 'delete'
}, {
type: 'separator'
}, {
role: 'selectall'
}]
}, {
label: '&View',
label: 'View',
submenu: this.getViewSubmenu()
}, {
label: '&History',
label: 'History',
submenu: this.getHistorySubmenu()
}, {
label: '&Window',
label: 'Window',
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
}, {
label: '&Tools',
submenu: this.getToolsSubmenu()
}, {
label: '&Help',
role: 'help',
submenu: this.getHelpSubmenu()
}];
@@ -424,37 +387,21 @@ class AppMenu {
win.webContents.send(action, ...params);
}
static checkForUpdate() {
appUpdater(true);
}
static resetAppSettings() {
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
// We save App's settings/configurations in following files
const settingFiles = ['config/window-state.json', 'config/domain.json', 'config/settings.json', 'config/certificates.json'];
const settingFiles = ['window-state.json', 'domain.json', 'settings.json'];
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure?',
detail: resetAppSettingsMessage
}, response => {
if (response === 0) {
settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, error => {
if (error) {
logger.error('Error while resetting app settings.');
logger.error(error);
} else {
fs.unlink(getSettingFilesPath, () => {
AppMenu.sendAction('clear-app-data');
});
}
settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, error => {
if (error) {
console.log(error);
} else {
fs.unlink(getSettingFilesPath, () => {
AppMenu.sendAction('clear-app-data');
});
});
}
}
});
});
}

1248
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.5.0-beta",
"version": "1.8.2",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -26,23 +26,19 @@
"InstantMessaging"
],
"dependencies": {
"@electron-elements/send-feedback": "1.0.8",
"@sentry/electron": "0.14.0",
"adm-zip": "0.4.11",
"auto-launch": "5.0.5",
"auto-launch": "5.0.1",
"electron-is-dev": "0.3.0",
"electron-log": "2.2.14",
"electron-log": "2.2.7",
"electron-spellchecker": "1.1.2",
"electron-updater": "4.0.6",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"electron-updater": "2.18.2",
"electron-window-state": "4.1.1",
"is-online": "7.0.0",
"node-json-db": "0.9.1",
"request": "2.85.0",
"node-json-db": "0.7.3",
"request": "2.81.0",
"semver": "5.4.1",
"wurl": "2.5.0"
},
"optionalDependencies": {
"node-mac-notifier": "1.1.0"
"node-mac-notifier": "0.0.13"
}
}

View File

@@ -1,46 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/about.css">
</head>
<body>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/about.css">
</head>
<body>
<div class="about">
<img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version">v?.?.?</p>
<div class="maintenance-info">
<p class="detail maintainer">
Maintained by
<a onclick="linkInBrowser('website')">Zulip</a>
Maintained by <a onclick="linkInBrowser('website')">Zulip</a>
</p>
<p class="detail license">
Available under the
<a onclick="linkInBrowser('license')">Apache 2.0 License</a>
Available under the <a onclick="linkInBrowser('license')">Apache 2.0 License</a>
</p>
<a class="bug" onclick="linkInBrowser('bug')" href="#">Found bug?</a>
</div>
</div>
<script>
const { app } = require('electron').remote;
const { shell } = require('electron');
const version_tag = document.querySelector('#version');
version_tag.innerHTML = 'v' + app.getVersion();
const { app } = require('electron').remote;
const { shell } = require('electron');
const version_tag = document.querySelector('#version');
version_tag.innerHTML = 'v' + app.getVersion();
function linkInBrowser(type) {
let url;
switch (type) {
case 'website':
url = "https://zulipchat.com";
break;
case 'license':
url = "https://github.com/zulip/zulip-electron/blob/master/LICENSE";
break;
}
shell.openExternal(url);
function linkInBrowser(type) {
let url;
switch (type) {
case 'website':
url = "https://zulipchat.com";
break;
case 'license':
url = "https://github.com/zulip/zulip-electron/blob/master/LICENSE";
break;
default:
url = 'https://github.com/zulip/zulip-electron/issues/new?body=' +
'%3C!--Please%20describe%20your%20issue%20and%20steps%20to%20reproduce%20it.--%3E';
}
shell.openExternal(url);
}
</script>
<script>require('./js/shared/preventdrag.js')</script>
</body>
</body>
</html>

View File

@@ -57,6 +57,22 @@ body {
width: 100%;
}
.maintenance-info .bug {
display: inline-block;
padding: 8px 15px;
margin-top: 30px;
text-decoration: none;
background-color: #52c2af;
color: #fff;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.maintenance-info .bug:hover {
background-color: #32a692;
}
p.detail a {
color: #355f4c;
}

View File

@@ -13,6 +13,9 @@ body {
#content {
display: flex;
height: 100%;
background: #fff url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
}
.toggle-sidebar {
@@ -42,28 +45,6 @@ body {
transition: all 0.6s ease-out;
}
#view-controls-container {
height: calc(100% - 208px);
overflow-y: hidden;
}
#view-controls-container:hover {
overflow-y: overlay;
}
#view-controls-container::-webkit-scrollbar {
width: 4px;
}
#view-controls-container::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
}
#view-controls-container::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid slategrey;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
@@ -151,7 +132,7 @@ body {
}
.tab:first-child {
margin-top: 9px;
margin-top: 8px;
}
.tab {
@@ -165,33 +146,25 @@ body {
}
.tab .server-icons {
width: 35px;
border-radius: 50%;
width: 30px;
padding: 3px;
height: 30px;
vertical-align: top;
border-radius: 4px;
}
.tab .server-tab {
width: 100%;
height: 35px;
position: relative;
margin-top: 5px;
margin: 5px 0 2px 0;
z-index: 11;
line-height: 31px;
color: #eee;
text-align: center;
overflow: hidden;
opacity: 0.6;
padding: 6px 0;
}
.server-tab .alt-icon {
font-family: Verdana;
font-weight: 600;
font-size: 22px;
border: 2px solid #222c31;
margin-left: 17%;
width: 35px;
border-radius: 4px;
padding: 2px 0;
}
.tab .server-tab:hover {
@@ -278,25 +251,6 @@ body {
width: 100%;
}
/*Pseudo element for loading indicator*/
#webviews-container::before {
content: "";
position: absolute;
z-index: 1;
background: #fff url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
width: 100%;
height: 100%;
}
/*When the active webview is loaded*/
#webviews-container.loaded::before {
opacity: 0;
z-index: -1;
visibility: hidden;
}
webview {
/* transition: opacity 0.3s ease-in; */
flex-grow: 1;
@@ -328,7 +282,6 @@ webview.focus {
/* Tooltip styling */
#dnd-tooltip,
#back-tooltip,
#reload-tooltip,
#setting-tooltip {
@@ -346,7 +299,6 @@ webview.focus {
font-size: 14px;
}
#dnd-tooltip:after,
#back-tooltip:after,
#reload-tooltip:after,
#setting-tooltip:after {
@@ -366,7 +318,7 @@ webview.focus {
left: 56px;
padding: 10px 20px;
position: fixed;
margin-top: 11px;
margin-top: 8px;
z-index: 5000 !important;
color: #fff;
border-radius: 4px;
@@ -451,26 +403,3 @@ webview.focus {
opacity: 1;
}
}
send-feedback {
width: 60%;
height: 85%;
}
#feedback-modal {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(68, 67, 67, 0.81);
align-items: center;
justify-content: center;
z-index: 2;
transition: all 1s ease-out;
}
#feedback-modal.show {
display: flex;
}

View File

@@ -7,16 +7,14 @@ body {
font-family: menu, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
color: #333;
background: #efefef;
letter-spacing: -.08px;
line-height: 18px;
color: #8b8e8f;
}
kbd {
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
border-radius: 3px;
font-size: 15px;
font-family: Courier New, Courier, monospace;
font-weight: bold;
@@ -111,7 +109,7 @@ td:nth-child(odd) {
}
.nav {
padding: 7px 0;
padding: 5px 0;
color: #999;
cursor: pointer;
}
@@ -169,16 +167,6 @@ td:nth-child(odd) {
padding: 4px 0 6px 0;
}
.add-server-info-row {
display: flex;
margin: 8px 0 0 0;
}
.add-server-info-right {
flex-grow: 1;
margin-top: 10px;
}
.sub-title {
padding: 4px 0 6px 0;
font-weight: bold;
@@ -188,41 +176,21 @@ td:nth-child(odd) {
img.server-info-icon {
width: 36px;
height: 36px;
padding: 4px 4px 4px 10px;
cursor: pointer;
vertical-align: middle;
padding: 4px;
}
.server-info-left {
margin: 4px 20px 0 0;
min-width: 40%;
margin: 10px 20px 0 0;
}
.server-info-right {
margin-top: 4px;
width: 55%;
display: flex;
align-items: baseline;
justify-content: space-between;
flex-grow: 1;
margin-right: 10px;
}
.server-info-row {
display: inline-block;
margin: 5px 0 0 0;
}
.server-info-left .server-info-row {
display: inline-flex;
align-items: inherit;
vertical-align: -2px;
position: relative;
}
.server-url {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: flex;
margin: 8px 0 0 0;
}
.server-info-alias {
@@ -230,10 +198,6 @@ img.server-info-icon {
cursor: pointer;
}
.server-url-info {
margin-right: 10px;
}
.setting-input-key {
font-size: 14px;
height: 27px;
@@ -247,7 +211,7 @@ img.server-info-icon {
.setting-input-value {
flex-grow: 1;
font-size: 14px;
border-radius: 4px;
border-radius: 3px;
padding: 13px;
border: #ededed 2px solid;
outline-width: 0;
@@ -259,8 +223,8 @@ img.server-info-icon {
border: #4EBFAC 2px solid;
}
.manual-proxy-block {
width: 96%;
.setting-block {
width: 100%;
}
.actions-container {
@@ -287,7 +251,6 @@ img.server-info-icon {
.settings-pane {
flex-grow: 1;
min-width: 550px;
}
.action:hover {
@@ -308,11 +271,12 @@ img.server-info-icon {
padding: 12px 30px;
margin: 10px 0 20px 0;
background: #fff;
width: 80%;
width: 70%;
transition: all 0.2s;
}
.settings-card:hover {
border-left: 8px solid #bcbcbc;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 0px 0px rgba(0, 0, 0, 0.12);
}
@@ -322,45 +286,19 @@ img.server-info-icon {
}
.red {
color: #ef5350;
padding: 8px;
border: rgba(239, 83, 80, 0.5) solid 1px;
border-radius: 4px;
border-color: #fff;
color: #ffffff;
background: #ef5350;
padding: 3px;
padding-right: 10px;
padding-left: 10px;
}
.red:hover {
background-color: rgba(230, 52, 49, 0.5);
color: #fff;
}
.green {
color: #fff;
.blue {
color: #ffffff;
background: #4EBFAC;
border-radius: 4px;
display: inline-block;
border: none;
padding: 10px;
width: 100px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
transition: background-color 0.2s ease;
text-decoration: none;
text-align: center;
}
.green:hover {
background-color: #3c9f8d;
color: #fff;
}
.w-150 {
width: 150px;
}
.w-250 {
width: 250px;
padding: 3px;
padding-right: 10px;
padding-left: 10px;
}
.grey {
@@ -382,19 +320,36 @@ img.server-info-icon {
}
i.open-tab-button {
padding-left: 5px;
font-size: 19px;
padding: 0 5px;
font-size: 18px;
cursor: pointer;
}
.reset-data-button,
.custom-css-button {
display: inline-block;
border: none;
padding: 10px;
width: 120px;
cursor: pointer;
font-size: 13px;
transition: background-color 0.2s ease;
text-decoration: none;
}
.css-delete-action {
margin-bottom: 10px;
}
.selected-css-path,
.download-folder-path {
.reset-data-button:hover {
background-color: #3c9f8d;
color: #fff;
}
.selected-css-path {
background: #eeeeee;
padding: 5px 10px;
padding: 10px;
margin-top: 10px;
margin-right: 10px;
display: flex;
width: 90%;
@@ -406,11 +361,7 @@ i.open-tab-button {
}
#server-info-container {
/* min-height: calc(100% - 260px); */
}
#new-org-button {
margin: 30px 0px 30px 0px;
min-height: calc(100% - 260px);
}
#create-organization-container {
@@ -460,12 +411,9 @@ input.toggle-round+label:after {
}
input.toggle-round+label:before {
right: 2px;
background-color: #f1f1f1;
border-radius: 25px;
top: 0;
right: 0px;
left: 0px;
bottom: 0px;
}
input.toggle-round+label:after {
@@ -477,16 +425,13 @@ input.toggle-round+label:after {
input.toggle-round:checked+label:before {
background-color: #4EBFAC;
top: 0;
right: 0px;
left: 0px;
bottom: 0px;
}
input.toggle-round:checked+label:after {
margin-left: 25px;
}
/* Add new server modal */
.add-server-modal {
@@ -501,9 +446,9 @@ input.toggle-round:checked+label:after {
height: 100%;
/* background: rgba(61, 64, 67, 15); */
background: linear-gradient(35deg, #003b52, #45b59b);
overflow: auto;
}
/* Modal Content */
.modal-container {
@@ -523,14 +468,15 @@ input.toggle-round:checked+label:after {
.divider {
margin-bottom: 30px;
margin-right: 10px;
margin-top: 30px;
color: #7d878a;
}
.divider hr {
margin-left: 8px;
margin-right: 8px;
width: 44%;
margin-left: auto;
margin-right: auto;
width: 45%;
}
.left {
@@ -546,81 +492,31 @@ input.toggle-round:checked+label:after {
text-align: center;
align-items: center;
padding-top: 13px;
margin-left: -5px;
}
.server-center button {
.server-center span {
font-weight: bold;
font-size: 1.1rem;
padding: 10px;
margin: auto;
align-items: center;
text-align: center;
color: #fff;
background: #4EBFAC;
border-color: none;
border: none;
width: 98%;
height: 46px;
border-radius: 4px;
cursor: pointer;
}
.server-center button:hover {
background: #329588;
.server-center .blue {
border-radius: 3px;
}
.server-center button:focus {
background: #329588;
.server-center .blue:hover {
border-right: 2px solid #309085;
border-bottom: 2px solid #309085;
}
.certificates-card {
width: 80%
}
.certificate-input {
width: 100%;
margin-top: 10px;
display: inline-flex;
}
.certificate-input div {
align-self: center;
}
.certificate-input .setting-input-value {
margin-left: 10px;
max-width: 100%;
}
#add-certificate-button {
margin: 10px 10px 10px 37px;
}
.tip {
background: none;
padding: 0;
}
.tip:hover{
box-shadow: none;
}
.md-14 {
font-size: 14px;
vertical-align: middle;
padding-right: 6px;
}
#open-hotkeys-link {
text-decoration: underline;
cursor: pointer;
}
/* responsive grid */
@media (max-width: 650px) {
.selected-css-path,
.download-folder-path {
.selected-css-path {
margin-right: 15px;
}
#css-delete-action {
@@ -637,44 +533,10 @@ input.toggle-round:checked+label:after {
padding: 40px;
min-width: 300px;
}
.server-center button {
margin-right: -12px;
width: 100%;
}
.divider {
margin-right: -8px;
}
.divider hr {
margin-left: 6px;
margin-right: 6px;
width: 43%;
.server-center .blue {
margin-right: 1px;
}
#new-server-container {
padding-left: 0px;
}
}
@media (max-width: 600px) {
.divider {
margin-left: 4%;
}
.divider hr {
margin-left: 2px;
margin-right: 2px;
width: 40%;
}
}
@media (max-width: 900px) {
.settings-card {
flex-direction: column;
align-items: center;
}
.server-info-right {
flex-direction: column;
align-items: center;
}
.action {
margin-top: 10px;
}
}
}

View File

@@ -1,80 +0,0 @@
const { ipcRenderer } = require('electron');
const { shell, app } = require('electron').remote;
const LinkUtil = require('../utils/link-util');
const DomainUtil = require('../utils/domain-util');
const ConfigUtil = require('../utils/config-util');
const dingSound = new Audio('../resources/sounds/ding.ogg');
function handleExternalLink(event) {
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
const downloadPath = ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`);
const shouldShowInFolder = ConfigUtil.getConfigItem('showDownloadFolder', false);
// Whitelist URLs which are allowed to be opened in the app
const {
isInternalUrl: isWhiteListURL,
isUploadsUrl: isUploadsURL
} = LinkUtil.isInternal(domainPrefix, url);
if (isWhiteListURL) {
event.preventDefault();
// Code to show pdf in a new BrowserWindow (currently commented out due to bug-upstream)
// Show pdf attachments in a new window
// if (LinkUtil.isPDF(url) && isUploadsURL) {
// ipcRenderer.send('pdf-view', url);
// return;
// }
// download txt, mp3, mp4 etc.. by using downloadURL in the
// main process which allows the user to save the files to their desktop
// and not trigger webview reload while image in webview will
// do nothing and will not save it
// Code to show pdf in a new BrowserWindow (currently commented out due to bug-upstream)
// if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
if (!LinkUtil.isImage(url) && isUploadsURL) {
ipcRenderer.send('downloadFile', url, downloadPath);
ipcRenderer.once('downloadFileCompleted', (event, filePath, fileName) => {
const downloadNotification = new Notification('Download Complete', {
body: shouldShowInFolder ? `Click to show ${fileName} in folder` : `Click to open ${fileName}`,
silent: true // We'll play our own sound - ding.ogg
});
// Play sound to indicate download complete
if (!ConfigUtil.getConfigItem('silent')) {
dingSound.play();
}
downloadNotification.onclick = () => {
if (shouldShowInFolder) {
// Reveal file in download folder
shell.showItemInFolder(filePath);
} else {
// Open file in the default native app
shell.openItem(filePath);
}
};
ipcRenderer.removeAllListeners('downloadFileFailed');
});
ipcRenderer.once('downloadFileFailed', () => {
// Automatic download failed, so show save dialog prompt and download
// through webview
this.$el.downloadURL(url);
ipcRenderer.removeAllListeners('downloadFileCompleted');
});
return;
}
// open internal urls inside the current webview.
this.$el.loadURL(url);
} else {
event.preventDefault();
shell.openExternal(url);
}
}
module.exports = handleExternalLink;

View File

@@ -8,7 +8,7 @@ const {ipcRenderer} = require('electron');
class ServerTab extends Tab {
template() {
return `<div class="tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tooltip" style="display:none">${this.props.name}</div>
<div class="server-tooltip" style="display:none"></div>
<div class="server-tab-badge"></div>
<div class="server-tab">
<img class="server-icons" src='${this.props.icon}'/>

View File

@@ -3,12 +3,13 @@
const path = require('path');
const fs = require('fs');
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
const ConfigUtil = require(__dirname + '/../utils/config-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const { app, dialog } = require('electron').remote;
const LinkUtil = require(__dirname + '/../utils/link-util.js');
const { shell, app, dialog } = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js');
const handleExternalLink = require(__dirname + '/../components/handle-external-link.js');
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
class WebView extends BaseComponent {
@@ -18,10 +19,9 @@ class WebView extends BaseComponent {
this.props = props;
this.zoomFactor = 1.0;
this.loading = true;
this.loading = false;
this.badgeCount = 0;
this.customCSS = ConfigUtil.getConfigItem('customCSS');
this.$webviewsContainer = document.querySelector('#webviews-container').classList;
}
template() {
@@ -33,7 +33,6 @@ class WebView extends BaseComponent {
disablewebsecurity
${this.props.preload ? 'preload="js/preload.js"' : ''}
partition="persist:webviewsession"
name="${this.props.name}"
webpreferences="allowRunningInsecureContent, javascript=yes">
</webview>`;
}
@@ -47,7 +46,16 @@ class WebView extends BaseComponent {
registerListeners() {
this.$el.addEventListener('new-window', event => {
handleExternalLink.call(this, event);
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) {
event.preventDefault();
this.$el.loadURL(url);
} else {
event.preventDefault();
shell.openExternal(url);
}
});
if (shouldSilentWebview) {
@@ -76,16 +84,11 @@ class WebView extends BaseComponent {
this.$el.addEventListener('page-favicon-updated', event => {
const { favicons } = event;
// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like
// https://chat.zulip.org/static/images/favicon/favicon-pms.png
if (favicons[0].indexOf('favicon-pms') > 0 && process.platform === 'darwin') {
// This api is only supported on macOS
app.dock.setBadge('●');
// bounce the dock
if (ConfigUtil.getConfigItem('dockBouncing')) {
app.dock.bounce();
}
}
});
@@ -93,7 +96,6 @@ class WebView extends BaseComponent {
if (this.props.role === 'server') {
this.$el.classList.add('onload');
}
this.loading = false;
this.show();
});
@@ -127,13 +129,6 @@ class WebView extends BaseComponent {
return;
}
// To show or hide the loading indicator in the the active tab
if (this.loading) {
this.$webviewsContainer.remove('loaded');
} else {
this.$webviewsContainer.add('loaded');
}
this.$el.classList.remove('disabled');
this.$el.classList.add('active');
setTimeout(() => {
@@ -142,6 +137,7 @@ class WebView extends BaseComponent {
}
}, 1000);
this.focus();
this.loading = false;
this.props.onTitleChange();
// Injecting preload css in webview to override some css rules
this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8'));
@@ -234,9 +230,6 @@ class WebView extends BaseComponent {
reload() {
this.hide();
// Shows the loading indicator till the webview is reloaded
this.$webviewsContainer.remove('loaded');
this.loading = true;
this.$el.reload();
}

View File

@@ -1,40 +0,0 @@
const events = require('events');
const { ipcRenderer } = require('electron');
// we have and will have some non camelcase stuff
// while working with zulip so just turning the rule off
// for the whole file.
/* eslint-disable camelcase */
class ElectronBridge extends events {
send_event(...args) {
this.emit(...args);
}
on_event(...args) {
this.on(...args);
}
}
const electron_bridge = new ElectronBridge();
electron_bridge.on('total_unread_count', (...args) => {
ipcRenderer.send('unread-count', ...args);
});
electron_bridge.on('realm_name', realmName => {
const serverURL = location.origin;
ipcRenderer.send('realm-name-changed', serverURL, realmName);
});
electron_bridge.on('realm_icon_url', iconURL => {
const serverURL = location.origin;
iconURL = iconURL.includes('http') ? iconURL : `${serverURL}${iconURL}`;
ipcRenderer.send('realm-icon-changed', serverURL, iconURL);
});
// this follows node's idiomatic implementation of event
// emitters to make event handling more simpler instead of using
// functions zulip side will emit event using ElectronBrigde.send_event
// which is alias of .emit and on this side we can handle the data by adding
// a listener for the event.
module.exports = electron_bridge;

View File

@@ -1,62 +0,0 @@
const { app } = require('electron').remote;
const path = require('path');
const fs = require('fs');
const SendFeedback = require('@electron-elements/send-feedback');
// make the button color match zulip app's theme
SendFeedback.customStyles = `
button:hover, button:focus {
border-color: #4EBFAC;
color: #4EBFAC;
}
button:active {
background-color: #f1f1f1;
color: #4EBFAC;
}
button {
background-color: #4EBFAC;
border-color: #4EBFAC;
}
`;
customElements.define('send-feedback', SendFeedback);
const sendFeedback = document.querySelector('send-feedback');
const feedbackHolder = sendFeedback.parentElement;
// customize the fields of custom elements
sendFeedback.title = 'Report Issue';
sendFeedback.titleLabel = 'Issue title:';
sendFeedback.titlePlaceholder = 'Enter issue title';
sendFeedback.textareaLabel = 'Describe the issue:';
sendFeedback.textareaPlaceholder = 'Succinctly describe your issue and steps to reproduce it...';
sendFeedback.buttonLabel = 'Report Issue';
sendFeedback.loaderSuccessText = '';
sendFeedback.useReporter('emailReporter', {
email: 'akash@zulipchat.com'
});
feedbackHolder.addEventListener('click', e => {
// only remove the class if the grey out faded
// part is clicked and not the feedback element itself
if (e.target === e.currentTarget) {
feedbackHolder.classList.remove('show');
}
});
sendFeedback.addEventListener('feedback-submitted', () => {
setTimeout(() => {
feedbackHolder.classList.remove('show');
}, 1000);
});
const dataDir = app.getPath('userData');
const logsDir = path.join(dataDir, '/Logs');
sendFeedback.logs.push(...fs.readdirSync(logsDir).map(file => path.join(logsDir, file)));
module.exports = {
feedbackHolder,
sendFeedback
};

View File

@@ -3,8 +3,7 @@
const { ipcRenderer, remote } = require('electron');
const isDev = require('electron-is-dev');
const { session, app, Menu, dialog } = remote;
const escape = require('escape-html');
const { session } = remote;
require(__dirname + '/js/tray.js');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
@@ -12,17 +11,7 @@ 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');
const DNDUtil = require(__dirname + '/js/utils/dnd-util.js');
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const Logger = require(__dirname + '/js/utils/logger-util.js');
const CommonUtil = require(__dirname + '/js/utils/common-util.js');
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class ServerManagerView {
constructor() {
@@ -34,14 +23,12 @@ class ServerManagerView {
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$webviewsContainer = document.getElementById('webviews-container');
this.$backButton = $actionsContainer.querySelector('#back-action');
this.$dndButton = $actionsContainer.querySelector('#dnd-action');
this.$addServerTooltip = document.getElementById('add-server-tooltip');
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip');
this.$backTooltip = $actionsContainer.querySelector('#back-tooltip');
this.$dndTooltip = $actionsContainer.querySelector('#dnd-tooltip');
this.$sidebar = document.getElementById('sidebar');
@@ -67,17 +54,7 @@ class ServerManagerView {
loadProxy() {
return new Promise(resolve => {
// To change proxyEnable to useManualProxy in older versions
const proxyEnabledOld = ConfigUtil.isConfigItemExists('useProxy');
if (proxyEnabledOld) {
const proxyEnableOldState = ConfigUtil.getConfigItem('useProxy');
if (proxyEnableOldState) {
ConfigUtil.setConfigItem('useManualProxy', true);
}
ConfigUtil.removeConfigItem('useProxy');
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false);
if (proxyEnabled) {
session.fromPartition('persist:webviewsession').setProxy({
pacScript: ConfigUtil.getConfigItem('proxyPAC', ''),
@@ -101,26 +78,16 @@ class ServerManagerView {
// Default settings which should be respected
const settingOptions = {
trayIcon: true,
useManualProxy: false,
useSystemProxy: false,
useProxy: false,
showSidebar: true,
badgeOption: true,
startAtLogin: false,
startMinimized: false,
enableSpellchecker: true,
showNotification: true,
autoUpdate: true,
betaUpdate: false,
customCSS: false,
silent: false,
lastActiveTab: 0,
dnd: false,
dndPreviousSettings: {
showNotification: true,
silent: false
},
downloadsPath: `${app.getPath('downloads')}`,
showDownloadFolder: false
lastActiveTab: 0
};
// Platform specific settings
@@ -128,16 +95,6 @@ class ServerManagerView {
if (process.platform === 'win32') {
// Only available on Windows
settingOptions.flashTaskbarOnMessage = true;
settingOptions.dndPreviousSettings.flashTaskbarOnMessage = true;
}
if (process.platform === 'darwin') {
// Only available on macOS
settingOptions.dockBouncing = true;
}
if (process.platform !== 'darwin') {
settingOptions.autoHideMenubar = false;
}
for (const i in settingOptions) {
@@ -174,19 +131,18 @@ class ServerManagerView {
this.tabs.push(new ServerTab({
role: 'server',
icon: server.icon,
name: server.alias,
$root: this.$tabsContainer,
onClick: this.activateLastTab.bind(this, index),
index,
tabIndex,
onHover: this.onHover.bind(this, index),
onHover: this.onHover.bind(this, index, server.alias),
onHoverOut: this.onHoverOut.bind(this, index),
webview: new WebView({
$root: this.$webviewsContainer,
index,
tabIndex,
url: server.url,
name: CommonUtil.decodeString(server.alias),
name: server.alias,
isActive: () => {
return index === this.activeTabIndex;
},
@@ -199,29 +155,6 @@ class ServerManagerView {
}
initActions() {
this.initDNDButton();
this.initServerActions();
this.initLeftSidebarEvents();
}
initServerActions() {
const $serverImgs = document.querySelectorAll('.server-icons');
$serverImgs.forEach(($serverImg, index) => {
this.addContextMenu($serverImg, index);
if ($serverImg.src.includes('img/icon.png')) {
this.displayInitialCharLogo($serverImg, index);
}
$serverImg.addEventListener('error', () => {
this.displayInitialCharLogo($serverImg, index);
});
});
}
initLeftSidebarEvents() {
this.$dndButton.addEventListener('click', () => {
const dndUtil = DNDUtil.toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
});
this.$reloadButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.reload();
});
@@ -235,16 +168,17 @@ class ServerManagerView {
this.tabs[this.activeTabIndex].webview.back();
});
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip, true);
const $serverImgs = document.querySelectorAll('.server-icons');
$serverImgs.forEach($serverImg => {
$serverImg.addEventListener('error', () => {
$serverImg.src = 'img/icon.png';
});
});
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip);
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
}
initDNDButton() {
const dnd = ConfigUtil.getConfigItem('dnd', false);
this.toggleDNDButton(dnd);
}
getTabIndex() {
@@ -253,60 +187,18 @@ class ServerManagerView {
return currentIndex;
}
displayInitialCharLogo($img, index) {
/*
index parameter needed because webview[data-tab-id] can increment
beyond size of sidebar org array and throw error
*/
const $altIcon = document.createElement('div');
const $parent = $img.parentElement;
const $container = $parent.parentElement;
const webviewId = $container.dataset.tabId;
const $webview = document.querySelector(`webview[data-tab-id="${webviewId}"]`);
const realmName = $webview.getAttribute('name');
if (realmName === null) {
$img.src = '/img/icon.png';
return;
}
$altIcon.textContent = realmName.charAt(0) || 'Z';
$altIcon.classList.add('server-icon');
$altIcon.classList.add('alt-icon');
$parent.removeChild($img);
$parent.appendChild($altIcon);
this.addContextMenu($altIcon, index);
}
sidebarHoverEvent(SidebarButton, SidebarTooltip, addServer = false) {
sidebarHoverEvent(SidebarButton, SidebarTooltip) {
SidebarButton.addEventListener('mouseover', () => {
SidebarTooltip.removeAttribute('style');
// To handle position of add server tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
// This needs to handled only for the add server tooltip and not others.
if (addServer) {
const { top } = SidebarButton.getBoundingClientRect();
SidebarTooltip.style.top = top + 'px';
}
});
SidebarButton.addEventListener('mouseout', () => {
SidebarTooltip.style.display = 'none';
});
}
onHover(index) {
// this.$serverIconTooltip[index].innerHTML already has realm name, so we are just
// removing the style.
onHover(index, serverName) {
this.$serverIconTooltip[index].innerHTML = serverName;
this.$serverIconTooltip[index].removeAttribute('style');
// To handle position of servers' tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
const { top } = this.$serverIconTooltip[index].parentElement.getBoundingClientRect();
this.$serverIconTooltip[index].style.top = top + 'px';
}
onHoverOut(index) {
@@ -322,7 +214,6 @@ class ServerManagerView {
this.functionalTabs[tabProps.name] = this.tabs.length;
const tabIndex = this.getTabIndex();
this.tabs.push(new FunctionalTab({
role: 'function',
materialIcon: tabProps.materialIcon,
@@ -347,11 +238,6 @@ class ServerManagerView {
preload: false
})
}));
// To show loading indicator the first time a functional tab is opened, indicator is
// closed when the functional tab DOM is ready, handled in webview.js
this.$webviewsContainer.classList.remove('loaded');
this.activateTab(this.functionalTabs[tabProps.name]);
}
@@ -382,29 +268,10 @@ class ServerManagerView {
}
activateLastTab(index) {
// Open all the tabs in background, also activate the tab based on the index
this.activateTab(index);
// Save last active tab
// Open last active tab
ConfigUtil.setConfigItem('lastActiveTab', index);
}
// returns this.tabs in an way that does
// not crash app when this.tabs is passed into
// ipcRenderer. Something about webview, and props.webview
// properties in ServerTab causes the app to crash.
get tabsForIpc() {
const tabs = [];
this.tabs.forEach(tab => {
const proto = Object.create(Object.getPrototypeOf(tab));
const tabClone = Object.assign(proto, tab);
tabClone.webview = { props: {} };
tabClone.webview.props.name = tab.webview.props.name;
delete tabClone.props.webview;
tabs.push(tabClone);
});
return tabs;
// Open all the tabs in background
this.activateTab(index);
}
activateTab(index, hideOldTab = true) {
@@ -433,12 +300,29 @@ class ServerManagerView {
this.tabs[index].activate();
ipcRenderer.send('update-menu', {
// JSON stringify this.tabs to avoid a crash
// util.inspect is being used to handle circular references
tabs: this.tabsForIpc,
activeTabIndex: this.activeTabIndex,
// Following flag controls whether a menu item should be enabled or not
enableMenu: this.tabs[index].props.role === 'server'
tabs: this.tabs,
activeTabIndex: this.activeTabIndex
});
ipcRenderer.on('toggle-sidebar', (event, state) => {
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-sidebar', state);
});
ipcRenderer.on('toogle-silent', (event, state) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
try {
webview.setAudioMuted(state);
} catch (err) {
// webview is not ready yet
webview.addEventListener('dom-ready', () => {
webview.isAudioMuted();
});
}
});
});
}
@@ -459,9 +343,6 @@ class ServerManagerView {
}
destroyView() {
// Show loading indicator
this.$webviewsContainer.classList.remove('loaded');
// Clear global variables
this.activeTabIndex = -1;
this.tabs = [];
@@ -480,7 +361,6 @@ class ServerManagerView {
// Destroy the current view and re-initiate it
this.destroyView();
this.initTabs();
this.initServerActions();
}
// This will trigger when pressed CTRL/CMD + R [WIP]
@@ -510,38 +390,6 @@ class ServerManagerView {
}
}
// Toggles the dnd button icon.
toggleDNDButton(alert) {
this.$dndTooltip.textContent = (alert ? 'Turn Off' : 'Enable') + ' Do Not Disturb';
this.$dndButton.querySelector('i').textContent = alert ? 'notifications_off' : 'notifications';
}
addContextMenu($serverImg, index) {
$serverImg.addEventListener('contextmenu', e => {
e.preventDefault();
const template = [
{
label: 'Disconnect organization',
click: () => {
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure you want to disconnect this organization?'
}, response => {
if (response === 0) {
DomainUtil.removeDomain(index);
ipcRenderer.send('reload-full-app');
}
});
}
}
];
const contextMenu = Menu.buildFromTemplate(template);
contextMenu.popup({ window: remote.getCurrentWindow() });
});
}
registerIpcs() {
const webviewListeners = {
'webview-reload': 'reload',
@@ -587,88 +435,16 @@ class ServerManagerView {
this.activateLastTab(index);
});
ipcRenderer.on('open-org-tab', () => {
this.openSettings('AddServer');
});
ipcRenderer.on('reload-proxy', (event, showAlert) => {
this.loadProxy().then(() => {
if (showAlert) {
alert('Proxy settings saved!');
ipcRenderer.send('reload-full-app');
}
});
});
ipcRenderer.on('toggle-sidebar', (event, show) => {
// Toggle the left sidebar
this.toggleSidebar(show);
// Toggle sidebar switch in the general settings
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-sidebar-setting', show);
});
ipcRenderer.on('toggle-silent', (event, state) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
try {
webview.setAudioMuted(state);
} catch (err) {
// webview is not ready yet
webview.addEventListener('dom-ready', () => {
webview.setAudioMuted(state);
});
}
});
});
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.toggleDNDButton(state);
ipcRenderer.send('forward-message', 'toggle-silent', newSettings.silent);
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-dnd', state, newSettings);
});
ipcRenderer.on('update-realm-name', (event, serverURL, realmName) => {
DomainUtil.getDomains().forEach((domain, index) => {
if (domain.url.includes(serverURL)) {
const serverTooltipSelector = `.tab .server-tooltip`;
const serverTooltips = document.querySelectorAll(serverTooltipSelector);
serverTooltips[index].innerHTML = escape(realmName);
this.tabs[index].props.name = escape(realmName);
this.tabs[index].webview.props.name = realmName;
domain.alias = escape(realmName);
DomainUtil.db.push(`/domains[${index}]`, domain, true);
DomainUtil.reloadDB();
// Update the realm name also on the Window menu
ipcRenderer.send('update-menu', {
tabs: this.tabsForIpc,
activeTabIndex: this.activeTabIndex
});
}
});
});
ipcRenderer.on('update-realm-icon', (event, serverURL, iconURL) => {
DomainUtil.getDomains().forEach((domain, index) => {
if (domain.url.includes(serverURL)) {
DomainUtil.saveServerIcon(iconURL).then(localIconUrl => {
const serverImgsSelector = `.tab .server-icons`;
const serverImgs = document.querySelectorAll(serverImgsSelector);
serverImgs[index].src = localIconUrl;
domain.icon = localIconUrl;
DomainUtil.db.push(`/domains[${index}]`, domain, true);
DomainUtil.reloadDB();
});
}
});
});
ipcRenderer.on('enter-fullscreen', () => {
@@ -720,10 +496,6 @@ class ServerManagerView {
}
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
});
ipcRenderer.on('open-feedback-modal', () => {
feedbackHolder.classList.add('show');
});
}
}
@@ -737,7 +509,7 @@ window.onload = () => {
window.addEventListener('offline', () => {
reconnectUtil.clearState();
logger.log('No internet connection, you are offline.');
console.log('No internet connection, you are offline.');
});
// only start electron-connect (auto reload on change) when its ran

View File

@@ -1,10 +1,4 @@
const { remote } = require('electron');
const Logger = require('../utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
// Do not change this
const appId = 'org.zulip.zulip-electron';
@@ -34,8 +28,8 @@ function loadBots(sync = false) {
botsListLoaded = true;
})
.fail(error => {
logger.log('Load bots request failed: ', error.responseText);
logger.log('Load bots request status: ', error.statusText);
console.log('Request failed: ', error.responseText);
console.log('Request status: ', error.statusText);
});
if (sync) {
$.ajaxSetup({async: true});
@@ -141,8 +135,7 @@ function parseReply(reply) {
function setupReply(id) {
const { narrow } = window;
const narrowByTopic = narrow.by_topic || narrow.by_subject;
narrowByTopic(id, { trigger: 'notification' });
narrow.by_subject(id, { trigger: 'notification' });
}
module.exports = {

View File

@@ -4,7 +4,6 @@ const {
remote: { app }
} = require('electron');
const params = require('../utils/params-util.js');
const DefaultNotification = require('./default-notification');
const { appId, loadBots } = require('./helpers');
@@ -20,8 +19,9 @@ if (process.platform === 'darwin') {
}
window.addEventListener('load', () => {
// Call this function only when user is logged in
// eslint-disable-next-line no-undef, camelcase
if (params.isPageParams() && page_params.realm_uri) {
if (page_params.realm_uri) {
loadBots();
}
});

View File

@@ -1,92 +0,0 @@
'use-strict';
const { dialog } = require('electron').remote;
const path = require('path');
const BaseComponent = require(__dirname + '/../../components/base.js');
const CertificateUtil = require(__dirname + '/../../utils/certificate-util.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class AddCertificate extends BaseComponent {
constructor(props) {
super();
this.props = props;
this._certFile = '';
}
template() {
return `
<div class="settings-card certificates-card">
<div class="certificate-input">
<div>Organization URL</div>
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="certificate-input">
<div>Certificate file</div>
<button class="green" id="add-certificate-button">Upload</button>
</div>
</div>
`;
}
init() {
this.$addCertificate = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$addCertificate);
this.addCertificateButton = this.$addCertificate.querySelector('#add-certificate-button');
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0];
this.initListeners();
}
validateAndAdd() {
const certificate = this._certFile;
const serverUrl = this.serverUrl.value;
if (certificate !== '' && serverUrl !== '') {
const server = encodeURIComponent(DomainUtil.formatUrl(serverUrl));
const fileName = path.basename(certificate);
const copy = CertificateUtil.copyCertificate(server, certificate, fileName);
if (!copy) {
return;
}
CertificateUtil.setCertificate(server, fileName);
dialog.showMessageBox({
title: 'Success',
message: `Certificate saved!`
});
this.serverUrl.value = '';
} else {
dialog.showErrorBox('Error', `Please, ${serverUrl === '' ?
'Enter an Organization URL' : 'Choose certificate file'}`);
}
}
addHandler() {
const showDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'crt, pem', extensions: ['crt', 'pem'] }]
};
dialog.showOpenDialog(showDialogOptions, selectedFile => {
if (selectedFile) {
this._certFile = selectedFile[0] || '';
this.validateAndAdd();
}
});
}
initListeners() {
this.addCertificateButton.addEventListener('click', () => {
this.addHandler();
});
this.serverUrl.addEventListener('keypress', event => {
const EnterkeyCode = event.keyCode;
if (EnterkeyCode === 13) {
this.addHandler();
}
});
}
}
module.exports = AddCertificate;

View File

@@ -3,7 +3,6 @@
const BaseSection = require(__dirname + '/base-section.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js');
const AddCertificate = require(__dirname + '/add-certificate.js');
class ConnectedOrgSection extends BaseSection {
constructor(props) {
@@ -17,9 +16,6 @@ class ConnectedOrgSection extends BaseSection {
<div class="page-title">Connected organizations</div>
<div class="title" id="existing-servers">All the connected orgnizations will appear here.</div>
<div id="server-info-container"></div>
<div id="new-org-button"><button class="green sea w-250">Connect to another organization</button></div>
<div class="page-title">Add Custom Certificates</div>
<div id="add-certificate-container"></div>
</div>
`;
}
@@ -33,11 +29,8 @@ class ConnectedOrgSection extends BaseSection {
const servers = DomainUtil.getDomains();
this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.getElementById('server-info-container');
this.$existingServers = document.getElementById('existing-servers');
this.$newOrgButton = document.getElementById('new-org-button');
this.$addCertificateContainer = document.getElementById('add-certificate-container');
const noServerText = 'All the connected orgnizations will appear here';
// Show noServerText if no servers are there otherwise hide it
@@ -51,20 +44,6 @@ class ConnectedOrgSection extends BaseSection {
onChange: this.reloadApp
}).init();
}
this.$newOrgButton.addEventListener('click', () => {
// We don't need to import this since it's already imported in other files
// eslint-disable-next-line no-undef
ipcRenderer.send('forward-message', 'open-org-tab');
});
this.initAddCertificate();
}
initAddCertificate() {
new AddCertificate({
$root: this.$addCertificateContainer
}).init();
}
}

View File

@@ -23,28 +23,20 @@ class GeneralSection extends BaseSection {
<div class="setting-description">Show app icon in system tray</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="menubar-option" style= "display:${process.platform === 'darwin' ? 'none' : ''}">
<div class="setting-description">Auto hide Menubar (Press Alt key to display)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="sidebar-option">
<div class="setting-description">Show sidebar (<span class="code">${process.platform === 'darwin' ? 'Cmd+Shift+S' : 'Ctrl+Shift+S'}</span>)</div>
<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+S</span>)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="badge-option">
<div class="setting-description">Show app unread badge</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="dock-bounce-option" style= "display:${process.platform === 'darwin' ? '' : 'none'}">
<div class="setting-description">Bounce dock on new private message</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="flash-taskbar-option" style= "display:${process.platform === 'win32' ? '' : 'none'}">
<div class="setting-description">Flash taskbar on new message</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Desktop Notifications</div>
<div class="title">Desktop Notification</div>
<div class="settings-card">
<div class="setting-row" id="show-notification-option">
<div class="setting-description">Show Desktop Notifications</div>
@@ -56,11 +48,7 @@ class GeneralSection extends BaseSection {
</div>
</div>
<div class="title">App Updates</div>
<div class="settings-card">
<div class="setting-row" id="autoupdate-option">
<div class="setting-description">Enable auto updates</div>
<div class="setting-control"></div>
</div>
<div class="settings-card">
<div class="setting-row" id="betaupdate-option">
<div class="setting-description">Get beta updates</div>
<div class="setting-control"></div>
@@ -77,50 +65,34 @@ class GeneralSection extends BaseSection {
<div class="setting-control"></div>
</div>
<div class="setting-row" id="enable-spellchecker-option">
<div class="setting-description">Enable spellchecker (requires restart)</div>
<div class="setting-description">Enable Spellchecker (requires restart)</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Advanced</div>
<div class="title">Add custom CSS</div>
<div class="settings-card">
<div class="setting-row" id="show-download-folder">
<div class="setting-description">Show downloaded files in file manager</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="add-custom-css">
<div class="setting-description">
Add custom CSS
</div>
<button class="custom-css-button green">Upload</button>
</div>
<div class="setting-row" id="remove-custom-css">
<div class="setting-description">
<div class="selected-css-path" id="custom-css-path">${ConfigUtil.getConfigItem('customCSS')}</div>
</div>
<div class="action red" id="css-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
<div class="setting-row" id="download-folder">
<div class="setting-row" id="add-custom-css">
<div class="setting-description">
Default download location
This will inject the selected css stylesheet in all the added accounts
</div>
<button class="download-folder-button green">Change</button>
<button class="custom-css-button blue">Add</button>
</div>
<div class="setting-row">
<div class="setting-row" id="remove-custom-css">
<div class="setting-description">
<div class="download-folder-path">${ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`)}</div>
<div class="selected-css-path" id="custom-css-path">${ConfigUtil.getConfigItem('customCSS')}</div>
</div>
<div class="action red" id="css-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
<div class="title">Reset Application Data</div>
<div class="settings-card">
<div class="setting-row" id="resetdata-option">
<div class="setting-description">This will delete all application data including all added accounts and preferences
</div>
<button class="reset-data-button green w-150">Reset App Data</button>
<button class="reset-data-button blue">Reset App Data</button>
</div>
</div>
</div>
@@ -132,8 +104,7 @@ class GeneralSection extends BaseSection {
this.updateTrayOption();
this.updateBadgeOption();
this.updateSilentOption();
this.autoUpdateOption();
this.betaUpdateOption();
this.updateUpdateOption();
this.updateSidebarOption();
this.updateStartAtLoginOption();
this.updateResetDataOption();
@@ -143,24 +114,12 @@ class GeneralSection extends BaseSection {
this.addCustomCSS();
this.showCustomCSSPath();
this.removeCustomCSS();
this.downloadFolder();
this.showDownloadFolder();
// Platform specific settings
// Flashing taskbar on Windows
if (process.platform === 'win32') {
this.updateFlashTaskbar();
}
// Dock bounce on macOS
if (process.platform === 'darwin') {
this.updateDockBouncing();
}
// Auto hide menubar on Windows and Linux
if (process.platform !== 'darwin') {
this.updateMenubarOption();
}
}
updateTrayOption() {
@@ -176,19 +135,6 @@ class GeneralSection extends BaseSection {
});
}
updateMenubarOption() {
this.generateSettingOption({
$element: document.querySelector('#menubar-option .setting-control'),
value: ConfigUtil.getConfigItem('autoHideMenubar', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('autoHideMenubar');
ConfigUtil.setConfigItem('autoHideMenubar', newValue);
ipcRenderer.send('toggle-menubar', newValue);
this.updateMenubarOption();
}
});
}
updateBadgeOption() {
this.generateSettingOption({
$element: document.querySelector('#badge-option .setting-control'),
@@ -202,18 +148,6 @@ class GeneralSection extends BaseSection {
});
}
updateDockBouncing() {
this.generateSettingOption({
$element: document.querySelector('#dock-bounce-option .setting-control'),
value: ConfigUtil.getConfigItem('dockBouncing', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('dockBouncing');
ConfigUtil.setConfigItem('dockBouncing', newValue);
this.updateDockBouncing();
}
});
}
updateFlashTaskbar() {
this.generateSettingOption({
$element: document.querySelector('#flash-taskbar-option .setting-control'),
@@ -226,32 +160,14 @@ class GeneralSection extends BaseSection {
});
}
autoUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#autoupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('autoUpdate', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('autoUpdate');
ConfigUtil.setConfigItem('autoUpdate', newValue);
if (!newValue) {
ConfigUtil.setConfigItem('betaUpdate', false);
this.betaUpdateOption();
}
this.autoUpdateOption();
}
});
}
betaUpdateOption() {
updateUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#betaupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('betaUpdate', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
if (ConfigUtil.getConfigItem('autoUpdate')) {
ConfigUtil.setConfigItem('betaUpdate', newValue);
this.betaUpdateOption();
}
ConfigUtil.setConfigItem('betaUpdate', newValue);
this.updateUpdateOption();
}
});
}
@@ -264,7 +180,7 @@ class GeneralSection extends BaseSection {
const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption();
currentBrowserWindow.send('toggle-silent', newValue);
currentBrowserWindow.send('toogle-silent', newValue);
}
});
}
@@ -394,40 +310,6 @@ class GeneralSection extends BaseSection {
});
}
downloadFolderDialog() {
const showDialogOptions = {
title: 'Select Download Location',
defaultId: 1,
properties: ['openDirectory']
};
dialog.showOpenDialog(showDialogOptions, selectedFolder => {
if (selectedFolder) {
ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
const downloadFolderPath = document.querySelector('.download-folder-path');
downloadFolderPath.innerText = selectedFolder[0];
}
});
}
downloadFolder() {
const downloadFolder = document.querySelector('#download-folder .download-folder-button');
downloadFolder.addEventListener('click', () => {
this.downloadFolderDialog();
});
}
showDownloadFolder() {
this.generateSettingOption({
$element: document.querySelector('#show-download-folder .setting-control'),
value: ConfigUtil.getConfigItem('showDownloadFolder', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('showDownloadFolder');
ConfigUtil.setConfigItem('showDownloadFolder', newValue);
this.showDownloadFolder();
}
});
}
}
module.exports = GeneralSection;

View File

@@ -16,15 +16,11 @@ class NetworkSection extends BaseSection {
<div class="settings-pane">
<div class="title">Proxy</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="use-system-settings">
<div class="setting-description">Use system proxy settings (requires restart)</div>
<div class="setting-row" id="use-proxy-option">
<div class="setting-description">Connect servers through a proxy</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="use-manual-settings">
<div class="setting-description">Manual proxy configuration</div>
<div class="setting-control"></div>
</div>
<div class="manual-proxy-block">
<div class="setting-block">
<div class="setting-row" id="proxy-pac-option">
<span class="setting-input-key">PAC script</span>
<input class="setting-input-value" placeholder="e.g. foobar.com/pacfile.js"/>
@@ -38,7 +34,8 @@ class NetworkSection extends BaseSection {
<input class="setting-input-value" placeholder="e.g. foobar.com"/>
</div>
<div class="setting-row">
<div class="action green" id="proxy-save-action">
<div class="action blue" id="proxy-save-action">
<i class="material-icons">check_box</i>
<span>Save</span>
</div>
</div>
@@ -54,7 +51,7 @@ class NetworkSection extends BaseSection {
this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
this.$proxySaveAction = document.getElementById('proxy-save-action');
this.$manualProxyBlock = this.props.$root.querySelector('.manual-proxy-block');
this.$settingBlock = this.props.$root.querySelector('.setting-block');
this.initProxyOption();
this.$proxyPAC.value = ConfigUtil.getConfigItem('proxyPAC', '');
@@ -71,54 +68,31 @@ class NetworkSection extends BaseSection {
}
initProxyOption() {
const manualProxyEnabled = ConfigUtil.getConfigItem('useManualProxy', false);
this.toggleManualProxySettings(manualProxyEnabled);
const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false);
this.toggleProxySettings(proxyEnabled);
this.updateProxyOption();
}
toggleManualProxySettings(option) {
toggleProxySettings(option) {
if (option) {
this.$manualProxyBlock.classList.remove('hidden');
this.$settingBlock.classList.remove('hidden');
} else {
this.$manualProxyBlock.classList.add('hidden');
this.$settingBlock.classList.add('hidden');
}
}
updateProxyOption() {
this.generateSettingOption({
$element: document.querySelector('#use-system-settings .setting-control'),
value: ConfigUtil.getConfigItem('useSystemProxy', false),
$element: document.querySelector('#use-proxy-option .setting-control'),
value: ConfigUtil.getConfigItem('useProxy', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('useSystemProxy');
const manualProxyValue = ConfigUtil.getConfigItem('useManualProxy');
if (manualProxyValue && newValue) {
ConfigUtil.setConfigItem('useManualProxy', !manualProxyValue);
this.toggleManualProxySettings(!manualProxyValue);
}
const newValue = !ConfigUtil.getConfigItem('useProxy');
ConfigUtil.setConfigItem('useProxy', newValue);
this.toggleProxySettings(newValue);
if (newValue === false) {
// Remove proxy system proxy settings
ConfigUtil.setConfigItem('proxyRules', '');
// Reload proxy if the proxy is turned off
ipcRenderer.send('forward-message', 'reload-proxy', false);
}
ConfigUtil.setConfigItem('useSystemProxy', newValue);
this.updateProxyOption();
}
});
this.generateSettingOption({
$element: document.querySelector('#use-manual-settings .setting-control'),
value: ConfigUtil.getConfigItem('useManualProxy', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('useManualProxy');
const systemProxyValue = ConfigUtil.getConfigItem('useSystemProxy');
this.toggleManualProxySettings(newValue);
if (systemProxyValue && newValue) {
ConfigUtil.setConfigItem('useSystemProxy', !systemProxyValue);
}
ConfigUtil.setConfigItem('proxyRules', '');
ConfigUtil.setConfigItem('useManualProxy', newValue);
// Reload app only when turning manual proxy off, hence !newValue
ipcRenderer.send('forward-message', 'reload-proxy', !newValue);
this.updateProxyOption();
}
});

View File

@@ -14,12 +14,12 @@ class NewServerForm extends BaseComponent {
return `
<div class="server-input-container">
<div class="title">Organization URL</div>
<div class="add-server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
<div class="server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or chat.your-organization.com"/>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="connect">Connect</button>
<div class="action blue server-save-action">
<span id="connect">Connect</span>
</div>
</div>
<div class="server-center">
@@ -28,8 +28,8 @@ class NewServerForm extends BaseComponent {
</div>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="open-create-org-link">Create a new organization</button>
<div class="action blue server-save-action">
<span id="open-create-org-link">Create a new organization</span>
</div>
</div>
</div>
@@ -44,6 +44,7 @@ class NewServerForm extends BaseComponent {
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);
@@ -63,7 +64,7 @@ class NewServerForm extends BaseComponent {
}
openCreateNewOrgExternalLink() {
const link = 'https://zulipchat.com/new/';
const link = 'https://zulipchat.com/beta/';
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);

View File

@@ -90,22 +90,13 @@ class PreferenceView extends BaseComponent {
this.handleNavigation(navItem);
});
ipcRenderer.on('toggle-sidebar-setting', (event, state) => {
ipcRenderer.on('toggle-sidebar', (event, state) => {
this.handleToggle('sidebar-option', state);
});
ipcRenderer.on('toggletray', (event, state) => {
this.handleToggle('tray-option', state);
});
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.handleToggle('show-notification-option', newSettings.showNotification);
this.handleToggle('silent-option', newSettings.silent);
if (process.platform === 'win32') {
this.handleToggle('flash-taskbar-option', newSettings.flashTaskbarOnMessage);
}
});
}
}

View File

@@ -16,18 +16,19 @@ class ServerInfoForm extends BaseComponent {
<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>
<div class="server-info-right">
<div class="server-info-row server-url">
<span class="server-url-info" title="${this.props.server.url}">${this.props.server.url}</span>
<div class="server-info-row">
<input class="setting-input-value" disabled value="${this.props.server.url}"/>
</div>
<div class="server-info-row">
<div class="action red server-delete-action">
<span>Disconnect</span>
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
@@ -43,7 +44,6 @@ class ServerInfoForm extends BaseComponent {
initForm() {
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0];
this.$serverIcon = this.$serverInfoForm.getElementsByClassName('server-info-icon')[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);
@@ -71,12 +71,7 @@ class ServerInfoForm extends BaseComponent {
this.$serverInfoAlias.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
this.$serverIcon.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
}
}
module.exports = ServerInfoForm;

View File

@@ -1,7 +1,6 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const shell = require('electron').shell;
class ShortcutsSection extends BaseSection {
constructor(props) {
@@ -14,7 +13,6 @@ class ShortcutsSection extends BaseSection {
return `
<div class="settings-pane">
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link"> keyboard shortcuts</span>.</p></div>
<div class="title">Application Shortcuts</div>
<div class="settings-card">
<table>
@@ -25,10 +23,6 @@ class ShortcutsSection extends BaseSection {
<tr>
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
<td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
@@ -84,6 +78,14 @@ class ShortcutsSection extends BaseSection {
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td>Select All</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
<td>Find</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>G</kbd></td>
<td>Find Next</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<td>Emoji & Symbols</td>
@@ -119,7 +121,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td><kbd>${userOSKey}</kbd><kbd>S</kbd></td>
<td>Toggle Sidebar</td>
</tr>
<tr>
@@ -180,10 +182,6 @@ class ShortcutsSection extends BaseSection {
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
<td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
@@ -258,7 +256,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td><kbd>${userOSKey}</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td>
</tr>
<tr>
@@ -300,22 +298,13 @@ class ShortcutsSection extends BaseSection {
</table>
<div class="setting-control"></div>
</div>
<div class="tip"><b><i class="material-icons md-14">lightbulb_outline</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div>
`;
}
openHotkeysExternalLink() {
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
const externalCreateNewOrgEl = document.getElementById('open-hotkeys-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
init() {
this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMac() : this.templateWinLin();
this.openHotkeysExternalLink();
}
}

View File

@@ -1,21 +1,13 @@
'use strict';
const { ipcRenderer, shell } = require('electron');
const { ipcRenderer } = require('electron');
const SetupSpellChecker = require('./spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const LinkUtil = require(__dirname + '/utils/link-util.js');
const params = require(__dirname + '/utils/params-util.js');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
// eslint-disable-next-line camelcase
window.electron_bridge = require('./electron-bridge');
const logout = () => {
// Create the menu for the below
document.querySelector('.dropdown-toggle').click();
@@ -28,7 +20,7 @@ const shortcut = () => {
// Create the menu for the below
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
if (node.text.trim().toLowerCase() === 'keyboard shortcuts (?)') {
if (node.text.trim().toLowerCase() === 'keyboard shortcuts') {
node.click();
} else {
// Atleast click the dropdown
@@ -43,42 +35,21 @@ process.once('loaded', () => {
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', () => {
if (params.isPageParams()) {
// Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (e) {
const url = $(this).attr('href');
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
// Open image in the default browser if image preview is unavailable
if (!$img[0]) {
shell.openExternal(window.location.origin + url);
}
// Open image in lightbox
lightbox.open($img);
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
});
@@ -89,10 +60,3 @@ window.addEventListener('beforeunload', () => {
SetupSpellChecker.unsubscribeSpellChecker();
});
// electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
document.addEventListener('keydown', event => {
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
}
});

View File

@@ -1,17 +0,0 @@
'use strict';
// This is a security fix. Following function prevents drag and drop event in the app
// so that attackers can't execute any remote code within the app
// It doesn't affect the compose box so that users can still
// use drag and drop event to share files etc
const preventDragAndDrop = () => {
const preventEvents = ['dragover', 'drop'];
preventEvents.forEach(dragEvents => {
document.addEventListener(dragEvents, event => {
event.preventDefault();
});
});
};
preventDragAndDrop();

View File

@@ -3,12 +3,6 @@
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const Logger = require(__dirname + '/utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class SetupSpellChecker {
init() {
@@ -22,7 +16,7 @@ class SetupSpellChecker {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
logger.error(err);
console.log(err);
}
}

View File

@@ -3,9 +3,9 @@ const path = require('path');
const electron = require('electron');
const { ipcRenderer, remote } = electron;
const {ipcRenderer, remote} = electron;
const { Tray, Menu, nativeImage, BrowserWindow } = remote;
const {Tray, Menu, nativeImage, BrowserWindow} = remote;
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
@@ -98,10 +98,8 @@ const renderNativeImage = function (arg) {
return Promise.resolve()
.then(() => renderCanvas(arg))
.then(canvas => {
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPNG();
return Promise.resolve(nativeImage.createFromBuffer(pngData, {
scaleFactor: config.pixelRatio
}));
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPng();
return Promise.resolve(nativeImage.createFromBuffer(pngData, config.pixelRatio));
});
};
@@ -117,36 +115,50 @@ function sendAction(action) {
const createTray = function () {
window.tray = new Tray(iconPath());
const contextMenu = Menu.buildFromTemplate([
{
label: 'Zulip',
click() {
ipcRenderer.send('focus-app');
}
},
{
label: 'Settings',
click() {
ipcRenderer.send('focus-app');
sendAction('open-settings');
}
},
{
type: 'separator'
},
{
label: 'Quit',
click() {
ipcRenderer.send('quit-app');
}
const contextMenu = Menu.buildFromTemplate([{
label: 'About',
click() {
// We need to focus the main window first
ipcRenderer.send('focus-app');
sendAction('open-about');
}
},
{
type: 'separator'
},
{
label: 'Focus',
click() {
ipcRenderer.send('focus-app');
}
},
{
type: 'separator'
},
{
label: 'Settings',
click() {
ipcRenderer.send('focus-app');
sendAction('open-settings');
}
},
{
type: 'separator'
},
{
label: 'Quit',
click() {
ipcRenderer.send('quit-app');
}
}
]);
window.tray.setContextMenu(contextMenu);
if (process.platform === 'linux' || process.platform === 'win32') {
window.tray.on('click', () => {
window.tray.on('click', () => {
// Click event only works on Windows
if (process.platform === 'win32') {
ipcRenderer.send('toggle-app');
});
}
}
});
};
ipcRenderer.on('destroytray', event => {

View File

@@ -1,86 +0,0 @@
'use strict';
const { app, dialog } = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const { initSetUp } = require('./default-util');
initSetUp();
const logger = new Logger({
file: `certificate-util.log`,
timestamp: true
});
let instance = null;
const certificatesDir = `${app.getPath('userData')}/certificates`;
class CertificateUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
return instance;
}
getCertificate(server, defaultValue = null) {
this.reloadDB();
const value = this.db.getData('/')[server];
if (value === undefined) {
return defaultValue;
} else {
return value;
}
}
// Function to copy the certificate to userData folder
copyCertificate(server, location, fileName) {
let copied = false;
const filePath = `${certificatesDir}/${fileName}`;
try {
fs.copyFileSync(location, filePath);
copied = true;
} catch (err) {
dialog.showErrorBox(
'Error saving certificate',
'We encountered error while saving the certificate.'
);
logger.error('Error while copying the certificate to certificates folder.');
logger.error(err);
}
return copied;
}
setCertificate(server, fileName) {
const filePath = `${certificatesDir}/${fileName}`;
this.db.push(`/${server}`, filePath, true);
this.reloadDB();
}
removeCertificate(server) {
this.db.delete(`/${server}`);
this.reloadDB();
}
reloadDB() {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/certificates.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(settingsJsonPath)) {
fs.unlinkSync(settingsJsonPath);
dialog.showErrorBox(
'Error saving settings',
'We encountered error while saving the certificate.'
);
logger.error('Error while JSON parsing certificates.json: ');
logger.error(err);
}
}
this.db = new JsonDB(settingsJsonPath, true, true);
}
}
module.exports = new CertificateUtil();

View File

@@ -1,25 +0,0 @@
'use strict';
let instance = null;
class CommonUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
return instance;
}
// unescape already encoded/escaped strings
decodeString(string) {
const parser = new DOMParser();
const dom = parser.parseFromString(
'<!doctype html><body>' + string,
'text/html');
return dom.body.textContent;
}
}
module.exports = new CommonUtil();

View File

@@ -39,12 +39,7 @@ class ConfigUtil {
}
getConfigItem(key, defaultValue = null) {
try {
this.db.reload();
} catch (err) {
logger.error('Error while reloading settings.json: ');
logger.error(err);
}
this.reloadDB();
const value = this.db.getData('/')[key];
if (value === undefined) {
this.setConfigItem(key, defaultValue);
@@ -53,30 +48,19 @@ class ConfigUtil {
return value;
}
}
// This function returns whether a key exists in the configuration file (settings.json)
isConfigItemExists(key) {
try {
this.db.reload();
} catch (err) {
logger.error('Error while reloading settings.json: ');
logger.error(err);
}
const value = this.db.getData('/')[key];
return (value !== undefined);
}
setConfigItem(key, value) {
this.db.push(`/${key}`, value, true);
this.db.save();
this.reloadDB();
}
removeConfigItem(key) {
this.db.delete(`/${key}`);
this.db.save();
this.reloadDB();
}
reloadDB() {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/settings.json');
const settingsJsonPath = path.join(app.getPath('userData'), '/settings.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
JSON.parse(file);
@@ -85,11 +69,10 @@ class ConfigUtil {
fs.unlinkSync(settingsJsonPath);
dialog.showErrorBox(
'Error saving settings',
'We encountered an error while saving the settings.'
'We encountered error while saving current settings.'
);
logger.error('Error while JSON parsing settings.json: ');
logger.error(err);
logger.reportSentry(err);
}
}
this.db = new JsonDB(settingsJsonPath, true, true);

View File

@@ -10,8 +10,6 @@ if (process.type === 'renderer') {
const zulipDir = app.getPath('userData');
const logDir = `${zulipDir}/Logs/`;
const certificatesDir = `${zulipDir}/certificates/`;
const configDir = `${zulipDir}/config/`;
const initSetUp = () => {
// if it is the first time the app is running
// create zulip dir in userData folder to
@@ -24,51 +22,6 @@ const initSetUp = () => {
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
if (!fs.existsSync(certificatesDir)) {
fs.mkdirSync(certificatesDir);
}
// Migrate config files from app data folder to config folder inside app
// data folder. This will be done once when a user updates to the new version.
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir);
const domainJson = `${zulipDir}/domain.json`;
const certificatesJson = `${zulipDir}/certificates.json`;
const settingsJson = `${zulipDir}/settings.json`;
const updatesJson = `${zulipDir}/updates.json`;
const windowStateJson = `${zulipDir}/window-state.json`;
const configData = [
{
path: domainJson,
fileName: `domain.json`
},
{
path: certificatesJson,
fileName: `certificates.json`
},
{
path: settingsJson,
fileName: `settings.json`
},
{
path: updatesJson,
fileName: `updates.json`
}
];
configData.forEach(data => {
if (fs.existsSync(data.path)) {
fs.copyFileSync(data.path, configDir + data.fileName);
fs.unlinkSync(data.path);
}
});
// window-state.json is only deleted not moved, as the electron-window-state
// package will recreate the file in the config folder.
if (fs.existsSync(windowStateJson)) {
fs.unlinkSync(windowStateJson);
}
}
setupCompleted = true;
}
};

View File

@@ -1,41 +0,0 @@
'use strict';
const ConfigUtil = require(__dirname + '/config-util.js');
function toggle() {
const dnd = !ConfigUtil.getConfigItem('dnd', false);
const dndSettingList = ['showNotification', 'silent'];
if (process.platform === 'win32') {
dndSettingList.push('flashTaskbarOnMessage');
}
let newSettings;
if (dnd) {
const oldSettings = {};
newSettings = {};
// Iterate through the dndSettingList.
for (const settingName of dndSettingList) {
// Store the current value of setting.
oldSettings[settingName] = ConfigUtil.getConfigItem(settingName);
// New value of setting.
newSettings[settingName] = (settingName === 'silent');
}
// Store old value in oldSettings.
ConfigUtil.setConfigItem('dndPreviousSettings', oldSettings);
} else {
newSettings = ConfigUtil.getConfigItem('dndPreviousSettings');
}
for (const settingName of dndSettingList) {
ConfigUtil.setConfigItem(settingName, newSettings[settingName]);
}
ConfigUtil.setConfigItem('dnd', dnd);
return {dnd, newSettings};
}
module.exports = {
toggle
};

View File

@@ -5,15 +5,8 @@ const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
const escape = require('escape-html');
const Logger = require('./logger-util');
const CertificateUtil = require(__dirname + '/certificate-util.js');
const ProxyUtil = require(__dirname + '/proxy-util.js');
const ConfigUtil = require(__dirname + '/config-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
@@ -66,7 +59,7 @@ class DomainUtil {
addDomain(server) {
return new Promise(resolve => {
if (server.icon) {
this.saveServerIcon(server).then(localIconUrl => {
this.saveServerIcon(server.icon).then(localIconUrl => {
server.icon = localIconUrl;
this.db.push('/domains[]', server, true);
this.reloadDB();
@@ -111,29 +104,7 @@ class DomainUtil {
domain = this.formatUrl(domain);
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
let certificateLocation = '';
if (certificate) {
// To handle case where certificate has been moved from the location in certificates.json
try {
certificateLocation = fs.readFileSync(certificate);
} catch (err) {
logger.warn('Error while trying to get certificate: ' + err);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
// Add proxy as a parameter if it sbeing used.
const checkDomain = {
url: domain + '/static/audio/zulip.ogg',
ca: (certificateLocation) ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : '',
ecdhCurve: 'auto',
headers: { 'User-Agent': SystemUtil.getUserAgent() }
};
const checkDomain = domain + '/static/audio/zulip.ogg';
const serverConf = {
icon: defaultIconUrl,
@@ -143,16 +114,21 @@ class DomainUtil {
return new Promise((resolve, reject) => {
request(checkDomain, (error, response) => {
const certsError =
[
'Error: self signed certificate',
'Error: unable to verify the first certificate',
'Error: unable to get local issuer certificate'
];
// If the domain contains following strings we just bypass the server
const whitelistDomains = [
'zulipdev.org'
];
// make sure that error is an error or string not undefined
// make sure that error is a error or string not undefined
// so validation does not throw error.
error = error || '';
const certsError = error.toString().includes('certificate');
if (!error && response.statusCode < 400) {
// Correct
this.getServerSettings(domain).then(serverSettings => {
@@ -160,7 +136,7 @@ class DomainUtil {
}, () => {
resolve(serverConf);
});
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError) {
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError.indexOf(error.toString()) >= 0) {
if (silent) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
@@ -168,19 +144,15 @@ class DomainUtil {
resolve(serverConf);
});
} else {
// Report error to sentry to get idea of possible certificate errors
// users get when adding the servers
logger.reportSentry(new Error(error));
const certErrorMessage = `Do you trust certificate from ${domain}? \n ${error}`;
const certErrorDetail = `The organization you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\nIf you have a valid certificate please add it from Settings>Organizations and try to add the organization again.
\nUnless you have a good reason to believe otherwise, you should not proceed.
\nYou can click here if you'd like to proceed with the connection.`;
const certErrorDetail = `The server you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\n Unless you have a good reason to believe otherwise, you should not proceed.
\n You can click here if you'd like to proceed with the connection.`;
dialog.showMessageBox({
type: 'warning',
buttons: ['Yes', 'No'],
defaultId: 1,
defaultId: 0,
message: certErrorMessage,
detail: certErrorDetail
}, response => {
@@ -197,8 +169,7 @@ class DomainUtil {
}
} else {
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \
\n (1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server \
\n (4) the server has a valid certificate, you can add custom certificates in Settings>Organizations`;
\n(1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server`;
reject(invalidZulipServerError);
}
});
@@ -206,28 +177,9 @@ class DomainUtil {
}
getServerSettings(domain) {
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
let certificateLocation = '';
if (certificate) {
// To handle case where certificate has been moved from the location in certificates.json
try {
certificateLocation = fs.readFileSync(certificate);
} catch (err) {
logger.warn('Error while trying to get certificate: ' + err);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverSettingsOptions = {
url: domain + '/api/v1/server_settings',
ca: (certificateLocation) ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : '',
ecdhCurve: 'auto',
headers: { 'User-Agent': SystemUtil.getUserAgent() }
};
const serverSettingsUrl = domain + '/api/v1/server_settings';
return new Promise((resolve, reject) => {
request(serverSettingsOptions, (error, response) => {
request(serverSettingsUrl, (error, response) => {
if (!error && response.statusCode === 200) {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
@@ -236,7 +188,7 @@ class DomainUtil {
// Following check handles both the cases
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
url: data.realm_uri,
alias: escape(data.realm_name)
alias: data.realm_name
});
}
} else {
@@ -246,58 +198,26 @@ class DomainUtil {
});
}
saveServerIcon(server) {
const url = server.icon;
const domain = server.url;
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
let certificateLocation = '';
if (certificate) {
// To handle case where certificate has been moved from the location in certificates.json
try {
certificateLocation = fs.readFileSync(certificate);
} catch (err) {
logger.warn('Error while trying to get certificate: ' + err);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
// Add proxy and certificate as a parameter if its being used.
const serverIconOptions = {
url,
ca: (certificateLocation) ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : '',
ecdhCurve: 'auto',
headers: { 'User-Agent': SystemUtil.getUserAgent() }
};
saveServerIcon(url) {
// The save will always succeed. If url is invalid, downgrade to default icon.
return new Promise(resolve => {
const filePath = this.generateFilePath(url);
const file = fs.createWriteStream(filePath);
try {
request(serverIconOptions).on('response', response => {
request(url).on('response', response => {
response.on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
});
response.pipe(file).on('finish', () => {
resolve(filePath);
});
}).on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
});
} catch (err) {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
}
});
@@ -306,7 +226,7 @@ class DomainUtil {
updateSavedServer(url, index) {
// Does not promise successful update
this.checkDomain(url, true).then(newServerConf => {
this.saveServerIcon(newServerConf).then(localIconUrl => {
this.saveServerIcon(newServerConf.icon).then(localIconUrl => {
newServerConf.icon = localIconUrl;
this.updateDomain(index, newServerConf);
this.reloadDB();
@@ -315,7 +235,7 @@ class DomainUtil {
}
reloadDB() {
const domainJsonPath = path.join(app.getPath('userData'), 'config/domain.json');
const domainJsonPath = path.join(app.getPath('userData'), '/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
@@ -329,7 +249,6 @@ class DomainUtil {
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
logger.reportSentry(err);
}
}
this.db = new JsonDB(domainJsonPath, true, true);

View File

@@ -19,26 +19,7 @@ class LinkUtil {
const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl);
const sameDomainUrl = (currentDomain === newDomain || newUrl === currentUrl + '/');
const isUploadsUrl = newUrl.includes(currentUrl + '/user_uploads/');
const isInternalUrl = newUrl.includes('/#narrow') || isUploadsUrl;
return {
isInternalUrl: sameDomainUrl && isInternalUrl,
isUploadsUrl
};
}
isImage(url) {
// test for images extension as well as urls like .png?s=100
const isImageUrl = /\.(bmp|gif|jpg|jpeg|png|webp)\?*.*$/i;
return isImageUrl.test(url);
}
isPDF(url) {
// test for pdf extension
const isPDFUrl = /\.(pdf)\?*.*$/i;
return isPDFUrl.test(url);
return (currentDomain === newDomain) && newUrl.includes('/#narrow');
}
}

View File

@@ -52,7 +52,7 @@ class LinuxUpdateUtil {
}
reloadDB() {
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/config/updates.json');
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/updates.json');
try {
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
JSON.parse(file);
@@ -61,7 +61,7 @@ class LinuxUpdateUtil {
fs.unlinkSync(linuxUpdateJsonPath);
dialog.showErrorBox(
'Error saving update notifications.',
'We encountered an error while saving the update notifications.'
'We encountered error while saving update notifications.'
);
logger.error('Error while JSON parsing updates.json: ');
logger.error(err);

View File

@@ -2,10 +2,8 @@ const NodeConsole = require('console').Console;
const fs = require('fs');
const isDev = require('electron-is-dev');
const { initSetUp } = require('./default-util');
const { sentryInit, captureException } = require('./sentry-util');
initSetUp();
sentryInit();
let app = null;
if (process.type === 'renderer') {
app = require('electron').remote.app;
@@ -84,10 +82,6 @@ class Logger {
`${date.getMinutes()}:${date.getSeconds()}`;
return timestamp;
}
reportSentry(err) {
captureException(err);
}
}
module.exports = Logger;

View File

@@ -1,15 +0,0 @@
// This util function returns the page params if they're present else returns null
function isPageParams() {
let webpageParams = null;
try {
// eslint-disable-next-line no-undef, camelcase
webpageParams = page_params;
} catch (err) {
webpageParams = null;
}
return webpageParams;
}
module.exports = {
isPageParams
};

View File

@@ -1,130 +0,0 @@
'use strict';
const url = require('url');
const ConfigUtil = require('./config-util.js');
let instance = null;
class ProxyUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
return instance;
}
// Return proxy to be used for a particular uri, to be used for request
getProxy(uri) {
uri = url.parse(uri);
const proxyRules = ConfigUtil.getConfigItem('proxyRules', '').split(';');
// If SPS is on and system uses no proxy then request should not try to use proxy from
// environment. NO_PROXY = '*' makes request ignore all environment proxy variables.
if (proxyRules[0] === '') {
process.env.NO_PROXY = '*';
return;
}
const proxyRule = {};
if (uri.protocol === 'http:') {
proxyRules.forEach(proxy => {
if (proxy.includes('http=')) {
proxyRule.hostname = proxy.split('http=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('http=')[1].trim().split(':')[1];
}
});
return proxyRule;
}
if (uri.protocol === 'https:') {
proxyRules.forEach(proxy => {
if (proxy.includes('https=')) {
proxyRule.hostname = proxy.split('https=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('https=')[1].trim().split(':')[1];
}
});
return proxyRule;
}
}
resolveSystemProxy(mainWindow) {
const page = mainWindow.webContents;
const ses = page.session;
const resolveProxyUrl = 'www.google.com';
// Check HTTP Proxy
const httpProxy = new Promise(resolve => {
ses.resolveProxy('http://' + resolveProxyUrl, proxy => {
let httpString = '';
if (proxy !== 'DIRECT') {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
// for all other HTTP or direct url:port both uses PROXY
if (proxy.includes('PROXY') || proxy.includes('HTTPS')) {
httpString = 'http=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(httpString);
});
});
// Check HTTPS Proxy
const httpsProxy = new Promise(resolve => {
ses.resolveProxy('https://' + resolveProxyUrl, proxy => {
let httpsString = '';
if (proxy !== 'DIRECT' || proxy.includes('HTTPS')) {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
// for all other HTTP or direct url:port both uses PROXY
if (proxy.includes('PROXY' || proxy.includes('HTTPS'))) {
httpsString += 'https=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(httpsString);
});
});
// Check FTP Proxy
const ftpProxy = new Promise(resolve => {
ses.resolveProxy('ftp://' + resolveProxyUrl, proxy => {
let ftpString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('PROXY')) {
ftpString += 'ftp=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(ftpString);
});
});
// Check SOCKS Proxy
const socksProxy = new Promise(resolve => {
ses.resolveProxy('socks4://' + resolveProxyUrl, proxy => {
let socksString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('SOCKS5')) {
socksString += 'socks=' + proxy.split('SOCKS5')[1] + ';';
} else if (proxy.includes('SOCKS4')) {
socksString += 'socks=' + proxy.split('SOCKS4')[1] + ';';
} else if (proxy.includes('PROXY')) {
socksString += 'socks=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(socksString);
});
});
Promise.all([httpProxy, httpsProxy, ftpProxy, socksProxy]).then(values => {
let proxyString = '';
values.forEach(proxy => {
proxyString += proxy;
});
ConfigUtil.setConfigItem('systemProxyRules', proxyString);
const useSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
if (useSystemProxy) {
ConfigUtil.setConfigItem('proxyRules', proxyString);
}
});
}
}
module.exports = new ProxyUtil();

View File

@@ -1,10 +1,4 @@
const isOnline = require('is-online');
const Logger = require('./logger-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
class ReconnectUtil {
constructor(serverManagerView) {
@@ -19,12 +13,12 @@ class ReconnectUtil {
pollInternetAndReload() {
const pollInterval = setInterval(() => {
this._checkAndReload()
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
}, 1500);
}
@@ -32,24 +26,22 @@ class ReconnectUtil {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
isOnline()
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
logger.log('You\'re back online.');
return resolve(true);
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
console.log('You\'re back online.');
return resolve(true);
}
logger.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = `
<div>Your internet connection doesn't seem to work properly!</div>
<div>Verify that it works and then click try again.</div>`;
}
return resolve(false);
});
console.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
errMsgHolder.innerHTML = `
<div>You internet connection does't seem to work properly!</div>
</div>Verify that it works and then click try again.</div>`;
return resolve(false);
});
} else {
return resolve(true);
}

View File

@@ -1,20 +0,0 @@
const { init, captureException } = require('@sentry/electron');
const isDev = require('electron-is-dev');
const sentryInit = () => {
if (!isDev) {
init({
dsn: 'SENTRY_DSN',
// We should ignore this error since it's harmless and we know the reason behind this
// This error mainly comes from the console logs.
// This is a temp solution until Sentry supports disabling the console logs
ignoreErrors: ['does not appear to be a valid Zulip server'],
sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
});
}
};
module.exports = {
sentryInit,
captureException
};

View File

@@ -24,10 +24,6 @@
</div>
</div>
<div id="actions-container">
<div class="action-button" id="dnd-action">
<i class="material-icons md-48">notifications</i>
<span id="dnd-tooltip" style="display:none">Do Not Disturb</span>
</div>
<div class="action-button" id="reload-action">
<i class="material-icons md-48">refresh</i>
<span id="reload-tooltip" style="display:none">Reload</span>
@@ -46,11 +42,6 @@
<div id="webviews-container"></div>
</div>
</div>
<div id="feedback-modal">
<send-feedback></send-feedback>
</div>
</body>
<script src="js/main.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>
</html>

View File

@@ -17,6 +17,5 @@
<div id="reconnect">Try now</div>
</div>
</body>
<script src="js/pages/network.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
<script src="js/pages/network.js"></script>
</html>

View File

@@ -13,5 +13,4 @@
</div>
</body>
<script src="js/pages/preference/preference.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

Binary file not shown.

View File

@@ -6,9 +6,9 @@ os: Previous Visual Studio 2015
cache:
- node_modules
install:
- ps: Install-Product node 8 x64
- ps: Install-Product node 6 x64
- git reset --hard HEAD
- npm install npm -g
- node --version
@@ -21,4 +21,4 @@ build: off
test_script:
- npm run test
# - npm run test-e2e
- npm run test-e2e

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -1,764 +0,0 @@
# Version History
All notable changes to the Zulip desktop app are documented in this file.
### v2.3.82 --2018-09-25
**New features**:
* UI enhancements.
* Updated some menu items.
**Fixes**:
* Fix about page not opening up.
<hr>
### v2.3.8 --2018-09-25
**New features**:
* Auto hide menubar on Windows/Linux. Add a setting option for the same.
* Improve design of setting page.
* Toggle app on clicking the tray icon (Linux).
* Update sidebar realm name when it's changed in webapp.
* left-sidebar: Add initial character of realm name instead of default icon.
**Fixes**:
* linux: Fix ALT+SHIFT opening menu items on Linux.
* sentry: Add ignore errors to sentry configuration.
* Linux: Add label for help menu item.
* file-attachments: Allow multiple downloads of same file name.
**Module Updates**
* electron: Update electron to v2.0.9.
<hr>
### v2.3.7-beta --2018-09-03
**New features**:
* Add a feature to show and view pdf file in the app.
**Fixes**:
* Use package reload instead of custom reload. This is an experimental fix for setting files getting corrupted issue.
* Unescape server name in window menu item.
<hr>
### v2.3.6 --2018-08-27
**New features**:
* Add proxy details while validating a server. This fixes the server validating issue for users who are using the proxy settings.
**Fixes**:
* Fix youtube video not playing in lightbox.
* Fix realm name not escaped properly.
<hr>
### v2.3.5 --2018-08-03
**New features**:
* Add a setting option to show downloaded file in file manager.
* Added electron bridge to communicate with webapp in real time.
**Fixes**:
* Fix failing attached file downloads.
* Fix page_params error.
* gulpfile: Update syntax and methods for gulp v4.x.
<hr>
### v2.3.4-beta --2018-07-24
**Fixes**:
* Fix downloading functionality of file attachments.
* Fix null of downloadPath when settings.json fails.
<hr>
### v2.3.3 --2018-07-14
**Enhancements**:
* Add dock bounce effect on macOS
* Add a setting option to use the system proxy settings
* Add support for self/custom signed certificate
* Add Sentry support to get the bug reports
* Show a notification when a user clicks on file attachments and open the same in default native app
**Fixes**:
* Fix auto-updates on Windows
* Fix image attachments not opening up in the app
* Security fix - Do proper HTML escaping for server data to avoid XSS attacks
* Other minor fixes
**Updated dependencies**:
electron-builder: v20.20.4
electron-updater: v2.23.3
<hr>
### v2.3.2 --2018-05-28
This is a quick release since we recently updated the certificate for code signing the app and we want to make sure the previous versions of the app get auto-updates without any conflicts.
<hr>
### v2.3.1 --2018-05-23
**Enhancements**:
* Add a new setting to disable auto-updates
* Add a menu item to check for updates manually
* Other minor improvements
**Fixes**:
* Fix app not rendering colors properly
* **Security fix** - Do proper HTML escaping for server data to avoid XSS attacks
* Fix loading indicator when server is loaded
* Other minor fixes
**Updated dependencies**:
electron - `v2.0.1`
electron-builder - `v20.13.4`
electron-updater - `v2.21.10`
<hr>
### v2.2.0-beta --2018-05-08
**Enhancements**:
* Added do not disturb feature
* New DMG installer image
* Minor improvements
**Fixes**:
* Fix download functionality of file attachment links
* Fix tray icon not showing unread counts
**Updated dependencies**:
electron - `v2.0.0`
electron-builder - `v2.21.10`
electron-updater - `v2.21.8`
<hr>
### v2.0.0 -- 2018-04-20
**Enhancements**:
* [Snap](https://snapcraft.io/zulip) support for Linux
* Add an option to download the file attachments instead of opening it in the browser
* Open image link in webapp lightbox
* Add scrollbar for list of organizations on overflow
* Better report issue UX
* Add F5 shortcut for reloading the app
* Responsive UI for connected orgs in smaller window sizes
* Minor improvements
**Fixes**:
* Remove unused shortcuts
* Update toggle sidebar shortcut to CMD/CTRL+SHIFT+S
* Warning dialog box for Reset App Settings
* Fix reinstall script for working across all platforms
* Other minor fixes
**Updated dependencies**:
* Update electron-builder to `v20.8.1`
<hr>
### v1.9.0 --2018-03-23
**Enhancements**:
* Major UI changes in the add new server and setting page
* Some users wanted to change the look of the Zulip. Now you have the power. Feel free to add your own CSS using the all-new setting option **Add Custom CSS**
* Added i18n locale helper script. Internalization is coming in the next release
* Added **What's new** in `help` submenu so that you can see all the latest changes in the app
* Other small improvements
**Fixes**:
* Add Zulip binary link to the bin. It was a regression we missed somehow. You can now start the app using `zulip` from a terminal [Linux]
* Fix app settings overriding issue. This bug was unnoticed from `v1.4.0`. The bug was overriding the app settings which was pretty bad. Don't worry this works as expected now
* Prevent drag and drop events to avoid few security risks
* Do not allow running insecure contents in the app
* The ugly white flickering in the webview is now fixed
* Fix position of left-sidebar on toggle
* Fix actions tooltip not visible on hover
* Other small fixes
**Updated dependencies**:
* electron - `v1.8.4`
* Spectron - `v3.8.0`
<hr>
### v1.8.2 --2018-02-27
**Enhancements**:
* Add bots mention support to reply option
* Show a notification when a new update available [Linux]
* Add back button in left-sidebar
* Add a Loading indication for new server button
* Improve help menu
* Add menu-item to reveal app logs in file manager
* Setting page and left-sidebar UI improvements
* Other minor improvments
**Fixes**:
* Fix broken last active tab
* Fix Zoom In shortcut
* Check whether the internet is working before reloading
* New organization link overlapping existing servers
* Disable electron-connect when it's run from npm start
* Do not auto-reload app when the system comes back from sleep
* Only toggle the setting state if the element is present
* Other minor fixes
**Updated dependencies**:
* electron - `v1.8.2`
<hr>
### v1.8.1 --2018-01-19
**Enhancements**:
* Reply from notifications [macOS]
* Add a setting option to start the app in the background
* 32-bit Debian installer
* 32-bit AppImage installer
* Automatically add apt-repo and GPG public keys on installing Debian file [Linux]
* Remove app data, config files etc on uninstalling Debian installer [Linux]
* Add logger utility for debugging that makes easier to sharing the apps
**Fixes**:
* Handle corrupted config files
* Reload full app on system hibernation
* Load default icon if organization icon is not available
* Focus app when a notification is triggered
* Reflect changes in the preference page (#362)
* Set the default value of flash taskbar setting [Windows]
* Rename Zoom In keyboard shortcut
* Fix checkDomain, so it checks all error codes
* Disable hardware acceleration to decrease the load on GPU
* Minor bug fixes
**Updated dependencies**:
* electron - `v1.7.10`
* electron-builder - `v19.53.6`
* electron-updater - `v2.18.2`
<hr>
### v1.7.0 --2017-11-24
**Enhancements**:
* Updated to Electron `v1.6.15`, electron-builder `v19.46.4`
* Improved setting page for new users
* On the developer side, we have added few tests to make sure app doesn't fail
* Minor improvements
<hr>
### 1.6.0-beta --2017-11-16
**Enhancements**:
* Added setting option to enable/disable spellcheck
* Added setting option to control Windows taskbar flashing [Windows]
* Auto-updates for Linux [AppImage]
* Better tray icons for retina display
* Reset app settings from Menu item
* Show detailed error message on invalid Zulip server
**Fixes**:
* Fixed a bug which was caused by app's shortcuts. From now on our shortcuts won't hijack other apps shortcuts
* Removed [electron-localshortcut](https://github.com/parro-it/electron-localshortcut) completely. Now we only depends on menu accelerators for keyboard shortcuts
* Handle certificate issue properly
* Other minor fixes
<hr>
### v1.5.0 --2017-10-11
**Enhancements**:
* Added an option to clear app data
* Added an option to show/hide desktop notifications
* Redesigned setting page
* Red dot over dock icon for PM [macOS]
* Show server-info on hovering the server icon
**Fixes**:
* Fetch correct organization icon from server_settings API
* Minor improvements
<hr>
### v1.4.0 --2017-09-04
**Enhancements**:
* Added proxy support
* Added setting option for badges
* Start app at login setting option
* Added app category for Linux
* Minor improvements in settings page
* apt-repo on bintray for linux - [here](https://bintray.com/zulip/debian/zulip-elec)
**Fixes**
* Fixed auto-updates
* Better warning message on certificate error
* Don't allow duplicate servers
<hr>
### 1.3.0-beta -- 2017-08-09
**Enhancements**:
* Added back/forward option under `History` submenu
* Added taskbar overlay icon [Windows]
* Added `Window` submenu by which users can switch to other servers
* Added flashing taskbar icon on incoming message [Windows]
* Option to hide the left sidebar
* Staged rollouts
* Minor style improvements
**Fixes**
* Handle focus event properly
* Open external links in default browser
* Allow user to change installation directory
* Fixed server-icon path error [Windows]
* Fixed server validation error
* Fetch server details from Zulip api
* Added tooltip in left sidebar
* Added publisher name in windows installer
**Updated dependencies**:
* electron - `v1.6.11`
* spellchecker - `v1.2.0`
* electron-builder - `v19.9.1`
<hr>
### v1.2.0-beta -- 2017-07-12
**Enhancements**:
* Added new setting page
* Setting for beta-updates. You can opt in for beta updates via selecting it from setting page
* Added Keyboard shortcuts to switch between multiple servers
* Desktop notifications support on Windows
**Fixes**
* Unregister keyboard shortcuts on windows close
* Show warning dialog while deleting server
* Allow server which is signed by root cert
* Other minor bug fixes
<hr>
### v1.1.0-beta --2017-06-23
**Enhancements**:
* New network error page. App will auto-reload once the network comes back
* Quit shortcut - CTRL+Q [Windows]
* Improved preferences
**Fixes**:
* Fixed power-monitor module error
* Fixed code signing error [macOS]
<hr>
### v1.0.0-beta --2017-06-21
**Enhancements**:
* Sign in to multiple teams
* Windows 7 desktop notification support
* Show badge count for each Zulip server
* Toggle window on clicking tray icon [Windows]
* Windows installer is properly code signed using [DigiCert](https://www.digicert.com/) certificate. You may get a warning regarding the certificate, please make it false positive
**Fixes**:
* Close/hide app properly
* Keep app running in background on clicking X
<hr>
### v0.5.10 --2017-05-12
**Enhancements**:
* Added self-signed server support
* Unread message counts in tray icon
<hr>
### v0.5.9 --2017-04-12
**Enhancements**:
* Properly signed app for macOS
* Toggle tray icon
* Better error handling when no internet connenction
**Fixes**:
* Fixed permission issue on windows
* Fixed wrong keyboard shortcuts
<hr>
### v0.5.8 --2017-02-13
**Enhacements**:
Smaller Windows installer size
<hr>
### v0.5.7 --2017-02-08
Minor improvements
<hr>
### v0.5.6 --2017-02-07
**Enhacements**:
- Using NSIS instead of [Squirrel.Windows](https://github.com/Squirrel/Squirrel.Windows) on Windows
- Autoupdates: now using [electron-updater](https://www.npmjs.com/package/electron-updater)
- Removed nuts dependency
**Fixes**:
- Windows installer error
- Compile and build error in native node modules + spellchecker [Windows]
- Missing icon on installing [Windows]
<hr>
### v0.5.4 --2017-01-09
**Fixes**:
- Fixed :
- Auto-updates
- Spellchecker
- Zooming functionality
- Removed unused node modules
- Using stable version for node modules
- Added icon for AppImage
<hr>
### v0.5.3 --2016-12-24
**Enhancements**:
- 🎉 Added automatic update support on Windows
**Fixes**:
- Fixed javascript error when electron has been left running and try to right-click
- Fixed error - Unregister Spellchecker while window is closed
- Fixed - Keyboard shortcuts not getting unregistered
- Minor UI Changes
<hr>
### v0.5.2 --2016-12-13
**Enhancements**:
- ⚡️ Added automatic app updates for macOS
- Added windows installer for 32 bit machine
- Better tray icons
**Fixes**:
- Blurry Unity icon fixed
<hr>
### v0.5.1 --2016-11-23
**Enhacements**:
- Added Spellchecker support with correct spell suggestions
- Added Code-Signing on MAC
- Added Win/linux/Mac installers
- Added Continuous Integration on travis and appveyor
- Open internal links in app only and external in default browser
- Better icons
- Electron version updated to `v1.4.7`
- Using two package.json structure
- Node integration disabled in main window due to jquery error
- Now using electron-builder for packaging instead of electron-packager
- Removed electron-context-menu and simple-spellchecker dependency
**Fixes**:
- Better error handling when user enters wrong zulip server
- Fixed close Zulip window JavaScript error
- Fixed opening image in default browser
- Fixed native notifications issue on linux
<hr>
### v0.0.1-alpha -- 2016-08-31
**Enhacements**:
* Added DMG installer for macOS

View File

@@ -15,7 +15,6 @@ To build and run the app from source, you'll need the following:
* [Python](https://www.python.org/downloads/release/python-2713/)
(v2.7.x recommended)
* A C++ compiler compatible with C++11
* Linux users also need [Snapcraft](https://snapcraft.io/)
* Development headers for the libXext, libXtst, and libxkbfile libraries
### Debian/Ubuntu and friends
@@ -26,7 +25,7 @@ manager (see [here][nodesource-install] for more on the first command):
```sh
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
$ sudo apt install git nodejs python build-essential snapcraft libxext-dev libxtst-dev libxkbfile-dev libgconf-2-4
$ sudo apt install git nodejs python build-essential libxext-dev libxtst-dev libxkbfile-dev
```
[nodesource-install]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
@@ -77,7 +76,7 @@ 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, `.snap` file and an
* 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.

View File

@@ -10,11 +10,11 @@ gulp.task('dev', () => {
// Start browser process
electron.start();
// Restart browser process
gulp.watch('app/main/*.js', gulp.series('restart:browser'));
gulp.watch('app/main/*.js', ['restart:browser']);
// Reload renderer process
gulp.watch('app/renderer/css/*.css', gulp.series('reload:renderer'));
gulp.watch('app/renderer/*.html', gulp.series('reload:renderer'));
gulp.watch('app/renderer/js/**/*.js', gulp.series('reload:renderer'));
gulp.watch('app/renderer/css/*.css', ['reload:renderer']);
gulp.watch('app/renderer/*.html', ['reload:renderer']);
gulp.watch('app/renderer/js/**/*.js', ['reload:renderer']);
});
gulp.task('restart:browser', done => {
@@ -36,4 +36,4 @@ gulp.task('test-e2e', () => {
}));
});
gulp.task('default', gulp.parallel('dev', 'test-e2e'));
gulp.task('default', ['dev', 'test-e2e']);

105
help.md Normal file
View File

@@ -0,0 +1,105 @@
# User Guide
> Welcome! This guide will walk you through the basics of using Zulip Desktop.
## Get Zulip Desktop
## Connect to a Server
### Connect through a proxy
It's possible to connect to your server through a proxy.
You can enter the proxy settings in the `Network` section of App Settings.
There are three fields provided:
* `PAC script` - The URL associated with the PAC file.
* `Proxy rules` - Rules indicating which proxies to use.
* `Proxy bypass rules` - Rules indicating which URLs should
bypass the proxy settings.
For a typical setup where internet access is required to use an HTTP proxy,
but URLs on the local network should be accessed directly, configure as follows:
`Proxy rules = proxy.example.com`
Your HTTP proxy server
`Proxy bypass rules = *.example.com;10.0.0.0/8`
Directly connect to your own domain and private IP subnet
for more complex setups, read below to configure complex proxy rules and proxy bypass rules.
### Sets the proxy settings.
When `PAC script` and `Proxy rules` are provided together, the `Proxy rules`
option is ignored and `PAC script` configuration is applied.
The `Proxy rules` has to follow the rules below:
```
proxyRules = schemeProxies[";"<schemeProxies>]
schemeProxies = [<urlScheme>"="]<proxyURIList>
urlScheme = "http" | "https" | "ftp" | "socks"
proxyURIList = <proxyURL>[","<proxyURIList>]
proxyURL = [<proxyScheme>"://"]<proxyHost>[":"<proxyPort>]
```
For example:
* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and
HTTP proxy `foopy2:80` for `ftp://` URLs.
* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs.
* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing
over to `bar` if `foopy:80` is unavailable, and after that using no proxy.
* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs.
* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail
over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable.
* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no
proxy if `foopy` is unavailable.
* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use
`socks4://foopy2` for all other URLs.
The `Proxy bypass rules` is a comma separated list of rules described below:
* `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]`
Match all hostnames that match the pattern HOSTNAME_PATTERN.
Examples:
"foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99",
"https://x.*.y.com:99"
* `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]`
Match a particular domain suffix.
Examples:
".google.com", ".com", "http://.google.com"
* `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]`
Match URLs which are IP address literals.
Examples:
"127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99"
* `IP_LITERAL "/" PREFIX_LENGHT_IN_BITS`
Match any URL that is to an IP literal that falls between the
given range. IP range is specified using CIDR notation.
Examples:
"192.168.1.1/16", "fefe:13::abc/33".
* `<local>`
Match local addresses. The meaning of `<local>` is whether the
host matches one of: "127.0.0.1", "::1", "localhost".
## Change App Preferences
## Reporting an Issue

8797
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.5.0-beta",
"version": "1.8.2",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
@@ -22,7 +22,7 @@
},
"scripts": {
"start": "electron app --disable-http-cache --no-electron-connect",
"reinstall": "node ./tools/reinstall-node-modules.js",
"reinstall": "./tools/reinstall-node-modules",
"postinstall": "electron-builder install-app-deps",
"test": "xo",
"test-e2e": "gulp test-e2e",
@@ -46,8 +46,7 @@
],
"copyright": "©2017 Kandra Labs, Inc.",
"mac": {
"category": "public.app-category.productivity",
"artifactName": "${productName}-${version}-${arch}.${ext}"
"category": "public.app-category.productivity"
},
"linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
@@ -56,41 +55,32 @@
"target": [
"deb",
"zip",
"AppImage",
"snap"
"AppImage"
],
"maintainer": "Akash Nimare <svnitakash@gmail.com>",
"artifactName": "${productName}-${version}-${arch}.${ext}"
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
},
"deb": {
"synopsis": "Zulip Desktop App",
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
},
"snap": {
"synopsis": "Zulip Desktop App"
},
"dmg": {
"background": "build/appdmg.png",
"icon": "build/icon.icns",
"iconSize": 100,
"iconSize": 128,
"contents": [
{
"x": 380,
"y": 280,
"y": 240,
"type": "link",
"path": "/Applications"
},
{
"x": 110,
"y": 280,
"x": 122,
"y": 240,
"type": "file"
}
],
"window": {
"width": 500,
"height": 500
}
]
},
"win": {
"target": [
@@ -106,6 +96,8 @@
"publisherName": "Kandra Labs, Inc."
},
"nsis": {
"perMachine": true,
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
},
@@ -121,17 +113,17 @@
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "3.0.10",
"electron-builder": "20.38.4",
"electron": "1.8.2",
"electron-builder": "19.53.6",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",
"gulp": "^4.0.0",
"gulp": "3.9.1",
"gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"nodemon": "^1.14.11",
"pre-commit": "1.2.2",
"spectron": "^5.0.0",
"spectron": "3.7.2",
"tap-colorize": "^1.2.0",
"tape": "^4.8.0",
"xo": "0.18.2"
@@ -151,7 +143,7 @@
"max-lines": [
"warn",
{
"max": 600,
"max": 500,
"skipBlankLines": true,
"skipComments": true
}

View File

@@ -2,10 +2,6 @@
# This script runs when user install the debian package
# Link to the binary
ln -sf '/opt/${productFilename}/${executable}' '/usr/local/bin/${executable}';
echo 'Successfully added /opt/${productFilename}/${executable} to /usr/local/bin/${executable}'
# Install apt repository source list if it does not exist
if ! grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then
sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9

View File

@@ -28,8 +28,4 @@ appDirectory=/home/$getSudoUser/.config/Zulip/;
if [ -d $appDirectory ]; then
sudo rm -rf $appDirectory;
fi
# Delete the link to the binary
echo 'Removing binary link'
sudo rm -f '/usr/local/bin/${executable}';
fi

View File

@@ -15,6 +15,6 @@ fi
npm run test
# if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
# npm run test-e2e
# fi
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
npm run test-e2e
fi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,37 +0,0 @@
name: zulip
version: 2.0.0
summary: Zulip Desktop Client for Linux
description: Zulip combines the immediacy of Slack with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones.
confinement: strict
grade: stable
icon: snap/gui/icon.png
apps:
zulip:
command: env TMPDIR=$XDG_RUNTIME_DIR desktop-launch $SNAP/zulip
plugs:
- desktop
- desktop-legacy
- home
- x11
- unity7
- browser-support
- network
- gsettings
- pulseaudio
- opengl
parts:
app:
plugin: dump
stage-packages:
- libasound2
- libgconf2-4
- libnotify4
- libnspr4
- libnss3
- libpcre3
- libpulse0
- libxss1
- libxtst6
source: dist/linux-unpacked
after:
- desktop-gtk2

View File

@@ -1,183 +0,0 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.4.0",
"main": "../../app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
"author": {
"name": "Kandra Labs, Inc.",
"email": "support@zulipchat.com"
},
"repository": {
"type": "git",
"url": "https://github.com/zulip/zulip-electron.git"
},
"bugs": {
"url": "https://github.com/zulip/zulip-electron/issues"
},
"engines": {
"node": ">=6.0.0"
},
"scripts": {
"start": "electron app --disable-http-cache --no-electron-connect",
"reinstall": "node ./tools/reinstall-node-modules.js",
"postinstall": "electron-builder install-app-deps",
"test": "xo",
"test-e2e": "gulp test-e2e",
"dev": "gulp dev & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"mas": "electron-builder --mac mas",
"travis": "cd ./scripts && ./travis-build-test.sh",
"build-locales": "node tools/locale-helper"
},
"pre-commit": [
"test"
],
"build": {
"appId": "org.zulip.zulip-electron",
"asar": true,
"files": [
"**/*",
"!docs${/*}",
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
],
"copyright": "©2017 Kandra Labs, Inc.",
"mac": {
"category": "public.app-category.productivity",
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
"packageCategory": "GNOME;GTK;Network;InstantMessaging",
"description": "Zulip Desktop Client for Linux",
"target": [
"deb",
"zip",
"AppImage",
"snap"
],
"maintainer": "Akash Nimare <svnitakash@gmail.com>",
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"deb": {
"synopsis": "Zulip Desktop App",
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
},
"snap": {
"synopsis": "Zulip Desktop App"
},
"dmg": {
"background": "build/appdmg.png",
"icon": "build/icon.icns",
"iconSize": 100,
"contents": [
{
"x": 380,
"y": 280,
"type": "link",
"path": "/Applications"
},
{
"x": 110,
"y": 280,
"type": "file"
}
],
"window": {
"width": 500,
"height": 500
}
},
"win": {
"target": [
{
"target": "nsis-web",
"arch": [
"x64",
"ia32"
]
}
],
"icon": "build/icon.ico",
"publisherName": "Kandra Labs, Inc."
},
"nsis": {
"allowToChangeInstallationDirectory": true
}
},
"keywords": [
"Zulip",
"Group Chat app",
"electron-app",
"electron",
"Desktop app",
"InstantMessaging"
],
"devDependencies": {
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "3.0.10",
"electron-builder": "20.38.4",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",
"gulp": "^4.0.0",
"gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"nodemon": "^1.14.11",
"pre-commit": "1.2.2",
"spectron": "3.8.0",
"tap-colorize": "^1.2.0",
"tape": "^4.8.0",
"xo": "0.18.2"
},
"xo": {
"parserOptions": {
"sourceType": "script",
"ecmaFeatures": {
"globalReturn": true
}
},
"esnext": true,
"overrides": [
{
"files": "app*/**/*.js",
"rules": {
"max-lines": [
"warn",
{
"max": 600,
"skipBlankLines": true,
"skipComments": true
}
],
"no-warning-comments": 0,
"object-curly-spacing": 0,
"capitalized-comments": 0,
"no-else-return": 0,
"no-path-concat": 0,
"no-alert": 0,
"guard-for-in": 0,
"prefer-promise-reject-errors": 0,
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0,
"no-prototype-builtins": 0
}
}
],
"ignore": [
"tests/*.js",
"tools/locale-helper/*.js"
],
"envs": [
"node",
"browser",
"mocha"
]
}
}

View File

@@ -7,7 +7,7 @@ test('app runs', function (t) {
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.waitForExist('//*[@id="connect"]')) // id of the connect button
.then(() => app.client.waitForExist('//*[@id="new-server-container"]/div/div/div[2]/input'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

View File

@@ -1,17 +0,0 @@
const test = require('tape')
const setup = require('./setup')
// Create new org link should open in the default browser [WIP]
test('new-org-link', function (t) {
t.timeoutAfter(50e3)
setup.resetTestDataDir()
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.click('#open-create-org-link')) // Click on new org link button
.then(() => setup.wait(5000))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

View File

@@ -1,81 +0,0 @@
#!/bin/bash
set -e
usage () {
cat >&2 <<EOF
usage: $0 PULL_REQUEST_ID [REMOTE]
Force-push our HEAD to the given GitHub pull request branch.
Useful for a maintainer to run just before pushing to master,
after tweaking the branch and/or rebasing to latest. This causes
GitHub to see the subsequent push to master as representing a
merge of the PR, rather than requiring the PR to be manually
(and to the casual observer misleadingly) closed instead.
REMOTE defaults to the value of the Git config variable
\`zulip.zulipRemote\` if set, else to \`upstream\`.
See also \`reset-to-pull-request\`.
EOF
exit 1
}
remote_default="$(git config zulip.zulipRemote || echo upstream)"
pr_id="$1"
remote="${2:-"$remote_default"}"
if [ -z "$pr_id" ]; then
usage
fi
remote_url="$(git config remote."$remote".url)"
repo_fq="$(echo "$remote_url" | perl -lne 'print $1 if (
m, ^ git\@github\.com:
([^/]+ / [^/]+?)
(?:\.git)?
$ ,x )')"
if [ -z "$repo_fq" ]; then
# We're pretty specific about what we expect the URL to look like;
# there are probably more cases we could legitimately cover, which
# we can add if/when they come up for someone.
echo "error: couldn't parse remote URL as GitHub repo" >&2
exit 1
fi
# See https://developer.github.com/v3/pulls/#get-a-single-pull-request .
# This is the old REST API; the new GraphQL API does look neat, but it
# seems to require authentication even for simple lookups of public data,
# and that'd be a pain for a simple script like this.
pr_url=https://api.github.com/repos/"${repo_fq}"/pulls/"${pr_id}"
pr_details="$(curl -s "$pr_url")"
pr_jq () {
echo "$pr_details" | jq "$@"
}
if [ "$(pr_jq -r .message)" = "Not Found" ]; then
echo "Invalid PR URL: $pr_url"
exit 1
fi
if [ "$(pr_jq .maintainer_can_modify)" != "true" ]; then
# This happens when the PR has already been merged or closed, or
# if the contributor has turned off the (default) setting to allow
# maintainers of the target repo to push to their PR branch.
#
# The latter seems to be rare (in Greg's experience doing the
# manual equivalent of this script for many different
# contributors, none have ever chosen this setting), but give a
# decent error message if it does happen.
echo "error: PR already closed, or contributor has disallowed pushing to branch" >&2
exit 1
fi
pr_head_repo_fq="$(pr_jq -r .head.repo.full_name)"
pr_head_refname="$(pr_jq -r .head.ref)"
set -x
exec git push git@github.com:"$pr_head_repo_fq" +@:"$pr_head_refname"

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env node
const {exec} = require('child_process');
const path = require('path');
const isWindows = process.platform === 'win32';
const command = path.join(__dirname, `reinstall-node-modules${isWindows ? '.cmd' : ''}`);
const proc = exec(command, error => {
if (error) {
console.error(error);
}
});
proc.stdout.on('data', data => console.log(data.toString()));
proc.stderr.on('data', data => console.error(data.toString()));
proc.on('exit', code => {
process.exit(code);
});

View File

@@ -1,19 +0,0 @@
#!/bin/bash
set -e
if ! git diff-index --quiet HEAD; then
set +x
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit 1
fi
remote_default="$(git config zulip.zulipRemote || echo upstream)"
request_id="$1"
remote=${2:-"$remote_default"}
set -x
git fetch "$remote" "pull/$request_id/head"
git reset --hard FETCH_HEAD