Compare commits

..

29 Commits

Author SHA1 Message Date
Akash Nimare
018b977880 pdf-viewer: Persist webview session.
This passes the partition parameter to the pdf window
so that a user don't need to login in order to view the
pdf.
2018-08-29 23:07:36 +05:30
Akash Nimare
99c227a5f0 pdf-viewer: Show pdf files in a new window.
This adds a feature of showing the pdf attachments in a
new window so that a user can quickly view the same.

Fixes: #547.
2018-08-29 18:28:30 +05:30
Harmon
76c7f24161 settings: Update reference to toggle sidebar shortcut in general section. 2018-08-27 08:49:48 -07:00
Akash Nimare
f9c270492c Fix a typo in changelog. 2018-08-27 15:00:09 +05:30
Akash Nimare
371c580934 Add v2.3.6 release notes. 2018-08-27 14:59:11 +05:30
Akash Nimare
3eec4c2209 release: 🎉 new release v2.3.6. 2018-08-23 20:10:18 +05:30
Akash Nimare
debbfb6b7d electron: Update electron to v2.0.8. 2018-08-23 18:56:36 +05:30
Akash Nimare
8bd1492586 left-sidebar: Do not escape realm name.
We escape the realm name whenever user adds a realm + on app startup.
That's why we don't need to do the double escaping for already added
servers.

Fixes: #541.
2018-08-23 18:48:06 +05:30
Akash Nimare
1115c6d5c3 docs: Remove help docs from the repo.
Content is now maintained in the /help docs.

Fixes: #543.
2018-08-23 12:32:25 +05:30
Abhigyan Khaund
9ba279213c proxy: Add proxy details in request module parameters.
This commit adds proxy details to request module paramters from
the proxyRules so that the request module can use these rules while
sending a request. In case of no system proxy, set environment
variable NO_PROXY to handle all links.

Fixes: #534.
2018-08-21 00:26:37 +05:30
Akash Nimare
89c35cb1d4 electron: Update electron to v2.0.7. 2018-08-17 14:16:00 +05:30
Akash Nimare
21d6eb52c5 sentry: Update Sentry to v0.8.1.
This fixes the youtube video not playing in the lightbox issue.
Youtube video stopped playing in the lightbox when we added the
Sentry support. The exact reason behind the issue is still unknown
but we're guessing that previous version of Sentry doesn't exit
process on oncaught errors which might have caused lightbox to break.
The issue was fixed in the latest release of the Sentry v0.8.1.

Fixes: #537.
2018-08-14 18:02:10 +05:30
Akash Nimare
aa1538837b Add v2.3.5 release notes. 2018-08-03 17:39:48 +05:30
Akash Nimare
ea103380b6 release: 🎉 new release v2.3.5. 2018-08-02 20:27:29 +05:30
aashish-ak
60d10d88d1 preload: use page_params only when it exists.
This PR adds a params-util.js file which checks wheather the
page_params exists or not.

Fixes: #517.
2018-08-02 20:00:42 +05:30
Abhigyan Khaund
124a842bbd electron-bridge: Implement electron bridge.
This PR adds a bridge to communicate with the webapp in real time. As of now, the bridge listens for following events -

* When realm name changes
* When realm icon changes
* When the unread count changes

Partially fixes #425.
2018-08-02 01:05:29 +05:30
Akash Nimare
7130103999 file-attachment: Update notification message. 2018-08-02 00:11:47 +05:30
Akash Nimare
26a144e1c2 Merge branch 'master' of github.com:zulip/zulip-electron 2018-08-01 23:50:21 +05:30
Akash Nimare
a5c1ae8726 file-attachment: Add a setting option to show downloaded file in file manager. 2018-08-01 23:50:02 +05:30
Abhigyan Khaund
ed5096840d attachments: Fix failing attached file downloads.
This commit fixes the failing download files that had occurred due to the session not being the same in the browserWindow and the webview. This made the uploaded files unavailable to browserWindow for download. This fix adds the persist session to the browserWindow.

