mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-11-05 22:43:17 +00:00
Compare commits
44 Commits
setup-karm
...
git-linter
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e3cfbd887 | ||
|
|
720f42ca80 | ||
|
|
e18ba5dab6 | ||
|
|
239631a2b6 | ||
|
|
89d1344e2f | ||
|
|
1948ba2cc3 | ||
|
|
b8da7dd6ee | ||
|
|
4a0efb7301 | ||
|
|
aedd95259d | ||
|
|
c8d7a79877 | ||
|
|
6e6db42b54 | ||
|
|
db79284fbb | ||
|
|
2434f06655 | ||
|
|
1d611d3382 | ||
|
|
a746194e9e | ||
|
|
7cc13f7a26 | ||
|
|
6a9bb152a0 | ||
|
|
8b6dcd355f | ||
|
|
91742a5770 | ||
|
|
fb74251a2c | ||
|
|
a920720f91 | ||
|
|
aa8e99b7a6 | ||
|
|
e23f8aaa58 | ||
|
|
5c3208d44c | ||
|
|
c0b57bbe2b | ||
|
|
afe4e8901b | ||
|
|
231e7fd9c2 | ||
|
|
a0d898a5b7 | ||
|
|
1abf62555c | ||
|
|
6befcbaa8f | ||
|
|
e56a01049b | ||
|
|
72cb8459ff | ||
|
|
0b83b22206 | ||
|
|
267d25e5c4 | ||
|
|
8401f8f5ce | ||
|
|
c4a7264f34 | ||
|
|
9d081ecd5a | ||
|
|
dc6582fa82 | ||
|
|
3b412672c6 | ||
|
|
04083bfa81 | ||
|
|
562e82d2f1 | ||
|
|
3b014e0715 | ||
|
|
13178ebc8f | ||
|
|
08693bf105 |
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE.md
vendored
Normal 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
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
3
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -28,7 +28,13 @@ cache:
|
|||||||
- app/node_modules
|
- app/node_modules
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- npm run travis
|
- echo $TRAVIS_COMMIT_RANGE
|
||||||
|
- echo ${TRAVIS_COMMIT_RANGE/.../..}
|
||||||
|
- echo "test"
|
||||||
|
- git log -4
|
||||||
|
- node ./tools/gitlint --ci-mode
|
||||||
|
- npm run travis
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
webhooks:
|
webhooks:
|
||||||
urls:
|
urls:
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
Please include:
|
|
||||||
- `Operating System`
|
|
||||||
- `Clear steps to reproduce the issue`
|
|
||||||
- `Relevant error messages and/or screenshots`
|
|
||||||
|
|
||||||
|
|
||||||
13
README.md
13
README.md
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class AppMenu {
|
|||||||
role: 'togglefullscreen'
|
role: 'togglefullscreen'
|
||||||
}, {
|
}, {
|
||||||
label: 'Zoom In',
|
label: 'Zoom In',
|
||||||
accelerator: 'CommandOrControl+=',
|
accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=',
|
||||||
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
31
app/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
100
app/renderer/js/notification/darwin-notifications.js
Normal file
100
app/renderer/js/notification/darwin-notifications.js
Normal 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;
|
||||||
31
app/renderer/js/notification/default-notification.js
Normal file
31
app/renderer/js/notification/default-notification.js
Normal 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;
|
||||||
102
app/renderer/js/notification/helpers.js
Normal file
102
app/renderer/js/notification/helpers.js
Normal 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
|
||||||
|
};
|
||||||
18
app/renderer/js/notification/index.js
Normal file
18
app/renderer/js/notification/index.js
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -43,11 +43,13 @@ class NewServerForm extends BaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submitFormHandler() {
|
submitFormHandler() {
|
||||||
|
this.$saveServerButton.children[1].innerHTML = 'Adding...';
|
||||||
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
|
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
|
||||||
DomainUtil.addDomain(serverConf).then(() => {
|
DomainUtil.addDomain(serverConf).then(() => {
|
||||||
this.props.onChange(this.props.index);
|
this.props.onChange(this.props.index);
|
||||||
});
|
});
|
||||||
}, errorMessage => {
|
}, errorMessage => {
|
||||||
|
this.$saveServerButton.children[1].innerHTML = 'Add';
|
||||||
alert(errorMessage);
|
alert(errorMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,27 @@ class PreferenceView extends BaseComponent {
|
|||||||
window.location.hash = `#${navItem}`;
|
window.location.hash = `#${navItem}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle toggling and reflect changes in preference page
|
||||||
|
handleToggle(elementName, state) {
|
||||||
|
const inputSelector = `#${elementName} .action .switch input`;
|
||||||
|
const input = document.querySelector(inputSelector);
|
||||||
|
if (input) {
|
||||||
|
input.checked = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registerIpcs() {
|
registerIpcs() {
|
||||||
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) => {
|
||||||
|
this.handleToggle('sidebar-option', state);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('toggletray', (event, state) => {
|
||||||
|
this.handleToggle('tray-option', state);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
app/renderer/js/utils/default-util.js
Normal file
31
app/renderer/js/utils/default-util.js
Normal 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
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -93,8 +99,7 @@ class DomainUtil {
|
|||||||
checkDomain(domain, silent = false) {
|
checkDomain(domain, silent = false) {
|
||||||
if (!silent && this.duplicateDomain(domain)) {
|
if (!silent && this.duplicateDomain(domain)) {
|
||||||
// Do not check duplicate in silent mode
|
// Do not check duplicate in silent mode
|
||||||
alert('This server has been added.');
|
return Promise.reject('This server has been added.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
domain = this.formatUrl(domain);
|
domain = this.formatUrl(domain);
|
||||||
@@ -115,11 +120,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 +169,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 +235,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) {
|
||||||
|
|||||||
87
app/renderer/js/utils/logger-util.js
Normal file
87
app/renderer/js/utils/logger-util.js
Normal 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;
|
||||||
@@ -41,4 +41,13 @@
|
|||||||
</body>
|
</body>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
|
|
||||||
|
<!-- To trigger electron.reload on changes in renderer from gulp -->
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const isDev = require('electron-is-dev');
|
||||||
|
if (isDev) {
|
||||||
|
require('electron-connect').client.create();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</html>
|
</html>
|
||||||
@@ -20,4 +20,6 @@ install:
|
|||||||
build: off
|
build: off
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- npm run test-all
|
- node ./tools/gitlint --ci-mode
|
||||||
|
- npm run test
|
||||||
|
- npm run test-e2e
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ gulp.task('dev', () => {
|
|||||||
// Reload renderer process
|
// Reload renderer process
|
||||||
gulp.watch('app/renderer/css/*.css', ['reload:renderer']);
|
gulp.watch('app/renderer/css/*.css', ['reload:renderer']);
|
||||||
gulp.watch('app/renderer/*.html', ['reload:renderer']);
|
gulp.watch('app/renderer/*.html', ['reload:renderer']);
|
||||||
gulp.watch('app/renderer/js/*.js', ['reload:renderer']);
|
gulp.watch('app/renderer/js/**/*.js', ['reload:renderer']);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('restart:browser', done => {
|
gulp.task('restart:browser', done => {
|
||||||
@@ -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()
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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');
|
|
||||||
39
package-lock.json
generated
39
package-lock.json
generated
@@ -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",
|
||||||
@@ -4762,6 +4762,34 @@
|
|||||||
"inherits": "2.0.1"
|
"inherits": "2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nodemon": {
|
||||||
|
"version": "1.14.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.14.11.tgz",
|
||||||
|
"integrity": "sha512-323uPopdzYcyDR2Ze1UOLF9zocwoQEyGPiKaLm/Y8Mbfjylt/YueAJUVHqox+vgG8TqZqZApcHv5lmUvrn/KQw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"chokidar": "2.0.0",
|
||||||
|
"debug": "3.1.0",
|
||||||
|
"ignore-by-default": "1.0.1",
|
||||||
|
"minimatch": "3.0.4",
|
||||||
|
"pstree.remy": "1.1.0",
|
||||||
|
"semver": "5.4.1",
|
||||||
|
"touch": "3.1.0",
|
||||||
|
"undefsafe": "2.0.1",
|
||||||
|
"update-notifier": "2.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"debug": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-abi": {
|
"node-abi": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.2.tgz",
|
||||||
@@ -5801,11 +5829,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",
|
||||||
|
|||||||
34
package.json
34
package.json
@@ -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,18 +21,18 @@
|
|||||||
"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",
|
"dev": "gulp dev & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js",
|
||||||
"test-all": "xo && npm run test-unit && npm run test-e2e",
|
|
||||||
"dev": "gulp dev",
|
|
||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
"dist": "electron-builder",
|
"dist": "electron-builder",
|
||||||
"mas": "electron-builder --mac mas",
|
"mas": "electron-builder --mac mas",
|
||||||
|
"setup-gitlint-hooks": "node ./tools/gitlint/setup-gitlint-hook",
|
||||||
|
"lint-commits": "node ./tools/gitlint --all-commits",
|
||||||
"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",
|
||||||
@@ -109,26 +109,21 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"assert": "1.4.1",
|
"assert": "1.4.1",
|
||||||
|
"chalk": "^2.3.0",
|
||||||
"cp-file": "^5.0.0",
|
"cp-file": "^5.0.0",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron-builder": "19.46.4",
|
"electron": "1.7.10",
|
||||||
"electron": "1.7.9",
|
"electron-builder": "19.53.6",
|
||||||
"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",
|
"nodemon": "^1.14.11",
|
||||||
"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": {
|
||||||
@@ -159,11 +154,16 @@
|
|||||||
"import/no-extraneous-dependencies": 0,
|
"import/no-extraneous-dependencies": 0,
|
||||||
"no-prototype-builtins": 0
|
"no-prototype-builtins": 0
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "scripts/gitlint/*.js",
|
||||||
|
"rules": {
|
||||||
|
"unicorn/no-process-exit": 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"tests/e2e/*.js",
|
"tests/*.js"
|
||||||
"tests/unit/*"
|
|
||||||
],
|
],
|
||||||
"envs": [
|
"envs": [
|
||||||
"node",
|
"node",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
@@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
16
tools/fetch-pull-request
Executable file
16
tools/fetch-pull-request
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if ! git diff-index --quiet HEAD; then
|
||||||
|
set +x
|
||||||
|
echo "There are uncommitted changes:"
|
||||||
|
git status --short
|
||||||
|
echo "Doing nothing to avoid losing your work."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
request_id="$1"
|
||||||
|
remote=${2:-"upstream"}
|
||||||
|
git fetch "$remote" "pull/$request_id/head"
|
||||||
|
git checkout -B "review-original-${request_id}"
|
||||||
|
git reset --hard FETCH_HEAD
|
||||||
17
tools/fetch-rebase-pull-request
Executable file
17
tools/fetch-rebase-pull-request
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if ! git diff-index --quiet HEAD; then
|
||||||
|
set +x
|
||||||
|
echo "There are uncommitted changes:"
|
||||||
|
git status --short
|
||||||
|
echo "Doing nothing to avoid losing your work."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
request_id="$1"
|
||||||
|
remote=${2:-"upstream"}
|
||||||
|
git fetch "$remote" "pull/$request_id/head"
|
||||||
|
git checkout -B "review-${request_id}" $remote/master
|
||||||
|
git reset --hard FETCH_HEAD
|
||||||
|
git pull --rebase
|
||||||
31
tools/gitlint/ci.js
Normal file
31
tools/gitlint/ci.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const {run} = require('./helpers');
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
if (process.argv.TRAVIS !== undefined) {
|
||||||
|
return travis();
|
||||||
|
}
|
||||||
|
|
||||||
|
return appveyor();
|
||||||
|
};
|
||||||
|
|
||||||
|
function travis() {
|
||||||
|
if (!process.env.TRAVIS_PULL_REQUEST) {
|
||||||
|
// Building against master last commit
|
||||||
|
return run('git log -1 HEAD');
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmd = `git log ${process.env.TRAVIS_COMMIT_RANGE}`;
|
||||||
|
const commitRange = run(`git diff --name-only ${process.env.TRAVIS_COMMIT_RANGE}`);
|
||||||
|
process.stdout.write(`COMMIT_RANGE: ${commitRange}`, 'utf8');
|
||||||
|
return run(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appveyor() {
|
||||||
|
if (!process.env.APPVEYOR_PULL_REQUEST_NUMBER) {
|
||||||
|
return run('git log -1 HEAD');
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmd =
|
||||||
|
`git log origin/master...${process.env.APPVEYOR_PULL_REQUEST_HEAD_COMMIT}`;
|
||||||
|
return run(cmd);
|
||||||
|
}
|
||||||
91
tools/gitlint/helpers.js
Normal file
91
tools/gitlint/helpers.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
const {spawnSync} = require('child_process');
|
||||||
|
const chalk = require('chalk');
|
||||||
|
|
||||||
|
const commitMsgRegex = /[A-Z]+.*\.$/;
|
||||||
|
const isFullCommitRegex = /(\w|\W){1,}:\s{1}/;
|
||||||
|
const fullCommitRegex = /(\w|\W){1,}:\s{1}[A-Z]+.*\.$/;
|
||||||
|
|
||||||
|
function run(script) {
|
||||||
|
script = script.split(' ');
|
||||||
|
const cmd = script.splice(0, 1)[0];
|
||||||
|
const args = script;
|
||||||
|
const output = spawnSync(cmd, args, {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
encoding: 'utf8',
|
||||||
|
windowsHide: true
|
||||||
|
}).stdout;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function garbageCollect(a) {
|
||||||
|
a.forEach((content, index) => {
|
||||||
|
if (content === '' || content === undefined) {
|
||||||
|
a.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllCommits(output) {
|
||||||
|
output = output.split('\ncommits');
|
||||||
|
if (!output.length > 1) {
|
||||||
|
exports.error('There are no commits to lint.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
output = garbageCollect(output);
|
||||||
|
output.forEach((commit, index) => {
|
||||||
|
output[index] = 'commit' + commit;
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCommit(output) {
|
||||||
|
output = output.split('\n\n');
|
||||||
|
|
||||||
|
let commit = output[0].replace('commit ', '');
|
||||||
|
commit = commit.replace(/\n.*/g, '');
|
||||||
|
let commitHash = commit.split('');
|
||||||
|
commitHash = commitHash.slice(commitHash.length - 7);
|
||||||
|
commitHash = commitHash.join('');
|
||||||
|
|
||||||
|
const fullCommit = output[1].split('\n');
|
||||||
|
const commitMsg = fullCommit[0];
|
||||||
|
let lintingStatus = commitMsgRegex.test(commitMsg);
|
||||||
|
lintingStatus = (commitMsg.length <= 72);
|
||||||
|
|
||||||
|
if (lintingStatus && isFullCommitRegex(commitMsg)) {
|
||||||
|
lintingStatus = fullCommitRegex.test(commitMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
failed: !lintingStatus,
|
||||||
|
commitHash
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function logSuccess() {
|
||||||
|
console.log(chalk`{green commit linter:} commit linter passed.`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(...args) {
|
||||||
|
args.unshift(chalk.red('ERROR! '));
|
||||||
|
console.error.apply(this, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function warn() {
|
||||||
|
// console.error(chalk`{yellow ${msg}}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
run,
|
||||||
|
getAllCommits,
|
||||||
|
parseCommit,
|
||||||
|
logSuccess,
|
||||||
|
error,
|
||||||
|
warn
|
||||||
|
};
|
||||||
44
tools/gitlint/index.js
Normal file
44
tools/gitlint/index.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const helpers = require('./helpers');
|
||||||
|
const getCICmd = require('./ci');
|
||||||
|
|
||||||
|
let checkAllCommits = false;
|
||||||
|
let ciMode = false;
|
||||||
|
if (process.argv[2]) {
|
||||||
|
checkAllCommits = process.argv[2].includes('-a');
|
||||||
|
ciMode = process.argv[2] === '--ci-mode';
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd;
|
||||||
|
if (ciMode) {
|
||||||
|
cmd = getCICmd();
|
||||||
|
} else {
|
||||||
|
cmd =
|
||||||
|
checkAllCommits ? 'git log upstream/master...HEAD' : 'git log -1 HEAD';
|
||||||
|
}
|
||||||
|
|
||||||
|
const commits = helpers.run(cmd);
|
||||||
|
const commitsArray = helpers.getAllCommits(commits);
|
||||||
|
let lintFailed = false;
|
||||||
|
commitsArray.forEach(commit => {
|
||||||
|
const res = helpers.parseCommit(commit);
|
||||||
|
if (res.failed) {
|
||||||
|
const {commitHash} = res;
|
||||||
|
helpers.error(`commit ${commitHash} does not pass our commit style.`);
|
||||||
|
lintFailed = true;
|
||||||
|
} else {
|
||||||
|
helpers.logSuccess('Commit[s] follow the zulip-electron commit rules.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lintFailed) {
|
||||||
|
helpers.warn(`
|
||||||
|
commit msg linting failed
|
||||||
|
-------------------------------
|
||||||
|
Reasons it does not have:
|
||||||
|
a) capitial latter at start of message
|
||||||
|
b) period at the end of commit or
|
||||||
|
c) Commit msg length is more than 72 charaters
|
||||||
|
`);
|
||||||
|
helpers.warn('Run with --no-verify flag to skip the commit-linter');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
73
tools/gitlint/setup-gitlint-hook.js
Normal file
73
tools/gitlint/setup-gitlint-hook.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// This script sets up the pre-push
|
||||||
|
// git hook which will be used to lint
|
||||||
|
// commits
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const gitHooks = '../../.git/hooks';
|
||||||
|
const postCommitPath = path.resolve(__dirname, `${gitHooks}/post-commit`);
|
||||||
|
const prePushPath = path.resolve(__dirname, `${gitHooks}/pre-push`);
|
||||||
|
|
||||||
|
function scriptTemplate(cmds) {
|
||||||
|
cmds = cmds.join('');
|
||||||
|
const script = [
|
||||||
|
'#!/bin/sh',
|
||||||
|
'set -e',
|
||||||
|
'echo running gitlint...',
|
||||||
|
cmds,
|
||||||
|
'exit $?'
|
||||||
|
];
|
||||||
|
|
||||||
|
return script.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const postCommitFile = scriptTemplate`node scripts/gitlint`;
|
||||||
|
const prePushFile = scriptTemplate`node scripts/gitlint --all-commits`;
|
||||||
|
|
||||||
|
function writeAndChmod(file, data) {
|
||||||
|
fs.writeFile(file, data, err => {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.chmod(file, '777', err => {
|
||||||
|
if (err) {
|
||||||
|
const msg =
|
||||||
|
'chmod post-commit, pre-push hooks, at .git/hooks 0777 so they work!';
|
||||||
|
console.error(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[postCommitPath, prePushPath].forEach((file, index) => {
|
||||||
|
fs.open(file, 'w+', err => {
|
||||||
|
if (err && err.code !== 'EEXIST') {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = index === 0 ? postCommitFile : prePushFile;
|
||||||
|
writeAndChmod(file, data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove .sample files since
|
||||||
|
// sometimes the hooks do not work
|
||||||
|
const postCommitSampleFile = `${postCommitPath}.sample`;
|
||||||
|
const prePushSampleFile = `${prePushPath}.sample`;
|
||||||
|
function removeSampleFile(file) {
|
||||||
|
fs.unlink(file, err => {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[postCommitSampleFile, prePushSampleFile].forEach(file => {
|
||||||
|
fs.exists(file, exists => {
|
||||||
|
if (exists) {
|
||||||
|
removeSampleFile(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user