Compare commits

..

35 Commits

Author SHA1 Message Date
Priyank P
c8d7a79877 design: Improve preference design. 2018-01-17 18:34:24 +05:30
akashnimare
6e6db42b54 v1.8.1 2018-01-17 02:16:04 +05:30
akashnimare
db79284fbb fix: Don't import appId from package file #386. 2018-01-17 01:54:20 +05:30
akashnimare
2434f06655 release: 🎉 new-release v1.8.0. 2018-01-15 20:59:28 +05:30
akashnimare
1d611d3382 setting-page: Update placeholder for adding new server page.
Improves #340.
2018-01-15 20:55:51 +05:30
akashnimare
a746194e9e help-menu: Add a space between app name and version. 2018-01-15 18:45:45 +05:30
akashnimare
7cc13f7a26 Update dependencies.
Updated:
electron - 1.7.10
electron-builder - 19.53.6
electron-updater - 2.18.2
2018-01-15 17:46:33 +05:30
Akash Nimare
6a9bb152a0 Update features section. 2018-01-14 01:11:18 +05:30
Priyank P
8b6dcd355f notification: Add reply option to notifications for macOS.
This PR adds reply option to notifications of macOS using
`node-mac-notifier` and then post the reply for to the webapp.
It also fixes an issue that even though the app is focused the server that sent
the notification did not focus. And it also adds parsing for mentioning. This also
refactors code for notification.