Fixes: #523.
2018-07-31 19:38:39 +05:30
Abhigyan Khaund
3f6d256910 gulpfile: Update syntax and methods for gulp v4.x.
This fixes the broken e2e-test.
2018-07-25 16:14:04 +05:30
Akash Nimare
28421992ba Update changelog.md 2018-07-24 19:31:25 +05:30
Akash Nimare
82199dd1c3 release: new beta release v2.3.4. 2018-07-23 23:16:18 +05:30
Akash Nimare
952baf1f42 electron: Downgrade electron to v2.0.1.
Looks like the latest release of electron has some
bugs and it's breaking the app. Downgrading until they
fix this in the upstream.
2018-07-23 23:12:03 +05:30
Abhigyan Khaund
3342d7da91 attachement: Handle downloading files failure.
In case of any failure, the app will fall back to the previous download
functionality which is to show a download dialog when a user clicks on 
file attachments.
2018-07-23 21:07:51 +05:30
Abhigyan Khaund
c0ec292090 fixes: Fix null of downloadPath when settings.json fails. 2018-07-23 21:04:49 +05:30
Akash Nimare
8f6fba0b97 electron: Update electron to v2.0.5. 2018-07-21 16:44:54 +05:30
Akash Nimare
d3453a3fe1 Update changelog.md 2018-07-14 21:31:24 +05:30
Akash Nimare
18e14fd3f8 Update changelog.md 2018-07-14 21:30:31 +05:30
21 changed files with 636 additions and 247 deletions

View File

