mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-10-24 16:43:38 +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
|
||||
|
||||
# Test generated files
|
||||
tests/e2e/package.json
|
||||
coverage
|
||||
tests/package.json
|
||||
|
||||
.python-version
|
||||
|
||||
@@ -28,7 +28,13 @@ cache:
|
||||
- app/node_modules
|
||||
|
||||
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:
|
||||
webhooks:
|
||||
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
|
||||
* Sign in to multiple teams
|
||||
* Native desktop Notifications
|
||||
* SpellChecker
|
||||
* Desktop Notifications with inline reply support
|
||||
* Multilanguage SpellChecker
|
||||
* OSX/Win/Linux installers
|
||||
* Automatic Updates (macOS/Windows)
|
||||
* Automatic Updates (macOS/Windows/Linux)
|
||||
* 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
|
||||
Please see our [development guide](./development.md) to get started and run app locally.
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const { app, dialog } = require('electron');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
const isDev = require('electron-is-dev');
|
||||
@@ -15,10 +14,6 @@ function appUpdater() {
|
||||
// Create Logs directory
|
||||
const LogsDir = `${app.getPath('userData')}/Logs`;
|
||||
|
||||
if (!fs.existsSync(LogsDir)) {
|
||||
fs.mkdirSync(LogsDir);
|
||||
}
|
||||
|
||||
// Log whats happening
|
||||
const log = require('electron-log');
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ const { setAutoLaunch } = require('./startup');
|
||||
const { app, ipcMain } = electron;
|
||||
|
||||
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
|
||||
// in development mode
|
||||
@@ -81,7 +82,11 @@ function createMainWindow() {
|
||||
});
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
win.show();
|
||||
if (ConfigUtil.getConfigItem('startMinimized')) {
|
||||
win.minimize();
|
||||
} else {
|
||||
win.show();
|
||||
}
|
||||
});
|
||||
|
||||
win.loadURL(mainURL);
|
||||
@@ -124,6 +129,9 @@ function createMainWindow() {
|
||||
return win;
|
||||
}
|
||||
|
||||
// Decrease load on GPU (experimental)
|
||||
app.disableHardwareAcceleration();
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
|
||||
event.preventDefault();
|
||||
@@ -145,7 +153,11 @@ app.on('ready', () => {
|
||||
const page = mainWindow.webContents;
|
||||
|
||||
page.on('dom-ready', () => {
|
||||
mainWindow.show();
|
||||
if (ConfigUtil.getConfigItem('startMinimized')) {
|
||||
mainWindow.minimize();
|
||||
} else {
|
||||
mainWindow.show();
|
||||
}
|
||||
});
|
||||
|
||||
page.once('did-frame-finish-load', () => {
|
||||
@@ -155,7 +167,8 @@ app.on('ready', () => {
|
||||
});
|
||||
|
||||
electron.powerMonitor.on('resume', () => {
|
||||
page.send('reload-viewer');
|
||||
mainWindow.reload();
|
||||
page.send('destroytray');
|
||||
});
|
||||
|
||||
ipcMain.on('focus-app', () => {
|
||||
|
||||
@@ -54,7 +54,7 @@ class AppMenu {
|
||||
role: 'togglefullscreen'
|
||||
}, {
|
||||
label: 'Zoom In',
|
||||
accelerator: 'CommandOrControl+=',
|
||||
accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
AppMenu.sendAction('zoomIn');
|
||||
@@ -121,7 +121,7 @@ class AppMenu {
|
||||
shell.openExternal('https://zulipchat.com/help/');
|
||||
}
|
||||
}, {
|
||||
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
|
||||
label: `${appName + ' Desktop'} - ${app.getVersion()}`,
|
||||
enabled: false
|
||||
}, {
|
||||
label: 'Report an Issue...',
|
||||
|
||||
31
app/package-lock.json
generated
31
app/package-lock.json
generated
@@ -101,6 +101,12 @@
|
||||
"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": {
|
||||
"version": "3.5.1",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
|
||||
@@ -622,6 +634,25 @@
|
||||
"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": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.1",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
"copyright": "Kandra Labs, Inc.",
|
||||
@@ -30,10 +30,13 @@
|
||||
"electron-is-dev": "0.3.0",
|
||||
"electron-log": "2.2.7",
|
||||
"electron-spellchecker": "1.1.2",
|
||||
"electron-updater": "2.18.2",
|
||||
"electron-window-state": "4.1.1",
|
||||
"electron-updater": "2.16.2",
|
||||
"node-json-db": "0.7.3",
|
||||
"request": "2.81.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;
|
||||
background: #fff;
|
||||
width: 70%;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.settings-card:hover {
|
||||
border-left: 8px solid #bcbcbc;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),
|
||||
0 2px 0px 0px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
||||
@@ -4,7 +4,7 @@ const Tab = require(__dirname + '/../components/tab.js');
|
||||
|
||||
class FunctionalTab extends Tab {
|
||||
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">
|
||||
<i class="material-icons">close</i>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ const {ipcRenderer} = require('electron');
|
||||
|
||||
class ServerTab extends Tab {
|
||||
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-tab-badge"></div>
|
||||
<div class="server-tab">
|
||||
|
||||
@@ -4,12 +4,14 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
|
||||
const ConfigUtil = require(__dirname + '/../utils/config-util.js');
|
||||
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||
const LinkUtil = require(__dirname + '/../utils/link-util.js');
|
||||
const { shell, app } = require('electron').remote;
|
||||
|
||||
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||
|
||||
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
|
||||
class WebView extends BaseComponent {
|
||||
constructor(props) {
|
||||
super();
|
||||
@@ -24,6 +26,7 @@ class WebView extends BaseComponent {
|
||||
template() {
|
||||
return `<webview
|
||||
class="disabled"
|
||||
data-tab-id="${this.props.tabIndex}"
|
||||
src="${this.props.url}"
|
||||
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
|
||||
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 => {
|
||||
const { title } = event;
|
||||
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';
|
||||
|
||||
require(__dirname + '/js/tray.js');
|
||||
const { ipcRenderer, remote } = require('electron');
|
||||
|
||||
const { session } = remote;
|
||||
|
||||
require(__dirname + '/js/tray.js');
|
||||
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
|
||||
const WebView = require(__dirname + '/js/components/webview.js');
|
||||
const ServerTab = require(__dirname + '/js/components/server-tab.js');
|
||||
@@ -35,6 +35,7 @@ class ServerManagerView {
|
||||
this.activeTabIndex = -1;
|
||||
this.tabs = [];
|
||||
this.functionalTabs = {};
|
||||
this.tabIndex = 0;
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -77,6 +78,7 @@ class ServerManagerView {
|
||||
showSidebar: true,
|
||||
badgeOption: true,
|
||||
startAtLogin: false,
|
||||
startMinimized: false,
|
||||
enableSpellchecker: true,
|
||||
showNotification: true,
|
||||
betaUpdate: false,
|
||||
@@ -84,6 +86,13 @@ class ServerManagerView {
|
||||
lastActiveTab: 0
|
||||
};
|
||||
|
||||
// Platform specific settings
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Only available on Windows
|
||||
settingOptions.flashTaskbarOnMessage = true;
|
||||
}
|
||||
|
||||
for (const i in settingOptions) {
|
||||
if (ConfigUtil.getConfigItem(i) === null) {
|
||||
ConfigUtil.setConfigItem(i, settingOptions[i]);
|
||||
@@ -112,17 +121,20 @@ class ServerManagerView {
|
||||
}
|
||||
|
||||
initServer(server, index) {
|
||||
const tabIndex = this.getTabIndex();
|
||||
this.tabs.push(new ServerTab({
|
||||
role: 'server',
|
||||
icon: server.icon,
|
||||
$root: this.$tabsContainer,
|
||||
onClick: this.activateLastTab.bind(this, index),
|
||||
index,
|
||||
tabIndex,
|
||||
onHover: this.onHover.bind(this, index, server.alias),
|
||||
onHoverOut: this.onHoverOut.bind(this, index),
|
||||
webview: new WebView({
|
||||
$root: this.$webviewsContainer,
|
||||
index,
|
||||
tabIndex,
|
||||
url: server.url,
|
||||
name: server.alias,
|
||||
isActive: () => {
|
||||
@@ -147,11 +159,24 @@ class ServerManagerView {
|
||||
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.$settingsButton, this.$settingsTooltip);
|
||||
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
|
||||
}
|
||||
|
||||
getTabIndex() {
|
||||
const currentIndex = this.tabIndex;
|
||||
this.tabIndex++;
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
sidebarHoverEvent(SidebarButton, SidebarTooltip) {
|
||||
SidebarButton.addEventListener('mouseover', () => {
|
||||
SidebarTooltip.removeAttribute('style');
|
||||
@@ -178,16 +203,19 @@ class ServerManagerView {
|
||||
|
||||
this.functionalTabs[tabProps.name] = this.tabs.length;
|
||||
|
||||
const tabIndex = this.getTabIndex();
|
||||
this.tabs.push(new FunctionalTab({
|
||||
role: 'function',
|
||||
materialIcon: tabProps.materialIcon,
|
||||
$root: this.$tabsContainer,
|
||||
index: this.functionalTabs[tabProps.name],
|
||||
tabIndex,
|
||||
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
|
||||
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
|
||||
webview: new WebView({
|
||||
$root: this.$webviewsContainer,
|
||||
index: this.functionalTabs[tabProps.name],
|
||||
tabIndex,
|
||||
url: tabProps.url,
|
||||
name: tabProps.name,
|
||||
isActive: () => {
|
||||
@@ -255,6 +283,27 @@ class ServerManagerView {
|
||||
tabs: this.tabs,
|
||||
activeTabIndex: this.activeTabIndex
|
||||
});
|
||||
|
||||
ipcRenderer.on('toggle-sidebar', (event, state) => {
|
||||
const selector = 'webview:not([class*=disabled])';
|
||||
const webview = document.querySelector(selector);
|
||||
const webContents = webview.getWebContents();
|
||||
webContents.send('toggle-sidebar', state);
|
||||
});
|
||||
|
||||
ipcRenderer.on('toogle-silent', (event, state) => {
|
||||
const webviews = document.querySelectorAll('webview');
|
||||
webviews.forEach(webview => {
|
||||
try {
|
||||
webview.setAudioMuted(state);
|
||||
} catch (err) {
|
||||
// webview is not ready yet
|
||||
webview.addEventListener('dom-ready', () => {
|
||||
webview.isAudioMuted();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
destroyTab(name, index) {
|
||||
@@ -387,6 +436,18 @@ class ServerManagerView {
|
||||
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) => {
|
||||
// Create a canvas from unread messagecounts
|
||||
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';
|
||||
const path = require('path');
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { app, dialog } = require('electron').remote;
|
||||
|
||||
const { ipcRenderer, remote } = require('electron');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const { app, dialog } = remote;
|
||||
const currentBrowserWindow = remote.getCurrentWindow();
|
||||
const BaseSection = require(__dirname + '/base-section.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-control"></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-description">Enable Spellchecker (requires restart)</div>
|
||||
<div class="setting-control"></div>
|
||||
</div>
|
||||
<div class="setting-description">Enable Spellchecker (requires restart)</div>
|
||||
<div class="setting-control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title">Reset Application Data</div>
|
||||
<div class="settings-card">
|
||||
@@ -89,6 +92,7 @@ class GeneralSection extends BaseSection {
|
||||
this.updateResetDataOption();
|
||||
this.showDesktopNotification();
|
||||
this.enableSpellchecker();
|
||||
this.minimizeOnStart();
|
||||
|
||||
// Platform specific settings
|
||||
// Flashing taskbar on Windows
|
||||
@@ -155,6 +159,7 @@ class GeneralSection extends BaseSection {
|
||||
const newValue = !ConfigUtil.getConfigItem('silent', true);
|
||||
ConfigUtil.setConfigItem('silent', newValue);
|
||||
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;
|
||||
|
||||
@@ -13,9 +13,9 @@ class NewServerForm extends BaseComponent {
|
||||
return `
|
||||
<div class="settings-card">
|
||||
<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">
|
||||
<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 class="server-info-row">
|
||||
<div class="action blue server-save-action">
|
||||
@@ -43,11 +43,13 @@ class NewServerForm extends BaseComponent {
|
||||
}
|
||||
|
||||
submitFormHandler() {
|
||||
this.$saveServerButton.children[1].innerHTML = 'Adding...';
|
||||
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
|
||||
DomainUtil.addDomain(serverConf).then(() => {
|
||||
this.props.onChange(this.props.index);
|
||||
});
|
||||
}, errorMessage => {
|
||||
this.$saveServerButton.children[1].innerHTML = 'Add';
|
||||
alert(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,10 +69,27 @@ class PreferenceView extends BaseComponent {
|
||||
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() {
|
||||
ipcRenderer.on('switch-settings-nav', (event, 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() {
|
||||
let state;
|
||||
if (window.tray) {
|
||||
state = false;
|
||||
window.tray.destroy();
|
||||
if (window.tray.isDestroyed()) {
|
||||
window.tray = null;
|
||||
}
|
||||
ConfigUtil.setConfigItem('trayIcon', false);
|
||||
} else {
|
||||
state = true;
|
||||
createTray();
|
||||
if (process.platform === 'linux' || process.platform === 'win32') {
|
||||
renderNativeImage(unread).then(image => {
|
||||
@@ -213,6 +216,10 @@ function toggleTray() {
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const process = require('process');
|
||||
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 dialog = null;
|
||||
let app = null;
|
||||
|
||||
/* To make the util runnable in both main and renderer process */
|
||||
if (process.type === 'renderer') {
|
||||
app = require('electron').remote.app;
|
||||
const remote = require('electron').remote;
|
||||
dialog = remote.dialog;
|
||||
app = remote.app;
|
||||
} else {
|
||||
app = require('electron').app;
|
||||
const electron = require('electron');
|
||||
dialog = electron.dialog;
|
||||
app = electron.app;
|
||||
}
|
||||
|
||||
class ConfigUtil {
|
||||
@@ -47,7 +60,22 @@ class ConfigUtil {
|
||||
}
|
||||
|
||||
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 JsonDB = require('node-json-db');
|
||||
const request = require('request');
|
||||
const Logger = require('./logger-util');
|
||||
|
||||
const logger = new Logger({
|
||||
file: `domain-util.log`,
|
||||
timestamp: true
|
||||
});
|
||||
|
||||
let instance = null;
|
||||
|
||||
@@ -93,8 +99,7 @@ class DomainUtil {
|
||||
checkDomain(domain, silent = false) {
|
||||
if (!silent && this.duplicateDomain(domain)) {
|
||||
// Do not check duplicate in silent mode
|
||||
alert('This server has been added.');
|
||||
return;
|
||||
return Promise.reject('This server has been added.');
|
||||
}
|
||||
|
||||
domain = this.formatUrl(domain);
|
||||
@@ -115,11 +120,16 @@ class DomainUtil {
|
||||
'Error: unable to verify the first certificate',
|
||||
'Error: unable to get local issuer certificate'
|
||||
];
|
||||
|
||||
// If the domain contains following strings we just bypass the server
|
||||
const whitelistDomains = [
|
||||
'zulipdev.org'
|
||||
];
|
||||
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
|
||||
this.getServerSettings(domain).then(serverSettings => {
|
||||
resolve(serverSettings);
|
||||
@@ -159,7 +169,7 @@ class DomainUtil {
|
||||
}
|
||||
} else {
|
||||
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \
|
||||
\n(1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings`;
|
||||
\n(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);
|
||||
}
|
||||
});
|
||||
@@ -225,7 +235,23 @@ class DomainUtil {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
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>
|
||||
<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>
|
||||
@@ -20,4 +20,6 @@ install:
|
||||
build: off
|
||||
|
||||
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
|
||||
gulp.watch('app/renderer/css/*.css', ['reload:renderer']);
|
||||
gulp.watch('app/renderer/*.html', ['reload:renderer']);
|
||||
gulp.watch('app/renderer/js/*.js', ['reload:renderer']);
|
||||
gulp.watch('app/renderer/js/**/*.js', ['reload:renderer']);
|
||||
});
|
||||
|
||||
gulp.task('restart:browser', done => {
|
||||
@@ -30,7 +30,7 @@ gulp.task('reload:renderer', done => {
|
||||
});
|
||||
|
||||
gulp.task('test-e2e', () => {
|
||||
return gulp.src('tests/e2e/*.js')
|
||||
return gulp.src('tests/*.js')
|
||||
.pipe(tape({
|
||||
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
|
||||
},
|
||||
"electron": {
|
||||
"version": "1.6.15",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-1.6.15.tgz",
|
||||
"integrity": "sha1-w07FRIa39Jpm21jG8koJKEFPrqc=",
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-1.7.9.tgz",
|
||||
"integrity": "sha1-rdVOn4+D7QL2UZ7BATX2mLGTNs8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "7.0.48",
|
||||
@@ -4762,6 +4762,34 @@
|
||||
"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": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.2.tgz",
|
||||
@@ -5801,11 +5829,6 @@
|
||||
"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": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",
|
||||
|
||||
34
package.json
34
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.1",
|
||||
"main": "./app/main",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,18 +21,18 @@
|
||||
"start": "electron app --disable-http-cache",
|
||||
"reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"lint": "xo",
|
||||
"test": "xo",
|
||||
"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 & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js",
|
||||
"pack": "electron-builder --dir",
|
||||
"dist": "electron-builder",
|
||||
"mas": "electron-builder --mac mas",
|
||||
"setup-gitlint-hooks": "node ./tools/gitlint/setup-gitlint-hook",
|
||||
"lint-commits": "node ./tools/gitlint --all-commits",
|
||||
"travis": "cd ./scripts && ./travis-build-test.sh"
|
||||
},
|
||||
"pre-commit": [
|
||||
"lint"
|
||||
"test"
|
||||
],
|
||||
"build": {
|
||||
"appId": "org.zulip.zulip-electron",
|
||||
@@ -109,26 +109,21 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"assert": "1.4.1",
|
||||
"chalk": "^2.3.0",
|
||||
"cp-file": "^5.0.0",
|
||||
"devtron": "1.4.0",
|
||||
"electron-builder": "19.46.4",
|
||||
"electron": "1.7.9",
|
||||
"electron": "1.7.10",
|
||||
"electron-builder": "19.53.6",
|
||||
"electron-connect": "0.6.2",
|
||||
"electron-debug": "1.4.0",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-tape": "0.0.9",
|
||||
"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",
|
||||
"nodemon": "^1.14.11",
|
||||
"pre-commit": "1.2.2",
|
||||
"spectron": "3.7.2",
|
||||
"tap-colorize": "^1.2.0",
|
||||
"tape": "^4.8.0",
|
||||
"watchify": "^3.9.0",
|
||||
"xo": "0.18.2"
|
||||
},
|
||||
"xo": {
|
||||
@@ -159,11 +154,16 @@
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"no-prototype-builtins": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "scripts/gitlint/*.js",
|
||||
"rules": {
|
||||
"unicorn/no-process-exit": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"ignore": [
|
||||
"tests/e2e/*.js",
|
||||
"tests/unit/*"
|
||||
"tests/*.js"
|
||||
],
|
||||
"envs": [
|
||||
"node",
|
||||
|
||||
@@ -10,14 +10,8 @@ if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
xdpyinfo | grep dimensions
|
||||
fi
|
||||
|
||||
# macOS
|
||||
# Run all the tests
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
npm run test-all
|
||||
fi
|
||||
npm run test
|
||||
|
||||
# Linux
|
||||
# Only run linting test on Linux
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
npm run lint
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
npm run test-e2e
|
||||
fi
|
||||
|
||||
@@ -22,7 +22,7 @@ module.exports = {
|
||||
function createApp (t) {
|
||||
generateTestAppPackageJson()
|
||||
return new Application({
|
||||
path: path.join(__dirname,'..', '..', 'node_modules', '.bin',
|
||||
path: path.join(__dirname, '..', 'node_modules', '.bin',
|
||||
'electron' + (process.platform === 'win32' ? '.cmd' : '')),
|
||||
args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont
|
||||
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
|
||||
// We do this so that the app integration doesn't doesn't share the same appDataDir as the dev application
|
||||
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.main = '../../app/main'
|
||||
packageJson.main = '../app/main'
|
||||
|
||||
const testPackageJsonPath = path.join(__dirname, 'package.json')
|
||||
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