Fixes: #284, #381.
2018-01-14 00:04:34 +05:30
Priyank P
91742a5770 silent: Reflect changes in webview for silent option. (#380)
* silent: Reflect changes in webview for mute/silent option.

This silent the webview incase silent option is toggled, and
by default silent the webview when its create if needed.

Fixes: #380.
2018-01-11 18:46:39 +05:30
akashnimare
fb74251a2c performance: Disable hardware acceleration to decrease the load on GPU.
Adding this experimental electron api to see if it makes any difference
in performance.

Improves #213.
2018-01-08 02:10:43 +05:30
akashnimare
a920720f91 setting-page: Improve add new server page #340. 2018-01-07 15:10:21 +05:30
Priyank P
aa8e99b7a6 domain-util: Fix checkDomain, so it checks all error codes. (#370)
This fixes an issue where if server send non 404 error code such
as 403 forbidden we marked them as Zulip server even though they are
not, now it checks for 400 error range.
2018-01-07 00:16:52 +05:30
akashnimare
e23f8aaa58 shortcut: Rename Zoom In keyboard shortcut. 2018-01-06 16:30:59 +05:30
Akash Nimare
5c3208d44c settings: Add a setting option to start the app in background. (#366)
Fixes #314.
2017-12-29 12:37:53 +05:30
akashnimare
c0b57bbe2b settings: Set default value of flashtaskbar setting[Windows]. 2017-12-28 00:29:16 +05:30
Priyank P
afe4e8901b github templates: Add pull request template and update issue template. (#365) 2017-12-27 23:15:02 +05:30
Priyank P
231e7fd9c2 preferences page: Reflect changes in the preference page. (#362)
This updated the setting page if the sidebar was toggled using a shortcut.
This also updates the setting page if the tray was toggled using menu.

Fixes: #304.
2017-12-27 21:06:32 +05:30
Akash Nimare
a0d898a5b7 Merge pull request #363 from cPhost/logger
logger-util: Code refactoring and better logs design.
2017-12-27 20:45:13 +05:30
cPhost
1abf62555c logger-util: code refactoring and better logs design.
This imporves logging and refactors most of the code.
This also renames console-util to logger-util.
2017-12-26 22:03:05 -05:00
Akash Nimare
6befcbaa8f Merge pull request #361 from cPhost/focus-app
notifications: Focus app when a notification is triggered.
2017-12-27 01:08:32 +05:30
cPhost
e56a01049b notifications: Focus app when a notification is triggered.
This PR adds a feature of showing app window when the notification are clicked
Fixes: #358
2017-12-26 14:30:33 -05:00
akashnimare
72cb8459ff code refactoring. 2017-12-26 01:02:42 +05:30
Akash Nimare
0b83b22206 Merge pull request #353 from cPhost/fix-errors
default-util: Fix log dir errors.
2017-12-26 00:21:35 +05:30
Akash Nimare
267d25e5c4 Merge pull request #357 from cPhost/img-fix
Add default icon if the server image is not available.
2017-12-25 23:43:35 +05:30
cPhost
8401f8f5ce server icon: Load default icon if org icon is not avalible. 2017-12-25 12:17:45 -05:00
cPhost
c4a7264f34 console: Fix errors where Logs dir can't be created. 2017-12-25 09:50:52 -05:00
akashnimare
9d081ecd5a Reload full app on system hibernation.
Improves #312.
2017-12-20 02:33:08 +05:30
akashnimare
dc6582fa82 Logs: Unused Logs dir. 2017-12-19 03:42:42 +05:30
akashnimare
3b412672c6 Logs: Fix an issue where Logs dir don't get init properly. 2017-12-19 03:40:24 +05:30
Akash Nimare
04083bfa81 Merge pull request #352 from zulip/cPhost-handle-domainjson
Handle corrupted config files.
Improves #310.
2017-12-19 02:33:39 +05:30
akashnimare
562e82d2f1 test: Create logs dir on app startup. 2017-12-19 01:22:50 +05:30
cPhost
3b014e0715 settings util: delete settings.json file in case it is corrupted. 2017-12-17 15:17:08 -05:00
cPhost
13178ebc8f domain util: delete domain.json file in case it is corrupted. 2017-12-17 15:16:55 -05:00
cPhost
08693bf105 console: require app form remote if required 2017-12-17 15:00:04 -05:00
41 changed files with 655 additions and 233 deletions

8
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,8 @@
---
<!-- Please Include: -->
- **Operating System**:
- [ ] Windows
- [ ] Linux/Ubutnu
- [ ] macOS
- **Clear steps to reproduce the issue**:
- **Relevant error messages and/or screenshots**:

16
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,16 @@
---
<!--
Remove the fields that are not appropriate
Please include:
-->
**What's this PR do?**
**Any background context you want to provide?**
**Screenshots?**
**You have tested this PR on:**
- [ ] Windows
- [ ] Linux/Ubuntu
- [ ] macOS

3
.gitignore vendored
View File

@@ -26,7 +26,6 @@ yarn-error.log*
config.gypi config.gypi
# Test generated files # Test generated files
tests/e2e/package.json tests/package.json
coverage
.python-version .python-version

View File

@@ -1,7 +0,0 @@
---
Please include:
- `Operating System`
- `Clear steps to reproduce the issue`
- `Relevant error messages and/or screenshots`

View File

@@ -12,19 +12,12 @@ Please see [installation guide](https://zulipchat.com/help/desktop-app-install-g
# Features # Features
* Sign in to multiple teams * Sign in to multiple teams
* Native desktop Notifications * Desktop Notifications with inline reply support
* SpellChecker * Multilanguage SpellChecker
* OSX/Win/Linux installers * OSX/Win/Linux installers
* Automatic Updates (macOS/Windows) * Automatic Updates (macOS/Windows/Linux)
* Keyboard shortcuts * Keyboard shortcuts
Description | Keys
-----------------------| -----------------------
Default shortcuts | <kbd>Cmd/Ctrl</kbd> <kbd>k</kbd>
Manage Zulip Servers | <kbd>Cmd/Ctrl</kbd> <kbd>,</kbd>
Back | <kbd>Cmd/Ctrl</kbd> <kbd>[</kbd>
Forward | <kbd>Cmd/Ctrl</kbd> <kbd>]</kbd>
# Development # Development
Please see our [development guide](./development.md) to get started and run app locally. Please see our [development guide](./development.md) to get started and run app locally.

View File

@@ -1,5 +1,4 @@
'use strict'; 'use strict';
const fs = require('fs');
const { app, dialog } = require('electron'); const { app, dialog } = require('electron');
const { autoUpdater } = require('electron-updater'); const { autoUpdater } = require('electron-updater');
const isDev = require('electron-is-dev'); const isDev = require('electron-is-dev');
@@ -15,10 +14,6 @@ function appUpdater() {
// Create Logs directory // Create Logs directory
const LogsDir = `${app.getPath('userData')}/Logs`; const LogsDir = `${app.getPath('userData')}/Logs`;
if (!fs.existsSync(LogsDir)) {
fs.mkdirSync(LogsDir);
}
// Log whats happening // Log whats happening
const log = require('electron-log'); const log = require('electron-log');

View File

@@ -12,6 +12,7 @@ const { setAutoLaunch } = require('./startup');
const { app, ipcMain } = electron; const { app, ipcMain } = electron;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js'); const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
// Adds debug features like hotkeys for triggering dev tools and reload // Adds debug features like hotkeys for triggering dev tools and reload
// in development mode // in development mode
@@ -81,7 +82,11 @@ function createMainWindow() {
}); });
win.once('ready-to-show', () => { win.once('ready-to-show', () => {
win.show(); if (ConfigUtil.getConfigItem('startMinimized')) {
win.minimize();
} else {
win.show();
}
}); });
win.loadURL(mainURL); win.loadURL(mainURL);
@@ -124,6 +129,9 @@ function createMainWindow() {
return win; return win;
} }
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
// eslint-disable-next-line max-params // eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault(); event.preventDefault();
@@ -145,7 +153,11 @@ app.on('ready', () => {
const page = mainWindow.webContents; const page = mainWindow.webContents;
page.on('dom-ready', () => { page.on('dom-ready', () => {
mainWindow.show(); if (ConfigUtil.getConfigItem('startMinimized')) {
mainWindow.minimize();
} else {
mainWindow.show();
}
}); });
page.once('did-frame-finish-load', () => { page.once('did-frame-finish-load', () => {
@@ -155,7 +167,8 @@ app.on('ready', () => {
}); });
electron.powerMonitor.on('resume', () => { electron.powerMonitor.on('resume', () => {
page.send('reload-viewer'); mainWindow.reload();
page.send('destroytray');
}); });
ipcMain.on('focus-app', () => { ipcMain.on('focus-app', () => {

View File

@@ -54,7 +54,7 @@ class AppMenu {
role: 'togglefullscreen' role: 'togglefullscreen'
}, { }, {
label: 'Zoom In', label: 'Zoom In',
accelerator: 'CommandOrControl+=', accelerator: 'CommandOrControl+Plus',
click(item, focusedWindow) { click(item, focusedWindow) {
if (focusedWindow) { if (focusedWindow) {
AppMenu.sendAction('zoomIn'); AppMenu.sendAction('zoomIn');
@@ -121,7 +121,7 @@ class AppMenu {
shell.openExternal('https://zulipchat.com/help/'); shell.openExternal('https://zulipchat.com/help/');
} }
}, { }, {
label: `${appName + 'Desktop'} - ${app.getVersion()}`, label: `${appName + ' Desktop'} - ${app.getVersion()}`,
enabled: false enabled: false
}, { }, {
label: 'Report an Issue...', label: 'Report an Issue...',

31
app/package-lock.json generated
View File

@@ -101,6 +101,12 @@
"tweetnacl": "0.14.5" "tweetnacl": "0.14.5"
} }
}, },
"bindings": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz",
"integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==",
"optional": true
},
"bluebird": { "bluebird": {
"version": "3.5.1", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
@@ -318,6 +324,12 @@
"resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.4.0.tgz", "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.4.0.tgz",
"integrity": "sha512-ZXd9jxUoc/f/zdLdR3OUcCzT84WnpaNWefquLyE125akIC90sDs8S3T/qihliuVsaj7Osc0z8lLL2fjooE9Z4A==" "integrity": "sha512-ZXd9jxUoc/f/zdLdR3OUcCzT84WnpaNWefquLyE125akIC90sDs8S3T/qihliuVsaj7Osc0z8lLL2fjooE9Z4A=="
}, },
"event-target-shim": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.1.1.tgz",
"integrity": "sha1-qG5e5r2qFgVEddp5fM3fDFVphJE=",
"optional": true
},
"extend": { "extend": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
@@ -622,6 +634,25 @@
"mkdirp": "0.5.1" "mkdirp": "0.5.1"
} }
}, },
"node-mac-notifier": {
"version": "0.0.13",
"resolved": "https://registry.npmjs.org/node-mac-notifier/-/node-mac-notifier-0.0.13.tgz",
"integrity": "sha1-1kt27RgfR5XURFui060Nb3KY9+I=",
"optional": true,
"requires": {
"bindings": "1.3.0",
"event-target-shim": "1.1.1",
"node-uuid": "1.4.8"
},
"dependencies": {
"node-uuid": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=",
"optional": true
}
}
},
"oauth-sign": { "oauth-sign": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",

View File

@@ -1,7 +1,7 @@
{ {
"name": "zulip", "name": "zulip",
"productName": "Zulip", "productName": "Zulip",
"version": "1.7.0", "version": "1.8.1",
"description": "Zulip Desktop App", "description": "Zulip Desktop App",
"license": "Apache-2.0", "license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.", "copyright": "Kandra Labs, Inc.",
@@ -30,10 +30,13 @@
"electron-is-dev": "0.3.0", "electron-is-dev": "0.3.0",
"electron-log": "2.2.7", "electron-log": "2.2.7",
"electron-spellchecker": "1.1.2", "electron-spellchecker": "1.1.2",
"electron-updater": "2.18.2",
"electron-window-state": "4.1.1", "electron-window-state": "4.1.1",
"electron-updater": "2.16.2",
"node-json-db": "0.7.3", "node-json-db": "0.7.3",
"request": "2.81.0", "request": "2.81.0",
"wurl": "2.5.0" "wurl": "2.5.0"
},
"optionalDependencies": {
"node-mac-notifier": "0.0.13"
} }
} }

View File

@@ -263,7 +263,13 @@ img.server-info-icon {
margin: 10px 0 20px 0; margin: 10px 0 20px 0;
background: #fff; background: #fff;
width: 70%; width: 70%;
transition: all 0.2s;
}
.settings-card:hover {
border-left: 8px solid #bcbcbc; 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);
} }
.hidden { .hidden {

View File

@@ -4,7 +4,7 @@ const Tab = require(__dirname + '/../components/tab.js');
class FunctionalTab extends Tab { class FunctionalTab extends Tab {
template() { template() {
return `<div class="tab functional-tab"> return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tab-badge close-button"> <div class="server-tab-badge close-button">
<i class="material-icons">close</i> <i class="material-icons">close</i>
</div> </div>

View File

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

View File

@@ -4,12 +4,14 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const DomainUtil = require(__dirname + '/../utils/domain-util.js'); 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 SystemUtil = require(__dirname + '/../utils/system-util.js');
const LinkUtil = require(__dirname + '/../utils/link-util.js'); const LinkUtil = require(__dirname + '/../utils/link-util.js');
const { shell, app } = require('electron').remote; const { shell, app } = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js'); const BaseComponent = require(__dirname + '/../components/base.js');
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
class WebView extends BaseComponent { class WebView extends BaseComponent {
constructor(props) { constructor(props) {
super(); super();
@@ -24,6 +26,7 @@ class WebView extends BaseComponent {
template() { template() {
return `<webview return `<webview
class="disabled" class="disabled"
data-tab-id="${this.props.tabIndex}"
src="${this.props.url}" src="${this.props.url}"
${this.props.nodeIntegration ? 'nodeIntegration' : ''} ${this.props.nodeIntegration ? 'nodeIntegration' : ''}
disablewebsecurity disablewebsecurity
@@ -54,6 +57,12 @@ class WebView extends BaseComponent {
} }
}); });
if (shouldSilentWebview) {
this.$el.addEventListener('dom-ready', () => {
this.$el.setAudioMuted(true);
});
}
this.$el.addEventListener('page-title-updated', event => { this.$el.addEventListener('page-title-updated', event => {
const { title } = event; const { title } = event;
this.badgeCount = this.getBadgeCount(title); this.badgeCount = this.getBadgeCount(title);

View File

@@ -1,69 +0,0 @@
const NodeConsole = require('console').Console;
const fs = require('fs');
const { app } = require('electron').remote;
const isDev = require('electron-is-dev');
const browserConsole = console;
const logDir = `${app.getPath('userData')}/Logs`;
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
function customConsole(opts, type, ...args) {
const { nodeConsole, timestamp } = opts;
if (timestamp) {
args.unshift(timestamp());
}
if (!isDev) {
const nodeConsoleLog = nodeConsole[type] || nodeConsole.log;
nodeConsoleLog.apply(null, args);
}
browserConsole[type].apply(null, args);
}
function getTimestamp() {
const date = new Date();
const timestamp =
`${date.getMonth()}/${date.getDate()} ` +
`${date.getMinutes()}:${date.getSeconds()}`;
return timestamp;
}
function setConsoleProto(type) {
Object.defineProperty(this, type, {
value(...args) {
const { timestamp, nodeConsole } = this;
const opts = {
timestamp,
nodeConsole
};
customConsole.apply(null, [].concat(opts, type, args));
}
});
}
class Console {
constructor(opts = {}) {
let { timestamp, file } = opts;
file = `${logDir}/${file || 'console.log'}`;
if (timestamp === true) {
timestamp = getTimestamp;
}
const fileStream = fs.createWriteStream(file);
const nodeConsole = new NodeConsole(fileStream);
this.nodeConsole = nodeConsole;
this.timestamp = timestamp;
this.setUpConsole();
}
setUpConsole() {
for (const type in browserConsole) {
setConsoleProto.call(this, type);
}
}
}
module.exports = Console;

View File

@@ -1,10 +1,10 @@
'use strict'; 'use strict';
require(__dirname + '/js/tray.js');
const { ipcRenderer, remote } = require('electron'); const { ipcRenderer, remote } = require('electron');
const { session } = remote; const { session } = remote;
require(__dirname + '/js/tray.js');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js'); const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
const WebView = require(__dirname + '/js/components/webview.js'); const WebView = require(__dirname + '/js/components/webview.js');
const ServerTab = require(__dirname + '/js/components/server-tab.js'); const ServerTab = require(__dirname + '/js/components/server-tab.js');
@@ -35,6 +35,7 @@ class ServerManagerView {
this.activeTabIndex = -1; this.activeTabIndex = -1;
this.tabs = []; this.tabs = [];
this.functionalTabs = {}; this.functionalTabs = {};
this.tabIndex = 0;
} }
init() { init() {
@@ -77,6 +78,7 @@ class ServerManagerView {
showSidebar: true, showSidebar: true,
badgeOption: true, badgeOption: true,
startAtLogin: false, startAtLogin: false,
startMinimized: false,
enableSpellchecker: true, enableSpellchecker: true,
showNotification: true, showNotification: true,
betaUpdate: false, betaUpdate: false,
@@ -84,6 +86,13 @@ class ServerManagerView {
lastActiveTab: 0 lastActiveTab: 0
}; };
// Platform specific settings
if (process.platform === 'win32') {
// Only available on Windows
settingOptions.flashTaskbarOnMessage = true;
}
for (const i in settingOptions) { for (const i in settingOptions) {
if (ConfigUtil.getConfigItem(i) === null) { if (ConfigUtil.getConfigItem(i) === null) {
ConfigUtil.setConfigItem(i, settingOptions[i]); ConfigUtil.setConfigItem(i, settingOptions[i]);
@@ -112,17 +121,20 @@ class ServerManagerView {
} }
initServer(server, index) { initServer(server, index) {
const tabIndex = this.getTabIndex();
this.tabs.push(new ServerTab({ this.tabs.push(new ServerTab({
role: 'server', role: 'server',
icon: server.icon, icon: server.icon,
$root: this.$tabsContainer, $root: this.$tabsContainer,
onClick: this.activateLastTab.bind(this, index), onClick: this.activateLastTab.bind(this, index),
index, index,
tabIndex,
onHover: this.onHover.bind(this, index, server.alias), onHover: this.onHover.bind(this, index, server.alias),
onHoverOut: this.onHoverOut.bind(this, index), onHoverOut: this.onHoverOut.bind(this, index),
webview: new WebView({ webview: new WebView({
$root: this.$webviewsContainer, $root: this.$webviewsContainer,
index, index,
tabIndex,
url: server.url, url: server.url,
name: server.alias, name: server.alias,
isActive: () => { isActive: () => {
@@ -147,11 +159,24 @@ class ServerManagerView {
this.openSettings('General'); this.openSettings('General');
}); });
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.$addServerButton, this.$addServerTooltip);
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip); this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip); this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
} }
getTabIndex() {
const currentIndex = this.tabIndex;
this.tabIndex++;
return currentIndex;
}
sidebarHoverEvent(SidebarButton, SidebarTooltip) { sidebarHoverEvent(SidebarButton, SidebarTooltip) {
SidebarButton.addEventListener('mouseover', () => { SidebarButton.addEventListener('mouseover', () => {
SidebarTooltip.removeAttribute('style'); SidebarTooltip.removeAttribute('style');
@@ -178,16 +203,19 @@ class ServerManagerView {
this.functionalTabs[tabProps.name] = this.tabs.length; this.functionalTabs[tabProps.name] = this.tabs.length;
const tabIndex = this.getTabIndex();
this.tabs.push(new FunctionalTab({ this.tabs.push(new FunctionalTab({
role: 'function', role: 'function',
materialIcon: tabProps.materialIcon, materialIcon: tabProps.materialIcon,
$root: this.$tabsContainer, $root: this.$tabsContainer,
index: this.functionalTabs[tabProps.name], index: this.functionalTabs[tabProps.name],
tabIndex,
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]), onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]), onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
webview: new WebView({ webview: new WebView({
$root: this.$webviewsContainer, $root: this.$webviewsContainer,
index: this.functionalTabs[tabProps.name], index: this.functionalTabs[tabProps.name],
tabIndex,
url: tabProps.url, url: tabProps.url,
name: tabProps.name, name: tabProps.name,
isActive: () => { isActive: () => {
@@ -255,6 +283,27 @@ class ServerManagerView {
tabs: this.tabs, tabs: this.tabs,
activeTabIndex: this.activeTabIndex 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();
});
}
});
});
} }
destroyTab(name, index) { destroyTab(name, index) {
@@ -387,6 +436,18 @@ class ServerManagerView {
this.$fullscreenPopup.classList.remove('show'); this.$fullscreenPopup.classList.remove('show');
}); });
ipcRenderer.on('focus-webview-with-id', (event, webviewId) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
const currentId = webview.getWebContents().id;
const tabId = webview.getAttribute('data-tab-id');
const concurrentTab = document.querySelector(`div[data-tab-id="${tabId}"]`);
if (currentId === webviewId) {
concurrentTab.click();
}
});
});
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => { ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
// Create a canvas from unread messagecounts // Create a canvas from unread messagecounts
function createOverlayIcon(messageCount) { function createOverlayIcon(messageCount) {

View File

@@ -1,30 +0,0 @@
'use strict';
const { remote } = require('electron');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const app = remote.app;
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
app.setAppUserModelId('org.zulip.zulip-electron');
const NativeNotification = window.Notification;
class baseNotification extends NativeNotification {
constructor(title, opts) {
opts.silent = ConfigUtil.getConfigItem('silent') || false;
super(title, opts);
}
static requestPermission() {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission() {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
}
window.Notification = baseNotification;

View File

@@ -0,0 +1,100 @@
'use strict';
const { ipcRenderer } = require('electron');
const url = require('url');
const MacNotifier = require('node-mac-notifier');
const ConfigUtil = require('../utils/config-util');
const {
appId, customReply, focusCurrentServer, parseReply, setupReply
} = require('./helpers');
let replyHandler;
let clickHandler;
class DarwinNotification {
constructor(title, opts) {
const silent = ConfigUtil.getConfigItem('silent') || false;
const { host, protocol } = location;
const { icon } = opts;
const profilePic = url.resolve(`${protocol}//${host}`, icon);
this.tag = opts.tag;
const notification = new MacNotifier(title, Object.assign(opts, {
bundleId: appId,
canReply: true,
silent,
icon: profilePic
}));
notification.addEventListener('click', () => {
// focus to the server who sent the
// notification if not focused already
if (clickHandler) {
clickHandler();
}
focusCurrentServer();
ipcRenderer.send('focus-app');
});
notification.addEventListener('reply', this.notificationHandler);
}
static requestPermission() {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission() {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
set onreply(handler) {
replyHandler = handler;
}
get onreply() {
return replyHandler;
}
set onclick(handler) {
clickHandler = handler;
}
get onclick() {
return clickHandler;
}
// not something that is common or
// used by zulip server but added to be
// future proff.
addEventListener(event, handler) {
if (event === 'click') {
clickHandler = handler;
}
if (event === 'reply') {
replyHandler = handler;
}
}
notificationHandler({ response }) {
response = parseReply(response);
focusCurrentServer();
setupReply(this.tag);
if (replyHandler) {
replyHandler(response);
return;
}
customReply(response);
}
// method specific to notification api
// used by zulip
close() {
return; // eslint-disable-line no-useless-return
}
}
module.exports = DarwinNotification;

View File

@@ -0,0 +1,31 @@
'use strict';
const { ipcRenderer } = require('electron');
const ConfigUtil = require('../utils/config-util');
const { focusCurrentServer } = require('./helpers');
const NativeNotification = window.Notification;
class BaseNotification extends NativeNotification {
constructor(title, opts) {
opts.silent = true;
super(title, opts);
this.addEventListener('click', () => {
// focus to the server who sent the
// notification if not focused already
focusCurrentServer();
ipcRenderer.send('focus-app');
});
}
static requestPermission() {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission() {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
}
module.exports = BaseNotification;

View File

@@ -0,0 +1,102 @@
const { remote } = require('electron');
// Do not change this
const appId = 'org.zulip.zulip-electron';
function checkElements(...elements) {
let status = true;
elements.forEach(element => {
if (element === null || element === undefined) {
status = false;
}
});
return status;
}
function customReply(reply) {
// server does not support notification reply yet.
const buttonSelector = '.messagebox #send_controls button[type=submit]';
const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content';
const textarea = document.querySelector('#compose-textarea');
const messagebox = document.querySelector(messageboxSelector);
const sendButton = document.querySelector(buttonSelector);
// sanity check for old server versions
const elementsExists = checkElements(textarea, messagebox, sendButton);
if (!elementsExists) {
return;
}
textarea.value = reply;
messagebox.click();
sendButton.click();
}
const currentWindow = remote.getCurrentWindow();
const webContents = remote.getCurrentWebContents();
const webContentsId = webContents.id;
// this function will focus the server that sent
// the notification. Main function implemented in main.js
function focusCurrentServer() {
currentWindow.send('focus-webview-with-id', webContentsId);
}
// this function parses the reply from to notification
// making it easier to reply from notification eg
// @username in reply will be converted to @**username**
// #stream in reply will be converted to #**stream**
// bot mentions are not yet supported
function parseReply(reply) {
const usersDiv = document.querySelectorAll('#user_presences li');
const streamHolder = document.querySelectorAll('#stream_filters li');
const users = [];
const streams = [];
usersDiv.forEach(userRow => {
const anchor = userRow.querySelector('span a');
if (anchor !== null) {
const user = `@${anchor.textContent.trim()}`;
const mention = `@**${user.replace(/^@/, '')}**`;
users.push([user, mention]);
}
});
streamHolder.forEach(stream => {
const streamAnchor = stream.querySelector('div a');
if (streamAnchor !== null) {
const streamName = `#${streamAnchor.textContent.trim()}`;
const streamMention = `#**${streamName.replace(/^#/, '')}**`;
streams.push([streamName, streamMention]);
}
});
users.forEach(([user, mention]) => {
if (reply.includes(user)) {
const regex = new RegExp(user, 'g');
reply = reply.replace(regex, mention);
}
});
streams.forEach(([stream, streamMention]) => {
const regex = new RegExp(stream, 'g');
reply = reply.replace(regex, streamMention);
});
reply = reply.replace(/\\n/, '\n');
return reply;
}
function setupReply(id) {
const { narrow } = window;
narrow.by_subject(id, { trigger: 'notification' });
}
module.exports = {
appId,
checkElements,
customReply,
parseReply,
setupReply,
focusCurrentServer
};

View File

@@ -0,0 +1,18 @@
'use strict';
const {
remote: { app }
} = require('electron');
const DefaultNotification = require('./default-notification');
const { appId } = require('./helpers');
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
app.setAppUserModelId(appId);
window.Notification = DefaultNotification;
if (process.platform === 'darwin') {
const DarwinNotification = require('./darwin-notifications');
window.Notification = DarwinNotification;
}

View File

@@ -1,11 +1,10 @@
'use strict'; 'use strict';
const path = require('path'); const path = require('path');
const { ipcRenderer, remote } = require('electron');
const { ipcRenderer } = require('electron');
const { app, dialog } = require('electron').remote;
const fs = require('fs-extra'); const fs = require('fs-extra');
const { app, dialog } = remote;
const currentBrowserWindow = remote.getCurrentWindow();
const BaseSection = require(__dirname + '/base-section.js'); const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js'); const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
@@ -61,10 +60,14 @@ class GeneralSection extends BaseSection {
<div class="setting-description">Start app at login</div> <div class="setting-description">Start app at login</div>
<div class="setting-control"></div> <div class="setting-control"></div>
</div> </div>
<div class="setting-row" id="start-minimize-option">
<div class="setting-description">Always start minimized</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="enable-spellchecker-option"> <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 class="setting-control"></div>
</div> </div>
</div> </div>
<div class="title">Reset Application Data</div> <div class="title">Reset Application Data</div>
<div class="settings-card"> <div class="settings-card">
@@ -89,6 +92,7 @@ class GeneralSection extends BaseSection {
this.updateResetDataOption(); this.updateResetDataOption();
this.showDesktopNotification(); this.showDesktopNotification();
this.enableSpellchecker(); this.enableSpellchecker();
this.minimizeOnStart();
// Platform specific settings // Platform specific settings
// Flashing taskbar on Windows // Flashing taskbar on Windows
@@ -155,6 +159,7 @@ class GeneralSection extends BaseSection {
const newValue = !ConfigUtil.getConfigItem('silent', true); const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue); ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption(); this.updateSilentOption();
currentBrowserWindow.send('toogle-silent', newValue);
} }
}); });
} }
@@ -234,6 +239,18 @@ class GeneralSection extends BaseSection {
}); });
} }
minimizeOnStart() {
this.generateSettingOption({
$element: document.querySelector('#start-minimize-option .setting-control'),
value: ConfigUtil.getConfigItem('startMinimized', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('startMinimized');
ConfigUtil.setConfigItem('startMinimized', newValue);
this.minimizeOnStart();
}
});
}
} }
module.exports = GeneralSection; module.exports = GeneralSection;