@@ -73,7 +73,8 @@ function createMainWindow() {
minHeight: 400,
webPreferences: {
plugins: true,
nodeIntegration: true
nodeIntegration: true,
partition: 'persist:webviewsession'
},
show: false
});
@@ -175,7 +176,7 @@ app.on('ready', () => {
});
page.once('did-frame-finish-load', () => {
// Initate auto-updates on MacOS and Windows
// Initiate auto-updates on MacOS and Windows
if (ConfigUtil.getConfigItem('autoUpdate')) {
appUpdater();
}
@@ -195,6 +196,29 @@ app.on('ready', () => {
app.quit();
});
// Show pdf in a new BrowserWindow
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);
});
// Reload full app not just webview, useful in debugging
ipcMain.on('reload-full-app', () => {
mainWindow.reload();
@@ -246,11 +270,42 @@ app.on('ready', () => {
page.session.once('will-download', (event, item) => {
const filePath = path.join(downloadPath, item.getFilename());
item.setSavePath(filePath);
item.once('done', () => {
page.send('downloadFileCompleted', filePath, item.getFilename());
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 progres in percentage in future.
break;
}
default: {
console.info('Unknown updated state of download item');
}
}
});
item.once('done', (event, state) => {
if (state === 'completed') {
page.send('downloadFileCompleted', item.getSavePath(), item.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-icon-changed', (event, serverURL, iconURL) => {
page.send('update-realm-icon', serverURL, iconURL);
});
});
app.on('before-quit', () => {

View File

@@ -4,6 +4,7 @@ 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');
@@ -15,10 +16,12 @@ const logger = new Logger({
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'}
headers: {'User-Agent': 'request'},
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : ''
};
request(options, (error, response, body) => {

139
app/package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "zulip",
"version": "2.3.2",
"version": "2.3.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -37,56 +37,91 @@
}
},
"@sentry/browser": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-0.5.4.tgz",
"integrity": "sha1-Yh/5chgrc7YoVlhLkvxl/elpBMw=",
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-4.0.0-beta.12.tgz",
"integrity": "sha512-At8qp2aM4q4KnuIQuC6wbbQ6gLykmzwZdHwWRN99GS5TNC7kXJNgO9WgiMLadA4ph12JtC1petrUIgY8t+aoSA==",
"requires": {
"@sentry/core": "0.5.4",
"@sentry/shim": "0.5.4"
"@sentry/core": "4.0.0-beta.12",
"@sentry/hub": "4.0.0-beta.12",
"@sentry/minimal": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12"
}
},
"@sentry/core": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-0.5.4.tgz",
"integrity": "sha1-mwqEK0QhMbOAG65wviyk6cUQV04=",
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-4.0.0-beta.12.tgz",
"integrity": "sha512-DGZqBOHejHPYP2I0MdF7APEaews7gLrfxHDh9rbwDLJJiuwPE/ridH97amxy/j2g4X4EZEc3sO3Ptv4nMaHK+Q==",
"requires": {
"@sentry/shim": "0.5.4"
"@sentry/hub": "4.0.0-beta.12",
"@sentry/minimal": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12"
}
},
"@sentry/electron": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-0.5.5.tgz",
"integrity": "sha1-kInWNC22xr1sCSpTdHcIAx1ft8M=",
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-0.8.1.tgz",
"integrity": "sha512-y3CJ7547Rhpg+6bnGP0mXLj9pgpjMLaRji704TdzTV0afr8KamBXySNhNHosHLaKm1OLg6p8U4pdVB7xQZ+X3g==",
"requires": {
"@sentry/browser": "0.5.4",
"@sentry/core": "0.5.4",
"@sentry/node": "0.5.4",
"@sentry/shim": "0.5.4",
"@sentry/utils": "0.5.4",
"@sentry/browser": "4.0.0-beta.12",
"@sentry/core": "4.0.0-beta.12",
"@sentry/hub": "4.0.0-beta.12",
"@sentry/minimal": "4.0.0-beta.12",
"@sentry/node": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12",
"electron-fetch": "^1.1.0",
"form-data": "^2.3.2",
"util.promisify": "^1.0.0"
}
},
"@sentry/node": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-0.5.4.tgz",
"integrity": "sha1-s+c0nRs2EjmVkDbqrhnOvzVkw9M=",
"@sentry/hub": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-4.0.0-beta.12.tgz",
"integrity": "sha512-fmLaPaD6F/ViWDTz3hRVnNvqgnAmX0foDsUGVVp0Dt2lT9apM5NECKV6761XFVMrbGGulCg9fguv2KV5IbQcIw==",
"requires": {
"@sentry/core": "0.5.4",
"@sentry/shim": "0.5.4",
"raven": "^2.6.0"
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12"
}
},
"@sentry/shim": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@sentry/shim/-/shim-0.5.4.tgz",
"integrity": "sha1-y4JrGjR2WuXhsh5h3y3vL42pHcE="
"@sentry/minimal": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-4.0.0-beta.12.tgz",
"integrity": "sha512-1V3Lrm7wIOOqG+l1uyVZ7iQysPGXszwfsd6DTVDPT19PSey7+KbvPNcQfh+GqMRMz8jzyg9XlwUxckg7qbSztQ==",
"requires": {
"@sentry/hub": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12"
}
},
"@sentry/node": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-4.0.0-beta.12.tgz",
"integrity": "sha512-oekXtdaaElt/lSfyKtAyTLcTofciMRuptsGOTMX4qQ7hoHESB6DUw55Or/RRMaJYm/nckM2VFzWDATd20NVV4w==",
"requires": {
"@sentry/core": "4.0.0-beta.12",
"@sentry/hub": "4.0.0-beta.12",
"@sentry/minimal": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12",
"cookie": "0.3.1",
"lsmod": "1.0.0",
"md5": "2.2.1",
"stack-trace": "0.0.10"
}
},
"@sentry/types": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-4.0.0-beta.12.tgz",
"integrity": "sha512-xRsTfRcb8OvMbLjl64bPNv3feHiXBprtRZqTlVUbDJmSNrm8wg+tKIPYsP90dxKc50CZBk59urIbgvpPg0mCLw=="
},
"@sentry/utils": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-0.5.4.tgz",
"integrity": "sha1-jt54rOlgIW3WMv1WHIP1AGLlruE="
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-4.0.0-beta.12.tgz",
"integrity": "sha512-lpTE2GlaztATlFfIWMN8RSQFP0z6PkPPoDz0KKRmK4ZxjPxVX2sAGbKJGCpf9fdUG23NVg3FNiqfpUbjhvd9YA==",
"requires": {
"@sentry/types": "4.0.0-beta.12"
}
},
"@sindresorhus/is": {
"version": "0.7.0",
@@ -437,9 +472,9 @@
}
},
"electron-fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.1.0.tgz",
"integrity": "sha512-eBtqbB522c/RJwC5v1yF/Y0SMVLiwel98BWGx050GucWbKlejHtPkfpq/vfMxCjg0SGpHGUISYUaMfu65QH+xg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.2.1.tgz",
"integrity": "sha512-pQy0el/vxu30sL9JjXss8yZL+ztRnmioffPQsLL4UYnfSUFTuGBlgCPCxxKYJV7y52WfaIEZQ9tlbac1YHrH0w==",
"requires": {
"encoding": "^0.1.12"
}
@@ -538,9 +573,9 @@
}
},
"es-abstract": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
"integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz",
"integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==",
"requires": {
"es-to-primitive": "^1.1.1",
"function-bind": "^1.1.1",
@@ -829,9 +864,9 @@
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-callable": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
"integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI="
},
"is-date-object": {
"version": "1.0.1",
@@ -1024,6 +1059,11 @@
"yallist": "^2.1.2"
}
},
"lsmod": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz",
"integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks="
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
@@ -1309,18 +1349,6 @@
"strict-uri-encode": "^1.0.0"
}
},
"raven": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/raven/-/raven-2.6.3.tgz",
"integrity": "sha512-bKre7qlDW+y1+G2bUtCuntdDYc8o5v1T233t0vmJfbj8ttGOgLrGRlYB8saelVMW9KUAJNLrhFkAKOwFWFJonw==",
"requires": {
"cookie": "0.3.1",
"md5": "^2.2.1",
"stack-trace": "0.0.10",
"timed-out": "4.0.1",
"uuid": "3.0.0"
}
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
@@ -1600,11 +1628,6 @@
"object.getownpropertydescriptors": "^2.0.3"
}
},
"uuid": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz",
"integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg="
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.3.3",
"version": "2.3.6",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -27,7 +27,7 @@
],
"dependencies": {
"@electron-elements/send-feedback": "1.0.7",
"@sentry/electron": "0.5.5",
"@sentry/electron": "0.8.1",
"adm-zip": "0.4.11",
"auto-launch": "5.0.5",
"electron-is-dev": "0.3.0",

View File

@@ -388,8 +388,7 @@ i.open-tab-button {
.selected-css-path,
.download-folder-path {
background: #eeeeee;
padding: 10px;
margin-top: 10px;
padding: 5px 10px;
margin-right: 10px;
display: flex;
width: 90%;
@@ -565,7 +564,7 @@ input.toggle-round:checked+label:after {
.certificate-input {
width:100%;
margin-top: 10px;
margin-top: 10px;
display:inline-flex;
}

View File

@@ -10,24 +10,34 @@ function handleExternalLink(event) {
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
const downloadPath = ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`);
// Whitelist URLs which are allowed to be opened in the app
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);
isInternalUrl: isWhiteListURL,
isUploadsUrl: isUploadsURL
} = LinkUtil.isInternal(domainPrefix, url);
if (isWhiteListURL) {
event.preventDefault();
// download txt, pdf, 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
if (!LinkUtil.isImage(url) && isUploadsURL) {
// 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
if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
ipcRenderer.send('downloadFile', url, downloadPath);
ipcRenderer.once('downloadFileCompleted', (event, filePath, fileName) => {
const downloadNotification = new Notification('Download Complete', {
body: `Click to open ${fileName}`,
body: shouldShowInFolder ? `Click to show ${fileName} in folder` : `Click to open ${fileName}`,
silent: true // We'll play our own sound - ding.ogg
});
@@ -37,13 +47,27 @@ function handleExternalLink(event) {
}
downloadNotification.onclick = () => {
shell.openItem(filePath);
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.
// open internal urls inside the current webview.
this.$el.loadURL(url);
} else {
event.preventDefault();

View File

@@ -0,0 +1,39 @@
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', (...args) => {
ipcRenderer.send('realm-name-changed', ...args);
});
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

@@ -16,8 +16,6 @@ const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const Logger = require(__dirname + '/js/utils/logger-util.js');
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
const escape = require('escape-html');
const logger = new Logger({
file: 'errors.log',
timestamp: true
@@ -117,7 +115,8 @@ class ServerManagerView {
showNotification: true,
silent: false
},
downloadsPath: `${app.getPath('downloads')}`
downloadsPath: `${app.getPath('downloads')}`,
showDownloadFolder: false
};
// Platform specific settings
@@ -252,7 +251,7 @@ class ServerManagerView {
}
onHover(index, serverName) {
this.$serverIconTooltip[index].innerHTML = escape(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
@@ -396,6 +395,22 @@ class ServerManagerView {
const webContents = webview.getWebContents();
webContents.send('toggle-dnd', state, newSettings);
});
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();
});
}
});
});
}
destroyTab(name, index) {

View File

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

View File

@@ -24,7 +24,7 @@ class GeneralSection extends BaseSection {
<div class="setting-control"></div>
</div>
<div class="setting-row" id="sidebar-option">
<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+S</span>)</div>
<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+Shift+S</span>)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="badge-option">
@@ -97,6 +97,10 @@ class GeneralSection extends BaseSection {
</div>
<div class="title">Advanced</div>
<div class="settings-card">
<div class="setting-row" id="show-download-folder">
<div class="setting-description">Show downloaded file in the file manager</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="download-folder">
<div class="setting-description">
Default download location
@@ -105,9 +109,10 @@ class GeneralSection extends BaseSection {
</div>
<div class="setting-row">
<div class="setting-description">
<div class="download-folder-path">${ConfigUtil.getConfigItem('downloadsPath')}</div>
<div class="download-folder-path">${ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`)}</div>
</div>
</div>
</div>
<div class="title">Reset Application Data</div>
<div class="settings-card">
@@ -138,6 +143,7 @@ class GeneralSection extends BaseSection {
this.showCustomCSSPath();
this.removeCustomCSS();
this.downloadFolder();
this.showDownloadFolder();
// Platform specific settings
@@ -385,6 +391,18 @@ class GeneralSection extends BaseSection {
});
}
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

@@ -5,6 +5,7 @@ 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');
@@ -12,6 +13,9 @@ 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();
@@ -39,46 +43,44 @@ 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
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');
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('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');
// 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);
}
});
}
// 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');
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
// 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);
}
});
});
// Clean up spellchecker events after you navigate away from this page;