View File

@@ -13,9 +13,9 @@ class NewServerForm extends BaseComponent {
return ` return `
<div class="settings-card"> <div class="settings-card">
<div class="server-info-right"> <div class="server-info-right">
<div class="title">URL of your Zulip organization</div> <div class="title">URL of Zulip organization</div>
<div class="server-info-row"> <div class="server-info-row">
<input class="setting-input-value" autofocus placeholder="acme.zulipchat.com"/> <input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or chat.your-organization.com"/>
</div> </div>
<div class="server-info-row"> <div class="server-info-row">
<div class="action blue server-save-action"> <div class="action blue server-save-action">

View File

@@ -73,6 +73,18 @@ class PreferenceView extends BaseComponent {
ipcRenderer.on('switch-settings-nav', (event, navItem) => { ipcRenderer.on('switch-settings-nav', (event, navItem) => {
this.handleNavigation(navItem); this.handleNavigation(navItem);
}); });
ipcRenderer.on('toggle-sidebar', (event, state) => {
const inputSelector = '#sidebar-option .action .switch input';
const input = document.querySelector(inputSelector);
input.checked = state;
});
ipcRenderer.on('toggletray', (event, state) => {
const inputSelector = '#tray-option .action .switch input';
const input = document.querySelector(inputSelector);
input.checked = state;
});
} }
} }

View File

@@ -197,13 +197,16 @@ ipcRenderer.on('tray', (event, arg) => {
}); });
function toggleTray() { function toggleTray() {
let state;
if (window.tray) { if (window.tray) {
state = false;
window.tray.destroy(); window.tray.destroy();
if (window.tray.isDestroyed()) { if (window.tray.isDestroyed()) {
window.tray = null; window.tray = null;
} }
ConfigUtil.setConfigItem('trayIcon', false); ConfigUtil.setConfigItem('trayIcon', false);
} else { } else {
state = true;
createTray(); createTray();
if (process.platform === 'linux' || process.platform === 'win32') { if (process.platform === 'linux' || process.platform === 'win32') {
renderNativeImage(unread).then(image => { renderNativeImage(unread).then(image => {
@@ -213,6 +216,10 @@ function toggleTray() {
} }
ConfigUtil.setConfigItem('trayIcon', true); ConfigUtil.setConfigItem('trayIcon', true);
} }
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggletray', state);
} }
ipcRenderer.on('toggletray', toggleTray); ipcRenderer.on('toggletray', toggleTray);

View File

@@ -1,16 +1,29 @@
'use strict'; 'use strict';
const fs = require('fs');
const path = require('path');
const process = require('process'); const process = require('process');
const JsonDB = require('node-json-db'); const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const logger = new Logger({
file: 'config-util.log',
timestamp: true
});
let instance = null; let instance = null;
let dialog = null;
let app = null; let app = null;
/* To make the util runnable in both main and renderer process */ /* To make the util runnable in both main and renderer process */
if (process.type === 'renderer') { if (process.type === 'renderer') {
app = require('electron').remote.app; const remote = require('electron').remote;
dialog = remote.dialog;
app = remote.app;
} else { } else {
app = require('electron').app; const electron = require('electron');
dialog = electron.dialog;
app = electron.app;
} }
class ConfigUtil { class ConfigUtil {
@@ -47,7 +60,22 @@ class ConfigUtil {
} }
reloadDB() { reloadDB() {
this.db = new JsonDB(app.getPath('userData') + '/settings.json', true, true); const settingsJsonPath = path.join(app.getPath('userData'), '/settings.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 current settings.'
);
logger.error('Error while JSON parsing settings.json: ');
logger.error(err);
}
}
this.db = new JsonDB(settingsJsonPath, true, true);
} }
} }

View File

@@ -0,0 +1,31 @@
const fs = require('fs');
let app = null;
let setupCompleted = false;
if (process.type === 'renderer') {
app = require('electron').remote.app;
} else {
app = require('electron').app;
}
const zulipDir = app.getPath('userData');
const logDir = `${zulipDir}/Logs/`;
const initSetUp = () => {
// if it is the first time the app is running
// create zulip dir in userData folder to
// avoid errors
if (!setupCompleted) {
if (!fs.existsSync(zulipDir)) {
fs.mkdirSync(zulipDir);
}
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
setupCompleted = true;
}
};
module.exports = {
initSetUp
};

View File

@@ -5,6 +5,12 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const JsonDB = require('node-json-db'); const JsonDB = require('node-json-db');
const request = require('request'); const request = require('request');
const Logger = require('./logger-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
let instance = null; let instance = null;
@@ -115,11 +121,16 @@ class DomainUtil {
'Error: unable to verify the first certificate', 'Error: unable to verify the first certificate',
'Error: unable to get local issuer certificate' 'Error: unable to get local issuer certificate'
]; ];
// If the domain contains following strings we just bypass the server // If the domain contains following strings we just bypass the server
const whitelistDomains = [ const whitelistDomains = [
'zulipdev.org' 'zulipdev.org'
]; ];
if (!error && response.statusCode !== 404) {
// make sure that error is a error or string not undefined
// so validation does not throw error.
error = error || '';
if (!error && response.statusCode < 400) {
// Correct // Correct
this.getServerSettings(domain).then(serverSettings => { this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings); resolve(serverSettings);
@@ -159,7 +170,7 @@ class DomainUtil {
} }
} else { } else {
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \ 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(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); reject(invalidZulipServerError);
} }
}); });
@@ -225,7 +236,23 @@ class DomainUtil {
} }
reloadDB() { reloadDB() {
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); const domainJsonPath = path.join(app.getPath('userData'), '/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(domainJsonPath)) {
fs.unlinkSync(domainJsonPath);
dialog.showErrorBox(
'Error saving new organization',
'There seems to be error while saving new organization, ' +
'you may have to re-add your previous organizations back.'
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
}
}
this.db = new JsonDB(domainJsonPath, true, true);
} }
generateFilePath(url) { generateFilePath(url) {

View File

@@ -0,0 +1,87 @@
const NodeConsole = require('console').Console;
const fs = require('fs');
const isDev = require('electron-is-dev');
const { initSetUp } = require('./default-util');
initSetUp();
let app = null;
if (process.type === 'renderer') {
app = require('electron').remote.app;
} else {
app = require('electron').app;
}
const browserConsole = console;
const logDir = `${app.getPath('userData')}/Logs`;
class Logger {
constructor(opts = {}) {
let {
timestamp = true,
file = 'console.log',
level = true,
logInDevMode = false
} = opts;
file = `${logDir}/${file}`;
if (timestamp === true) {
timestamp = this.getTimestamp;
}
const fileStream = fs.createWriteStream(file, { flags: 'a' });
const nodeConsole = new NodeConsole(fileStream);
this.nodeConsole = nodeConsole;
this.timestamp = timestamp;
this.level = level;
this.logInDevMode = logInDevMode;
this.setUpConsole();
}
_log(type, ...args) {
const {
nodeConsole, timestamp, level, logInDevMode
} = this;
let nodeConsoleLog;
/* eslint-disable no-fallthrough */
switch (true) {
case typeof timestamp === 'function':
args.unshift(timestamp() + ' |\t');
case (level !== false):
args.unshift(type.toUpperCase() + ' |');
case isDev || logInDevMode:
nodeConsoleLog = nodeConsole[type] || nodeConsole.log;
nodeConsoleLog.apply(null, args);
default: break;
}
/* eslint-enable no-fallthrough */
browserConsole[type].apply(null, args);
}
setUpConsole() {
for (const type in browserConsole) {
this.setupConsoleMethod(type);
}
}
setupConsoleMethod(type) {
this[type] = (...args) => {
this._log(type, ...args);
};
}
getTimestamp() {
const date = new Date();
const timestamp =
`${date.getMonth()}/${date.getDate()} ` +
`${date.getMinutes()}:${date.getSeconds()}`;
return timestamp;
}
}
module.exports = Logger;

View File

@@ -20,4 +20,5 @@ install:
build: off build: off
test_script: test_script:
- npm run test-all - npm run test
- npm run test-e2e

View File

@@ -30,7 +30,7 @@ gulp.task('reload:renderer', done => {
}); });
gulp.task('test-e2e', () => { gulp.task('test-e2e', () => {
return gulp.src('tests/e2e/*.js') return gulp.src('tests/*.js')
.pipe(tape({ .pipe(tape({
reporter: tapColorize() reporter: tapColorize()
})); }));

View File

@@ -1,21 +0,0 @@
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
browsers: ['Electron'],
preprocessors: {
'**/*.js': ['electron']
},
files: [
{pattern: './karma.shim.js', watched: true, included: true, served: true},
{pattern: './tests/unit/*.js', watched: true, included: true, served: true},
{pattern: './app/renderer/**/*.js', watched: true, included: false, served: true}
],
reporters: ['mocha'],
client: {
captureConsole: true,
useIframe: false
},
singleRun: true
});
};

View File

@@ -1,5 +0,0 @@
window.require = window.parent.require;
window.process = window.parent.process;
window.__dirname = window.parent.__dirname;
require('module').globalPaths.push('./node_modules');
require('module').globalPaths.push('./app/renderer');

11
package-lock.json generated
View File

@@ -1561,9 +1561,9 @@
"dev": true "dev": true
}, },
"electron": { "electron": {
"version": "1.6.15", "version": "1.7.9",
"resolved": "https://registry.npmjs.org/electron/-/electron-1.6.15.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-1.7.9.tgz",
"integrity": "sha1-w07FRIa39Jpm21jG8koJKEFPrqc=", "integrity": "sha1-rdVOn4+D7QL2UZ7BATX2mLGTNs8=",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "7.0.48", "@types/node": "7.0.48",
@@ -5801,11 +5801,6 @@
"resolve-from": "1.0.1" "resolve-from": "1.0.1"
} }
}, },
"resemblejs": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/resemblejs/-/resemblejs-2.5.0.tgz",
"integrity": "sha512-j2p4H8jpxch3bG9SOoap4tWbNZ4hNGrl54y7WJSwoKBQM3ZBhVyaFEYqzlv06dY1I4Pns/M8Ten6R6+/jTjycA=="
},
"resolve": { "resolve": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",

View File

@@ -1,7 +1,7 @@
{ {
"name": "zulip", "name": "zulip",
"productName": "Zulip", "productName": "Zulip",
"version": "1.7.0", "version": "1.8.1",
"main": "./app/main", "main": "./app/main",
"description": "Zulip Desktop App", "description": "Zulip Desktop App",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -21,10 +21,8 @@
"start": "electron app --disable-http-cache", "start": "electron app --disable-http-cache",
"reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install", "reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"lint": "xo", "test": "xo",
"test-e2e": "gulp test-e2e", "test-e2e": "gulp test-e2e",
"test-unit": "karma start karma.conf.js",
"test-all": "xo && npm run test-unit && npm run test-e2e",
"dev": "gulp dev", "dev": "gulp dev",
"pack": "electron-builder --dir", "pack": "electron-builder --dir",
"dist": "electron-builder", "dist": "electron-builder",
@@ -32,7 +30,7 @@
"travis": "cd ./scripts && ./travis-build-test.sh" "travis": "cd ./scripts && ./travis-build-test.sh"
}, },
"pre-commit": [ "pre-commit": [
"lint" "test"
], ],
"build": { "build": {
"appId": "org.zulip.zulip-electron", "appId": "org.zulip.zulip-electron",
@@ -111,24 +109,17 @@
"assert": "1.4.1", "assert": "1.4.1",
"cp-file": "^5.0.0", "cp-file": "^5.0.0",
"devtron": "1.4.0", "devtron": "1.4.0",
"electron-builder": "19.46.4", "electron-builder": "19.53.6",
"electron": "1.7.9", "electron": "1.7.10",
"electron-connect": "0.6.2", "electron-connect": "0.6.2",
"electron-debug": "1.4.0", "electron-debug": "1.4.0",
"gulp": "3.9.1", "gulp": "3.9.1",
"gulp-tape": "0.0.9", "gulp-tape": "0.0.9",
"is-ci": "^1.0.10", "is-ci": "^1.0.10",
"jasmine": "^2.8.0",
"karma": "^1.7.1",
"karma-electron": "^5.2.2",
"karma-electron-launcher": "^0.2.0",
"karma-jasmine": "^1.1.0",
"karma-mocha-reporter": "^2.2.5",
"pre-commit": "1.2.2", "pre-commit": "1.2.2",
"spectron": "3.7.2", "spectron": "3.7.2",
"tap-colorize": "^1.2.0", "tap-colorize": "^1.2.0",
"tape": "^4.8.0", "tape": "^4.8.0",
"watchify": "^3.9.0",
"xo": "0.18.2" "xo": "0.18.2"
}, },
"xo": { "xo": {
@@ -162,8 +153,7 @@
} }
], ],
"ignore": [ "ignore": [
"tests/e2e/*.js", "tests/*.js"
"tests/unit/*"
], ],
"envs": [ "envs": [
"node", "node",

View File

@@ -10,14 +10,8 @@ if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
xdpyinfo | grep dimensions xdpyinfo | grep dimensions
fi fi
# macOS npm run test
# Run all the tests
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
npm run test-all
fi
# Linux if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
# Only run linting test on Linux npm run test-e2e
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
npm run lint
fi fi

View File

@@ -22,7 +22,7 @@ module.exports = {
function createApp (t) { function createApp (t) {
generateTestAppPackageJson() generateTestAppPackageJson()
return new Application({ return new Application({
path: path.join(__dirname,'..', '..', 'node_modules', '.bin', path: path.join(__dirname, '..', 'node_modules', '.bin',
'electron' + (process.platform === 'win32' ? '.cmd' : '')), 'electron' + (process.platform === 'win32' ? '.cmd' : '')),
args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont
env: {NODE_ENV: 'test'}, env: {NODE_ENV: 'test'},
@@ -34,9 +34,9 @@ function createApp (t) {
// Reads app package.json and updates the productName to config.TEST_APP_PRODUCT_NAME // Reads app package.json and updates the productName to config.TEST_APP_PRODUCT_NAME
// We do this so that the app integration doesn't doesn't share the same appDataDir as the dev application // We do this so that the app integration doesn't doesn't share the same appDataDir as the dev application
function generateTestAppPackageJson () { function generateTestAppPackageJson () {
let packageJson = require(path.join(__dirname, '..', '..', 'package.json')) let packageJson = require(path.join(__dirname, '../package.json'))
packageJson.productName = config.TEST_APP_PRODUCT_NAME packageJson.productName = config.TEST_APP_PRODUCT_NAME
packageJson.main = '../../app/main' packageJson.main = '../app/main'
const testPackageJsonPath = path.join(__dirname, 'package.json') const testPackageJsonPath = path.join(__dirname, 'package.json')
fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8') fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8')

View File

@@ -1,20 +0,0 @@
const ConfigUtil = require('js/utils/config-util.js');
const SetupSpellChecker = require('js/spellchecker')
describe('test spell checker', function () {
// enable spellchecker settings
ConfigUtil.setConfigItem('enableSpellchecker', true);
ConfigUtil.setConfigItem('spellcheckerLanguage', 'en');
SetupSpellChecker.init() // re-initialize after setting update
const spellCheckHandler = SetupSpellChecker.SpellCheckHandler
it('mark misspelled word', function () {
expect(spellCheckHandler.isMisspelled('helpe')).toBe(true)
})
it('verify properly spelled word', function () {
expect(spellCheckHandler.isMisspelled('help')).toBe(false)
})
})