View File

@@ -10,6 +10,8 @@ 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 logger = new Logger({
file: `domain-util.log`,
@@ -119,8 +121,16 @@ class DomainUtil {
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
const checkDomain = (certificateLocation) ? ({url: domain + '/static/audio/zulip.ogg', ca: certificateLocation}) : domain + '/static/audio/zulip.ogg';
// 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) : ''
};
const serverConf = {
icon: defaultIconUrl,
@@ -193,9 +203,13 @@ class DomainUtil {
}
getServerSettings(domain) {
const serverSettingsUrl = domain + '/api/v1/server_settings';
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverSettingsOptions = {
url: domain + '/api/v1/server_settings',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : ''
};
return new Promise((resolve, reject) => {
request(serverSettingsUrl, (error, response) => {
request(serverSettingsOptions, (error, response) => {
if (!error && response.statusCode === 200) {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
@@ -215,12 +229,17 @@ class DomainUtil {
}
saveServerIcon(url) {
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverIconOptions = {
url,
proxy: proxyEnabled ? ProxyUtil.getProxy(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(url).on('response', response => {
request(serverIconOptions).on('response', response => {
response.on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);

View File

@@ -34,6 +34,12 @@ class LinkUtil {
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);
}
}
module.exports = new LinkUtil();

View File

@@ -0,0 +1,15 @@
// 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,5 +1,6 @@
'use strict';
const url = require('url');
const ConfigUtil = require('./config-util.js');
let instance = null;
@@ -15,6 +16,39 @@ class ProxyUtil {
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;

View File

@@ -4,7 +4,64 @@
All notable changes to the Zulip desktop app are documented in this file.
### 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

View File

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

105
help.md
View File

@@ -1,105 +0,0 @@
# 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

16
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "zulip",
"version": "2.3.2",
"version": "2.3.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -11,9 +11,9 @@
"dev": true
},
"@types/node": {
"version": "8.10.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.17.tgz",
"integrity": "sha512-3N3FRd/rA1v5glXjb90YdYUa+sOB7WrkU2rAhKZnF4TKD86Cym9swtulGuH0p9nxo7fP5woRNa8b0oFTpCO1bg==",
"version": "8.10.26",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.26.tgz",
"integrity": "sha512-opk6bLLErLSwyVVJeSH5Ek7ZWOBSsN0JrvXTNVGLXLAXKB9xlTYajrplR44xVyMrmbut94H6uJ9jqzM/12jxkA==",
"dev": true
},
"abbrev": {
@@ -2091,9 +2091,9 @@
"dev": true
},
"electron": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-2.0.1.tgz",
"integrity": "sha512-piSwY2P7L6NWx672MNdSvtGPdQP/mhwAg8ICN6ofTTItPkd7D6kNHBPkq+DXwZcXVH1EifYR9yD/l3Xw1haVpQ==",
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/electron/-/electron-2.0.8.tgz",
"integrity": "sha512-pbeGFbwijb5V3Xy/KMcwIp59eA9igg2br+7EHbbwQoa3HRDF5JjTrciX7OiscCA52+ze2n4q38S4lXPqRitgIA==",
"dev": true,
"requires": {
"@types/node": "^8.0.24",
@@ -2458,7 +2458,7 @@
"es6-promise": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
"integrity": "sha1-3EIhwrFlGHYL2MOaUtjzVvwA7Sk=",
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==",
"dev": true
},
"es6-set": {

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.3.3",
"version": "2.3.6",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
@@ -123,7 +123,7 @@
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "2.0.1",
"electron": "2.0.8",
"electron-builder": "20.20.4",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",

185
tests/e2e/package.json Normal file
View File

@@ -0,0 +1,185 @@
{
"name": "zulip",
"productName": "ZulipTest",
"version": "2.3.3",
"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": {
"perMachine": true,
"oneClick": false,
"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": "2.0.1",
"electron-builder": "20.20.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": 500,
"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/e2e/*.js",
"tools/locale-helper/*.js"
],
"envs": [
"node",
"browser",
"mocha"
]
}
}