mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-10-24 08:33:36 +00:00
Compare commits
340 Commits
v0.5.3
...
v1.1.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a16181be33 | ||
|
|
cd0a7741b1 | ||
|
|
6da523a18b | ||
|
|
86302308a9 | ||
|
|
b0294db133 | ||
|
|
252586cf71 | ||
|
|
52ea1a48fd | ||
|
|
98e73f807c | ||
|
|
f96dd6e6bc | ||
|
|
1511ce4610 | ||
|
|
c2db6fc0f0 | ||
|
|
937a193a61 | ||
|
|
3823ac7f78 | ||
|
|
878cc3fe82 | ||
|
|
eace637f29 | ||
|
|
316b5fda9e | ||
|
|
e3b8d5ea2a | ||
|
|
c8434cfd21 | ||
|
|
fbc048e8cb | ||
|
|
8a4483da80 | ||
|
|
8252c9ae6c | ||
|
|
5beb425f1c | ||
|
|
44337dd04c | ||
|
|
bb76e2c2f4 | ||
|
|
71305bca4e | ||
|
|
9cff5c5a4d | ||
|
|
f3cf2229c6 | ||
|
|
a62fc3d3bf | ||
|
|
e538543512 | ||
|
|
f85bca9879 | ||
|
|
f548a0ae53 | ||
|
|
7192dc69f6 | ||
|
|
47eec89a9b | ||
|
|
48ff506344 | ||
|
|
c2e6e9603f | ||
|
|
f4f4836887 | ||
|
|
02bc5e41a5 | ||
|
|
68c90434b3 | ||
|
|
7f8d933ab7 | ||
|
|
935b37705a | ||
|
|
798235fb06 | ||
|
|
743d689281 | ||
|
|
4c188bbdc8 | ||
|
|
26e0543ae2 | ||
|
|
463701c5f8 | ||
|
|
4c33f0779c | ||
|
|
4888efb9f2 | ||
|
|
ea332a9ff3 | ||
|
|
f1b2fdcf99 | ||
|
|
9f73160f74 | ||
|
|
b18e3ad5d2 | ||
|
|
ed6013fb5d | ||
|
|
39b436819c | ||
|
|
3e74fc9b0a | ||
|
|
0708519816 | ||
|
|
84808313fe | ||
|
|
e7e55596c6 | ||
|
|
1b8eb099ab | ||
|
|
1fa276a400 | ||
|
|
0751f6ac72 | ||
|
|
3d8da55648 | ||
|
|
318a729a4a | ||
|
|
aab581f204 | ||
|
|
c0075b4f1c | ||
|
|
7bd2e751c5 | ||
|
|
5ff2492c79 | ||
|
|
a26df708f4 | ||
|
|
57df256a4a | ||
|
|
7f1890d8a1 | ||
|
|
ff12b041a1 | ||
|
|
606e407aee | ||
|
|
b0db81095a | ||
|
|
2208b03612 | ||
|
|
7cf2422d76 | ||
|
|
57d4e5c930 | ||
|
|
deafa315df | ||
|
|
4c09da791c | ||
|
|
e3490dbfa5 | ||
|
|
f6358a06fd | ||
|
|
3ef346495f | ||
|
|
64fcc51c7e | ||
|
|
a5299a6973 | ||
|
|
154aa323ab | ||
|
|
c52318f5d6 | ||
|
|
bcd4048709 | ||
|
|
f6cb262d4c | ||
|
|
ca21912374 | ||
|
|
472ba9a199 | ||
|
|
f3a4d4225d | ||
|
|
62178b6035 | ||
|
|
2068ac8905 | ||
|
|
51352be1f6 | ||
|
|
8c3bfcbdd5 | ||
|
|
06ed522714 | ||
|
|
d5526944fe | ||
|
|
616ed89d90 | ||
|
|
92362653d3 | ||
|
|
06a09574a5 | ||
|
|
c19a7f81c2 | ||
|
|
635b6f6128 | ||
|
|
996084cd36 | ||
|
|
6a403e52e1 | ||
|
|
aed58ed1a8 | ||
|
|
0412702e35 | ||
|
|
cf0086e324 | ||
|
|
d156ba99c8 | ||
|
|
7d71e2e04d | ||
|
|
546abb28c0 | ||
|
|
a43d008aa9 | ||
|
|
777ed5c561 | ||
|
|
61db04c574 | ||
|
|
93123401fe | ||
|
|
dca4debee2 | ||
|
|
bef1503b9e | ||
|
|
f9d430b2d2 | ||
|
|
0d551861c7 | ||
|
|
4ced663f01 | ||
|
|
06f15eba2d | ||
|
|
3008cd1f24 | ||
|
|
ea449965af | ||
|
|
06ecdb678c | ||
|
|
dd99560426 | ||
|
|
cebea28ba8 | ||
|
|
da3e7e39b9 | ||
|
|
4b4e3a3d01 | ||
|
|
aff4632927 | ||
|
|
eeb9ee512f | ||
|
|
11785d78d2 | ||
|
|
f8d8d0ce2e | ||
|
|
b5af5d413d | ||
|
|
d962bd6e60 | ||
|
|
0271ada591 | ||
|
|
42fedf2d73 | ||
|
|
0b17dbb014 | ||
|
|
ae3c595d82 | ||
|
|
de34a22740 | ||
|
|
865553fa45 | ||
|
|
23fd7ba2b3 | ||
|
|
e43b651060 | ||
|
|
c0fc7718aa | ||
|
|
3fe23e84b3 | ||
|
|
6b29139805 | ||
|
|
0bfa202763 | ||
|
|
8c494f329b | ||
|
|
35bf2b0012 | ||
|
|
5ac4ea71c9 | ||
|
|
f7eb4128cb | ||
|
|
f9f21cd626 | ||
|
|
4a84f17d86 | ||
|
|
d5a92110db | ||
|
|
e9cf591559 | ||
|
|
ba60c04452 | ||
|
|
f4567c762d | ||
|
|
b2aec4b27e | ||
|
|
0fc58b4cc9 | ||
|
|
e9d7bfe48b | ||
|
|
070eb099d0 | ||
|
|
e35661993d | ||
|
|
f6234cd2f6 | ||
|
|
66475cf46c | ||
|
|
06734d2e56 | ||
|
|
1c012c7a28 | ||
|
|
7fdccd278b | ||
|
|
9a4556a59c | ||
|
|
92b9388f9a | ||
|
|
869600e3d4 | ||
|
|
3c22d5462d | ||
|
|
92ff92f501 | ||
|
|
47b3dd04fb | ||
|
|
9ed09c9e1c | ||
|
|
239cce8a4c | ||
|
|
d6a35408b8 | ||
|
|
35e6f7dcdd | ||
|
|
a38c933bc8 | ||
|
|
720ccf5d00 | ||
|
|
8dc87d0485 | ||
|
|
cd9f1c0c47 | ||
|
|
331452edbb | ||
|
|
d18885ecc9 | ||
|
|
1aef53ef94 | ||
|
|
11086210de | ||
|
|
e0d693fa19 | ||
|
|
81c71b1f83 | ||
|
|
cf8c83e3cf | ||
|
|
3dade768a7 | ||
|
|
7b3c7ba5fa | ||
|
|
616bc0f73b | ||
|
|
8ecdf1f18a | ||
|
|
d2daa65059 | ||
|
|
487ee538e3 | ||
|
|
6382c6d2b3 | ||
|
|
cbcff67d28 | ||
|
|
61a429365b | ||
|
|
e55f38a962 | ||
|
|
81798583ae | ||
|
|
9cab61cebc | ||
|
|
f290732cb6 | ||
|
|
2dd44852fa | ||
|
|
bd2f17deec | ||
|
|
06faf46bcc | ||
|
|
468e9d539b | ||
|
|
449f407236 | ||
|
|
7efe90e709 | ||
|
|
184e1a5bc4 | ||
|
|
85e7b337a7 | ||
|
|
83759bde1c | ||
|
|
d9b1d45e0e | ||
|
|
df91c20f36 | ||
|
|
c1f6159d69 | ||
|
|
5a0461211a | ||
|
|
1394f790c3 | ||
|
|
4d374ff40c | ||
|
|
0d15435408 | ||
|
|
d4448ba086 | ||
|
|
4bd6fde5b6 | ||
|
|
e5097ace06 | ||
|
|
ca078cbbfd | ||
|
|
6d45105b69 | ||
|
|
40f81af2dd | ||
|
|
d5e6184e75 | ||
|
|
0ec38ba41d | ||
|
|
460a64710a | ||
|
|
9ec62a748f | ||
|
|
a7a80cef99 | ||
|
|
bcaf54b349 | ||
|
|
1f6d0762bb | ||
|
|
1aa1655676 | ||
|
|
0ad66399d0 | ||
|
|
3a6bb14224 | ||
|
|
53eb8051ad | ||
|
|
ed9174f57c | ||
|
|
260d6a1906 | ||
|
|
dc53319c8e | ||
|
|
77369536b3 | ||
|
|
531afcb1e5 | ||
|
|
d6c4eeccf8 | ||
|
|
496b906fd0 | ||
|
|
1be29faea6 | ||
|
|
bc9a7c9890 | ||
|
|
5745276dbb | ||
|
|
acf0282aa0 | ||
|
|
54d942178a | ||
|
|
d84ada373e | ||
|
|
d4d36d0582 | ||
|
|
63e6c634b9 | ||
|
|
4bc558cdbc | ||
|
|
34293fd66b | ||
|
|
5eba4b8200 | ||
|
|
a949307820 | ||
|
|
a714977b5a | ||
|
|
bfcaa51c46 | ||
|
|
d7d3017bc1 | ||
|
|
c7ce8fb7c8 | ||
|
|
65a80de01d | ||
|
|
f6bf210451 | ||
|
|
8c23ec3417 | ||
|
|
97bbd809f7 | ||
|
|
76381cac08 | ||
|
|
13c4ceedc2 | ||
|
|
f0889edf9c | ||
|
|
ab8367c946 | ||
|
|
c5887c8f71 | ||
|
|
7b7ab03d0b | ||
|
|
394caa7934 | ||
|
|
db2860b53e | ||
|
|
6d20df3557 | ||
|
|
2942cd1244 | ||
|
|
174049f489 | ||
|
|
21eae28999 | ||
|
|
f256cbcd5d | ||
|
|
ff8c20f0b4 | ||
|
|
9e0c17a793 | ||
|
|
b4fb00aa52 | ||
|
|
13d0b5e51c | ||
|
|
dc15e4578c | ||
|
|
673da66ee9 | ||
|
|
e397e9bfb4 | ||
|
|
4eca2e9254 | ||
|
|
a1407826b6 | ||
|
|
6d8f83798b | ||
|
|
bfc03c7e95 | ||
|
|
06737ce629 | ||
|
|
6b09840347 | ||
|
|
19b5eecdcd | ||
|
|
f443918433 | ||
|
|
bb74e58d63 | ||
|
|
612e670bb5 | ||
|
|
34bb55cb9f | ||
|
|
1457e82649 | ||
|
|
0bdeaaba18 | ||
|
|
e2286b6110 | ||
|
|
27ba3f3068 | ||
|
|
9859315fea | ||
|
|
73d18dde9e | ||
|
|
0372befc5a | ||
|
|
063799a053 | ||
|
|
297b307726 | ||
|
|
deed66973f | ||
|
|
03486e438d | ||
|
|
e113f59aad | ||
|
|
29ece4824a | ||
|
|
66c62c55e2 | ||
|
|
40852942d2 | ||
|
|
15c8591691 | ||
|
|
e21902a5e3 | ||
|
|
a5d42e8ccd | ||
|
|
0923df7250 | ||
|
|
37f5258210 | ||
|
|
19819f7d48 | ||
|
|
40f74cdac2 | ||
|
|
e4ba3b9721 | ||
|
|
9a221585b9 | ||
|
|
069a0ff306 | ||
|
|
3153fb91da | ||
|
|
92d4d27fa8 | ||
|
|
209fc4a65c | ||
|
|
5e9ecedecd | ||
|
|
613df32bf1 | ||
|
|
7606f37695 | ||
|
|
a2f758a46b | ||
|
|
2a477abe5f | ||
|
|
5f027820f4 | ||
|
|
9e75861546 | ||
|
|
4060596474 | ||
|
|
2e5888c8af | ||
|
|
03d1188e14 | ||
|
|
c91b0c209a | ||
|
|
531973194c | ||
|
|
4d1face275 | ||
|
|
ca7503f1f0 | ||
|
|
9c163b4166 | ||
|
|
742afb1c09 | ||
|
|
f9f70f001b | ||
|
|
edf34efd86 | ||
|
|
72ebed95da | ||
|
|
975a6ab8bf | ||
|
|
3352301b67 | ||
|
|
358260f766 | ||
|
|
b58052ce34 | ||
|
|
de9ad8082b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ config.gypi
|
||||
// osx garbage
|
||||
*.DS_Store
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
6.9.4
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
2.7.9
|
||||
16
.travis.yml
16
.travis.yml
@@ -5,6 +5,14 @@ os:
|
||||
- osx
|
||||
- linux
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- build-essential
|
||||
- libxext-dev
|
||||
- libxtst-dev
|
||||
- libxkbfile-dev
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
@@ -17,8 +25,12 @@ cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- app/node_modules
|
||||
- $HOME/.electron
|
||||
- $HOME/.cache
|
||||
|
||||
script:
|
||||
- npm run travis
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://zulip.org/zulipbot/travis
|
||||
on_success: always
|
||||
on_failure: always
|
||||
@@ -1,37 +1,47 @@
|
||||
#Contributing Guidelines
|
||||
# Contributing Guidelines
|
||||
|
||||
Thanks for taking the time to contribute!
|
||||
|
||||
The following is a set of guidelines for contributing to zulip-electron. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request.
|
||||
The following is a set of guidelines for contributing to Zulip Electron Desktop Client. These are just guidelines, not rules, so use your best judgement and feel free to propose changes to this document in a pull request.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron please head over to [this](http://jlord.us/essential-electron/) great article.
|
||||
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](http://jlord.us/essential-electron/) great article.
|
||||
|
||||
## Community
|
||||
|
||||
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
|
||||
|
||||
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/electron).
|
||||
|
||||
## Issue
|
||||
Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/zulip/zulip-electron/issues/new). Please pay attention to following points while opening an issue.
|
||||
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-electron/issues/new).
|
||||
|
||||
The [zulipbot](https://github.com/zulip/zulipbot) helps to claim an issue by commenting the following in the comment section: "**@zulipbot** claim". **@zulipbot** will assign you to the issue and label the issue as **in progress**. For more details, check out [**@zulipbot**](https://github.com/zulip/zulipbot).
|
||||
|
||||
Please pay attention to the following points while opening an issue.
|
||||
|
||||
### Does it happen on web browsers? (especially Chrome)
|
||||
Zulip-Desktop is based on Electron, which integrates the Chrome engine within a standalone application.
|
||||
If the problem you encounter can be reproduced on web browsers, it may be an issue with Zulip web app.
|
||||
Zulip's desktop client is based on Electron, which integrates the Chrome engine within a standalone application.
|
||||
If the problem you encounter can be reproduced on web browsers, it may be an issue with [Zulip web app](https://github.com/zulip/zulip).
|
||||
|
||||
### Write detailed information
|
||||
Detailed information is very helpful to understand the problem.
|
||||
Detailed information is very helpful to understand an issue.
|
||||
|
||||
For example:
|
||||
* How to reproduce, step-by-step
|
||||
* Expected behavior (or what is wrong)
|
||||
* Screenshots (for GUI issues)
|
||||
* Application version
|
||||
* Operating system
|
||||
* Zulip-Desktop version
|
||||
* How to reproduce the issue, step-by-step.
|
||||
* The expected behavior (or what is wrong).
|
||||
* Screenshots for GUI issues.
|
||||
* The application version.
|
||||
* The operating system.
|
||||
* The Zulip-Desktop version.
|
||||
|
||||
|
||||
## Pull Requests
|
||||
Pull Requests are welcome.
|
||||
Pull Requests are always welcome.
|
||||
|
||||
1. When you edit the code, please run `npm run test` to check formatting of your code before git commit.
|
||||
1. When you edit the code, please run `npm run test` to check the formatting of your code before you `git commit`.
|
||||
2. Ensure the PR description clearly describes the problem and solution. It should include:
|
||||
* Operating System version on which you tested
|
||||
* Zulip-Desktop version on which you tested
|
||||
* The relevant issue number if applicable
|
||||
* The operating system on which you tested.
|
||||
* The Zulip-Desktop version on which you tested.
|
||||
* The relevant issue number, if applicable.
|
||||
|
||||
79
README.md
79
README.md
@@ -1,80 +1,39 @@
|
||||
# Zulip Desktop Client
|
||||
# Zulip Desktop Client
|
||||
[](https://travis-ci.org/zulip/zulip-electron)
|
||||
[](https://ci.appveyor.com/project/akashnimare/zulip-electron/branch/master)
|
||||
[](https://github.com/sindresorhus/xo)
|
||||
|
||||
This is an experimental replacement for the [Zulip Desktop
|
||||
app](https://github.com/zulip/zulip-desktop) implemented in
|
||||
[Electron](http://electron.atom.io/).
|
||||
Desktop client for Zulip. Available for Mac, Linux and Windows.
|
||||
|
||||
The goal is to achieve feature-compatibility with the old desktop app
|
||||
and then start adding cool features like easy support for
|
||||
multi-account, auto-updates etc.
|
||||
<img src="http://i.imgur.com/ChzTq4F.png"/>
|
||||
|
||||
## Prerequisites
|
||||
* node >= v6.3.1
|
||||
* npm >= 3.10.3
|
||||
* If you're on Debian or Ubuntu, you'll also need to install
|
||||
`nodejs-legacy`:
|
||||
```sh
|
||||
$ sudo apt-get install nodejs-legacy
|
||||
```
|
||||
# Download
|
||||
You can download the latest version from the [Releases](https://github.com/zulip/zulip-electron/releases/latest) page.
|
||||
|
||||
## Installation
|
||||
|
||||
Clone the source locally:
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/zulip/zulip-electron
|
||||
$ cd zulip-electron
|
||||
```
|
||||
|
||||
Install project dependencies:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
```
|
||||
Start the app:
|
||||
|
||||
```sh
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Start and watch changes
|
||||
|
||||
```sh
|
||||
$ npm run dev
|
||||
```
|
||||
# Making a release
|
||||
|
||||
To package app into an installer use command:
|
||||
```
|
||||
npm run dist
|
||||
```
|
||||
It will start the packaging process for operating system you are running this command on. Ready for distribution file (e.g. dmg, windows installer, deb package) will be outputted to `dist` directory.
|
||||
|
||||
You can create Windows installer only when running on Windows, the same is true for Linux and OSX. So to generate all three installers you need all three operating systems.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Native Notifications
|
||||
- [x] SpellChecker
|
||||
- [x] OSX/Win/Linux installer
|
||||
- [x] Automatic Updates (macOS)
|
||||
- [x] Keyboard shortcuts
|
||||
# Features
|
||||
* Sign in to multiple teams
|
||||
* Native desktop Notifications
|
||||
* SpellChecker
|
||||
* OSX/Win/Linux installers
|
||||
* Automatic Updates (macOS/Windows)
|
||||
* Keyboard shortcuts
|
||||
|
||||
Description | Keys
|
||||
-----------------------| -----------------------
|
||||
Default shortcuts | <kbd>Cmd/Ctrl</kbd> <kbd>k</kbd>
|
||||
Change Zulip Server | <kbd>Cmd/Ctrl</kbd> <kbd>,</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.
|
||||
|
||||
## Contribute
|
||||
# Contribute
|
||||
|
||||
If you want to contribute please make sure to read [our documentation about contributing](./CONTRIBUTING.md) first.
|
||||
|
||||
* [Issue Tracker](https://github.com/zulip/zulip-electron/issues)
|
||||
* [Source Code](https://github.com/zulip/zulip-electron/)
|
||||
|
||||
# License
|
||||
Released under the [Apache-2.0](./LICENSE) license.
|
||||
|
||||
@@ -1,50 +1,34 @@
|
||||
'use strict';
|
||||
const os = require('os');
|
||||
const {app, autoUpdater, dialog} = require('electron');
|
||||
|
||||
const version = app.getVersion();
|
||||
const platform = os.platform() + '_' + os.arch(); // usually returns darwin_64
|
||||
|
||||
const updaterFeedURL = 'http://zulipdesktop.herokuapp.com/update/' + platform + '/' + version;
|
||||
const {app, dialog} = require('electron');
|
||||
const {autoUpdater} = require('electron-updater');
|
||||
|
||||
function appUpdater() {
|
||||
autoUpdater.setFeedURL(updaterFeedURL);
|
||||
|
||||
// Log whats happening
|
||||
// TODO send autoUpdater events to renderer so that we could
|
||||
// it could console log in developer tools
|
||||
autoUpdater.on('error', err => console.log(err));
|
||||
autoUpdater.on('checking-for-update', () => console.log('checking-for-update'));
|
||||
autoUpdater.on('update-available', () => console.log('update-available'));
|
||||
autoUpdater.on('update-not-available', () => console.log('update-not-available'));
|
||||
const log = require('electron-log');
|
||||
log.transports.file.level = 'info';
|
||||
autoUpdater.logger = log;
|
||||
autoUpdater.allowPrerelease = false;
|
||||
|
||||
// Ask the user if update is available
|
||||
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
|
||||
let message = app.getName() + ' ' + releaseName + ' is now available. It will be installed the next time you restart the application.';
|
||||
if (releaseNotes) {
|
||||
const splitNotes = releaseNotes.split(/[^\r]\n/);
|
||||
message += '\n\nRelease notes:\n';
|
||||
splitNotes.forEach(notes => {
|
||||
message += notes + '\n\n';
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
autoUpdater.on('update-downloaded', (event, info) => {
|
||||
// Ask user to update the app
|
||||
dialog.showMessageBox({
|
||||
type: 'question',
|
||||
buttons: ['Install and Relaunch', 'Later'],
|
||||
defaultId: 0,
|
||||
message: 'A new version of ' + app.getName() + ' has been downloaded',
|
||||
detail: message
|
||||
detail: 'It will be installed the next time you restart the application'
|
||||
}, response => {
|
||||
if (response === 0) {
|
||||
setTimeout(() => autoUpdater.quitAndInstall(), 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
// init for updates
|
||||
// Init for updates
|
||||
autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
exports = module.exports = {
|
||||
module.exports = {
|
||||
appUpdater
|
||||
};
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
const {app} = require('electron').remote;
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const JsonDB = require('node-json-db');
|
||||
const request = require('request');
|
||||
|
||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
||||
const data = db.getData('/');
|
||||
|
||||
console.log(data.domain);
|
||||
|
||||
// if (data.domain && window.location.href.indexOf(data.domain) === -1) {
|
||||
// window.location.href = data.domain
|
||||
// }
|
||||
// require('electron-connect').client.create();
|
||||
|
||||
window.addDomain = function () {
|
||||
document.getElementById('main').innerHTML = 'checking...';
|
||||
|
||||
let newDomain = document.getElementById('url').value;
|
||||
newDomain = newDomain.replace(/^https?:\/\//, '');
|
||||
|
||||
const domain = 'https://' + newDomain;
|
||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
||||
|
||||
request(checkDomain, (error, response) => {
|
||||
if (!error && response.statusCode !== 404) {
|
||||
document.getElementById('main').innerHTML = 'Connect';
|
||||
db.push('/domain', domain);
|
||||
ipcRenderer.send('new-domain', domain);
|
||||
} else {
|
||||
document.getElementById('main').innerHTML = 'Connect';
|
||||
document.getElementById('server-status').innerHTML = 'Not a vaild Zulip Server.';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,44 +1,28 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const electron = require('electron');
|
||||
const {app} = require('electron');
|
||||
const ipc = require('electron').ipcMain;
|
||||
const electronLocalshortcut = require('electron-localshortcut');
|
||||
const Configstore = require('configstore');
|
||||
const JsonDB = require('node-json-db');
|
||||
const Configstore = require('electron-config');
|
||||
const isDev = require('electron-is-dev');
|
||||
const tray = require('./tray');
|
||||
const appMenu = require('./menu');
|
||||
const {linkIsInternal, skipImages} = require('./link-helper');
|
||||
const {appUpdater} = require('./autoupdater');
|
||||
|
||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
||||
const data = db.getData('/');
|
||||
|
||||
// Handling squirrel.windows events on windows
|
||||
if (require('electron-squirrel-startup')) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
// adds debug features like hotkeys for triggering dev tools and reload
|
||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||
require('electron-debug')();
|
||||
|
||||
const conf = new Configstore('Zulip-Desktop');
|
||||
const conf = new Configstore();
|
||||
|
||||
// prevent window being garbage collected
|
||||
// Setting userAgent so that server-side code can identify the desktop app
|
||||
|
||||
// Prevent window being garbage collected
|
||||
let mainWindow;
|
||||
let targetLink;
|
||||
|
||||
let isQuitting = false;
|
||||
|
||||
// Load this url in main window
|
||||
const staticURL = 'file://' + path.join(__dirname, '../renderer', 'index.html');
|
||||
|
||||
const targetURL = function () {
|
||||
if (data.domain === undefined) {
|
||||
return staticURL;
|
||||
}
|
||||
return data.domain;
|
||||
};
|
||||
const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
|
||||
|
||||
const isAlreadyRunning = app.makeSingleInstance(() => {
|
||||
if (mainWindow) {
|
||||
@@ -51,14 +35,7 @@ const isAlreadyRunning = app.makeSingleInstance(() => {
|
||||
});
|
||||
|
||||
if (isAlreadyRunning) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
function checkWindowURL() {
|
||||
if (data.domain !== undefined) {
|
||||
return data.domain;
|
||||
}
|
||||
return targetLink;
|
||||
return app.quit();
|
||||
}
|
||||
|
||||
function isWindowsOrmacOS() {
|
||||
@@ -71,25 +48,6 @@ const iconPath = () => {
|
||||
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
|
||||
};
|
||||
|
||||
function onClosed() {
|
||||
// dereference the window
|
||||
// for multiple windows store them in an array
|
||||
mainWindow = null;
|
||||
}
|
||||
|
||||
function updateDockBadge(title) {
|
||||
if (title.indexOf('Zulip') === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let messageCount = (/\(([0-9]+)\)/).exec(title);
|
||||
messageCount = messageCount ? Number(messageCount[1]) : 0;
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
app.setBadgeCount(messageCount);
|
||||
}
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
const win = new electron.BrowserWindow({
|
||||
// This settings needs to be saved in config
|
||||
@@ -98,17 +56,38 @@ function createMainWindow() {
|
||||
height: conf.get('height') || 600,
|
||||
icon: iconPath(),
|
||||
minWidth: 600,
|
||||
minHeight: 400,
|
||||
minHeight: 500,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
plugins: true,
|
||||
allowDisplayingInsecureContent: true,
|
||||
nodeIntegration: false
|
||||
nodeIntegration: true
|
||||
},
|
||||
show: false
|
||||
});
|
||||
|
||||
win.on('focus', () => {
|
||||
win.webContents.send('focus');
|
||||
});
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
win.show();
|
||||
});
|
||||
|
||||
win.loadURL(mainURL);
|
||||
|
||||
// Keep the app running in background on close event
|
||||
win.on('close', e => {
|
||||
if (!isQuitting) {
|
||||
e.preventDefault();
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
app.hide();
|
||||
} else {
|
||||
win.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
win.loadURL(targetURL());
|
||||
win.on('closed', onClosed);
|
||||
win.setTitle('Zulip');
|
||||
|
||||
// Let's save browser window position
|
||||
@@ -137,7 +116,7 @@ function createMainWindow() {
|
||||
});
|
||||
});
|
||||
|
||||
// on osx it's 'moved'
|
||||
// On osx it's 'moved'
|
||||
win.on('move', function () {
|
||||
const pos = this.getPosition();
|
||||
conf.set({
|
||||
@@ -146,24 +125,25 @@ function createMainWindow() {
|
||||
});
|
||||
});
|
||||
|
||||
// stop page to update it's title
|
||||
win.on('page-title-updated', (e, title) => {
|
||||
e.preventDefault();
|
||||
updateDockBadge(title);
|
||||
// To destroy tray icon when navigate to a new URL
|
||||
win.webContents.on('will-navigate', e => {
|
||||
if (e) {
|
||||
win.webContents.send('destroytray');
|
||||
}
|
||||
});
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
// TODO - fix certificate errors
|
||||
app.commandLine.appendSwitch('ignore-certificate-errors', 'true');
|
||||
// eslint-disable-next-line max-params
|
||||
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
|
||||
event.preventDefault();
|
||||
callback(true);
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// unregister all the shortcuts so that they don't interfare with other apps
|
||||
// Unregister all the shortcuts so that they don't interfare with other apps
|
||||
electronLocalshortcut.unregisterAll(mainWindow);
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
@@ -175,65 +155,73 @@ app.on('activate', () => {
|
||||
app.on('ready', () => {
|
||||
electron.Menu.setApplicationMenu(appMenu);
|
||||
mainWindow = createMainWindow();
|
||||
tray.create(mainWindow);
|
||||
|
||||
const page = mainWindow.webContents;
|
||||
|
||||
// TODO - use global shortcut instead
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
|
||||
// page.send('reload');
|
||||
mainWindow.reload();
|
||||
// page.send('destroytray');
|
||||
});
|
||||
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
|
||||
if (page.canGoBack()) {
|
||||
page.goBack();
|
||||
}
|
||||
page.send('back');
|
||||
});
|
||||
|
||||
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
|
||||
if (page.canGoForward()) {
|
||||
page.goForward();
|
||||
}
|
||||
page.send('forward');
|
||||
});
|
||||
|
||||
page.on('dom-ready', () => {
|
||||
page.insertCSS(fs.readFileSync(path.join(__dirname, 'preload.css'), 'utf8'));
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
page.on('new-window', (event, url) => {
|
||||
if (linkIsInternal(checkWindowURL(), url) && url.match(skipImages) === null) {
|
||||
event.preventDefault();
|
||||
return mainWindow.loadURL(url);
|
||||
}
|
||||
event.preventDefault();
|
||||
electron.shell.openExternal(url);
|
||||
});
|
||||
|
||||
page.once('did-frame-finish-load', () => {
|
||||
const checkOS = isWindowsOrmacOS();
|
||||
if (checkOS && !isDev) {
|
||||
// Initate auto-updates on macOs and windows
|
||||
// Initate auto-updates on MacOS and Windows
|
||||
appUpdater();
|
||||
}
|
||||
});
|
||||
electron.powerMonitor.on('resume', () => {
|
||||
mainWindow.reload();
|
||||
mainWindow.webContents.send('destroytray');
|
||||
});
|
||||
|
||||
ipc.on('focus-app', () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
ipc.on('quit-app', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
ipc.on('reload-main', () => {
|
||||
page.reload();
|
||||
});
|
||||
|
||||
ipc.on('toggle-app', () => {
|
||||
if (mainWindow.isVisible()) {
|
||||
mainWindow.hide();
|
||||
} else {
|
||||
mainWindow.show();
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('update-badge', (event, messageCount) => {
|
||||
if (process.platform === 'darwin') {
|
||||
app.setBadgeCount(messageCount);
|
||||
}
|
||||
page.send('tray', messageCount);
|
||||
});
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
// unregister all the shortcuts so that they don't interfare with other apps
|
||||
// Unregister all the shortcuts so that they don't interfare with other apps
|
||||
electronLocalshortcut.unregisterAll(mainWindow);
|
||||
});
|
||||
|
||||
ipc.on('new-domain', (e, domain) => {
|
||||
// mainWindow.loadURL(domain);
|
||||
if (!mainWindow) {
|
||||
mainWindow = createMainWindow();
|
||||
mainWindow.loadURL(domain);
|
||||
} else if (mainWindow.isMinimized()) {
|
||||
mainWindow.loadURL(domain);
|
||||
mainWindow.show();
|
||||
} else {
|
||||
mainWindow.loadURL(domain);
|
||||
}
|
||||
targetLink = domain;
|
||||
app.on('before-quit', () => {
|
||||
isQuitting = true;
|
||||
});
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
const wurl = require('wurl');
|
||||
|
||||
// Check link if it's internal/external
|
||||
function linkIsInternal(currentUrl, newUrl) {
|
||||
const currentDomain = wurl('hostname', currentUrl);
|
||||
const newDomain = wurl('hostname', newUrl);
|
||||
return currentDomain === newDomain;
|
||||
}
|
||||
|
||||
// We'll be needing this to open images in default browser
|
||||
const skipImages = '.jpg|.gif|.png|.jpeg|.JPG|.PNG';
|
||||
|
||||
exports = module.exports = {
|
||||
linkIsInternal, skipImages
|
||||
};
|
||||
@@ -2,13 +2,13 @@
|
||||
const os = require('os');
|
||||
const electron = require('electron');
|
||||
|
||||
const {dialog} = require('electron');
|
||||
|
||||
const app = electron.app;
|
||||
const BrowserWindow = electron.BrowserWindow;
|
||||
const shell = electron.shell;
|
||||
const appName = app.getName();
|
||||
|
||||
const {addDomain, about} = require('./windowmanager');
|
||||
|
||||
function sendAction(action) {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
|
||||
@@ -19,12 +19,20 @@ function sendAction(action) {
|
||||
win.webContents.send(action);
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
const ses = win.webContents.session;
|
||||
ses.clearCache(() => {
|
||||
dialog.showMessageBox({type: 'info', buttons: [], message: 'Cache cleared!'});
|
||||
});
|
||||
}
|
||||
|
||||
const viewSubmenu = [
|
||||
{
|
||||
label: 'Reload',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
sendAction('reload');
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -65,13 +73,30 @@ const viewSubmenu = [
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
label: 'Toggle Tray Icon',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.send('toggletray');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Toggle DevTools for Zulip App',
|
||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Toggle DevTools for Active Tab',
|
||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('tab-devtools');
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -108,18 +133,22 @@ const darwinTpl = [
|
||||
submenu: [
|
||||
{
|
||||
label: 'Zulip desktop',
|
||||
click() {
|
||||
about();
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('open-about');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Change Zulip Server',
|
||||
label: 'Manage Zulip Servers',
|
||||
accelerator: 'Cmd+,',
|
||||
click() {
|
||||
addDomain();
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('open-settings');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -134,8 +163,15 @@ const darwinTpl = [
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Clear Cache',
|
||||
click() {
|
||||
clearCache();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Log Out',
|
||||
accelerator: 'Cmd+L',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('log-out');
|
||||
@@ -234,18 +270,22 @@ const otherTpl = [
|
||||
submenu: [
|
||||
{
|
||||
label: 'Zulip desktop',
|
||||
click() {
|
||||
about();
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('open-about');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Change Zulip Server',
|
||||
label: 'Manage Zulip Servers',
|
||||
accelerator: 'Ctrl+,',
|
||||
click() {
|
||||
addDomain();
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('open-settings');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -263,8 +303,15 @@ const otherTpl = [
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Clear Cache',
|
||||
click() {
|
||||
clearCache();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Log Out',
|
||||
accelerator: 'Ctrl+L',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('log-out');
|
||||
@@ -275,7 +322,8 @@ const otherTpl = [
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'quit'
|
||||
role: 'quit',
|
||||
accelerator: 'Ctrl+Q'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -312,7 +360,6 @@ const otherTpl = [
|
||||
{
|
||||
role: 'selectall'
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/* We'll be overriding default styling so that app look more native * /
|
||||
@@ -1,52 +0,0 @@
|
||||
'use strict';
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const {webFrame} = require('electron');
|
||||
// Handle zooming functionality
|
||||
|
||||
const zoomIn = () => {
|
||||
webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1);
|
||||
};
|
||||
|
||||
const zoomOut = () => {
|
||||
webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1);
|
||||
};
|
||||
|
||||
const zoomActualSize = () => {
|
||||
webFrame.setZoomFactor(1);
|
||||
};
|
||||
|
||||
// Get zooming actions from main process
|
||||
ipcRenderer.on('zoomIn', () => {
|
||||
zoomIn();
|
||||
});
|
||||
|
||||
ipcRenderer.on('zoomOut', () => {
|
||||
zoomOut();
|
||||
});
|
||||
|
||||
ipcRenderer.on('zoomActualSize', () => {
|
||||
zoomActualSize();
|
||||
});
|
||||
|
||||
ipcRenderer.on('log-out', () => {
|
||||
// create the menu for the below
|
||||
document.querySelector('.dropdown-toggle').click();
|
||||
|
||||
const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
|
||||
nodes[nodes.length - 1].click();
|
||||
});
|
||||
|
||||
ipcRenderer.on('shortcut', () => {
|
||||
// create the menu for the below
|
||||
document.querySelector('.dropdown-toggle').click();
|
||||
|
||||
const nodes = document.querySelectorAll('.dropdown-menu li:nth-child(4) a');
|
||||
nodes[nodes.length - 1].click();
|
||||
});
|
||||
|
||||
// To prevent failing this script on linux we need to load it after the document loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
require('./spellchecker')();
|
||||
});
|
||||
|
||||
require('./domain')();
|
||||
@@ -1,22 +0,0 @@
|
||||
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
|
||||
|
||||
// Implement spellcheck using electron api
|
||||
|
||||
window.spellCheckHandler = new SpellCheckHandler();
|
||||
window.spellCheckHandler.attachToInput();
|
||||
|
||||
// Start off as US English
|
||||
window.spellCheckHandler.switchLanguage('en-US');
|
||||
|
||||
const contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler);
|
||||
const contextMenuListener = new ContextMenuListener(info => {
|
||||
contextMenuBuilder.showPopupMenu(info);
|
||||
});
|
||||
|
||||
// Clean up events after you navigate away from this page;
|
||||
// otherwise, you may experience errors
|
||||
window.addEventListener('beforeunload', () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
spellCheckHandler.unsubscribe();
|
||||
contextMenuListener.unsubscribe();
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const electron = require('electron');
|
||||
const app = require('electron').app;
|
||||
const {addDomain, about} = require('./windowmanager');
|
||||
|
||||
let tray = null;
|
||||
|
||||
const APP_ICON = path.join(__dirname, '../resources/tray', 'tray');
|
||||
|
||||
const iconPath = () => {
|
||||
if (process.platform === 'linux') {
|
||||
return APP_ICON + 'linux.png';
|
||||
}
|
||||
return APP_ICON + (process.platform === 'win32' ? 'win.ico' : 'osx.png');
|
||||
};
|
||||
|
||||
exports.create = () => {
|
||||
const contextMenu = electron.Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'About',
|
||||
click() {
|
||||
about();
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Change Zulip server',
|
||||
click() {
|
||||
addDomain();
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Reload',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
click() {
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
tray = new electron.Tray(iconPath());
|
||||
tray.setToolTip(`${app.getName()}`);
|
||||
tray.setContextMenu(contextMenu);
|
||||
};
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const electron = require('electron');
|
||||
|
||||
let domainWindow;
|
||||
let aboutWindow;
|
||||
|
||||
function onClosed() {
|
||||
// dereference the window
|
||||
domainWindow = null;
|
||||
aboutWindow = null;
|
||||
}
|
||||
|
||||
// Change Zulip server Window
|
||||
function createdomainWindow() {
|
||||
const domainwin = new electron.BrowserWindow({
|
||||
frame: false,
|
||||
height: 300,
|
||||
resizable: false,
|
||||
width: 400
|
||||
});
|
||||
const domainURL = 'file://' + path.join(__dirname, '../renderer', 'pref.html');
|
||||
domainwin.loadURL(domainURL);
|
||||
domainwin.on('closed', onClosed);
|
||||
|
||||
return domainwin;
|
||||
}
|
||||
|
||||
// Call this window onClick addDomain in tray
|
||||
function addDomain() {
|
||||
domainWindow = createdomainWindow();
|
||||
domainWindow.show();
|
||||
}
|
||||
|
||||
// About window
|
||||
function createAboutWindow() {
|
||||
const aboutwin = new electron.BrowserWindow({
|
||||
width: 500,
|
||||
height: 500,
|
||||
title: 'About Zulip Desktop',
|
||||
show: false,
|
||||
center: true,
|
||||
fullscreen: false,
|
||||
fullscreenable: false,
|
||||
resizable: false
|
||||
});
|
||||
const aboutURL = 'file://' + path.join(__dirname, '../renderer', 'about.html');
|
||||
aboutwin.loadURL(aboutURL);
|
||||
aboutwin.on('closed', onClosed);
|
||||
|
||||
// stop page to update it's title
|
||||
aboutwin.on('page-title-updated', e => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
aboutwin.on('closed', onClosed);
|
||||
|
||||
return aboutwin;
|
||||
}
|
||||
|
||||
// Call this onClick About in tray
|
||||
function about() {
|
||||
aboutWindow = createAboutWindow();
|
||||
aboutWindow.show();
|
||||
}
|
||||
|
||||
exports = module.exports = {
|
||||
addDomain, about
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "0.5.3",
|
||||
"version": "1.1.0-beta",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
"email":"<svnitakash@gmail.com>",
|
||||
"copyright": "©2016 Kandra Labs, Inc.",
|
||||
"email": "<svnitakash@gmail.com>",
|
||||
"copyright": "©2017 Kandra Labs, Inc.",
|
||||
"author": {
|
||||
"name": "Akash Nimare",
|
||||
"email": "svnitakash@gmail.com"
|
||||
@@ -27,17 +27,16 @@
|
||||
"InstantMessaging"
|
||||
],
|
||||
"dependencies": {
|
||||
"electron-is-dev": "*",
|
||||
"electron-squirrel-startup":"*",
|
||||
"configstore": "^2.0.0",
|
||||
"dialogs": "1.1.14",
|
||||
"electron-context-menu": "0.4.0",
|
||||
"electron-debug": "^1.0.0",
|
||||
"electron-dl": "^0.2.0",
|
||||
"electron-localshortcut": "^0.6.1",
|
||||
"node-json-db": "^0.7.2",
|
||||
"request": "^2.74.0",
|
||||
"electron-spellchecker": "^0.5.12",
|
||||
"wurl": "^2.1.0"
|
||||
"electron-config": "0.2.1",
|
||||
"electron-debug": "1.1.0",
|
||||
"electron-is-dev": "0.1.2",
|
||||
"electron-localshortcut": "1.0.0",
|
||||
"electron-log": "1.3.0",
|
||||
"electron-spellchecker": "1.0.8",
|
||||
"electron-updater": "1.11.2",
|
||||
"https": "^1.0.0",
|
||||
"node-json-db": "0.7.3",
|
||||
"request": "2.79.0",
|
||||
"wurl": "2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,19 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="about">
|
||||
<center><img src="../resources/zulip.png"></center>
|
||||
<center><p class="detail" id="version"> Version : ?.?.? </p>
|
||||
<center><p class="detail"> License : Apache </p>
|
||||
<center><p class="detail"> Maintainer : Zulip </p>
|
||||
<p class="left"><a class="bug" onclick="linkInBrowser()" href="#">Found bug?</a></p>
|
||||
<img class="logo" src="../resources/zulip.png" />
|
||||
<p class="detail" id="version">version ?.?.?</p>
|
||||
<div class="maintenance-info">
|
||||
<p class="detail maintainer">Maintained by Zulip</p>
|
||||
<p class="detail license">Available under the Apache License</p>
|
||||
<a class="bug" onclick="linkInBrowser()" href="#">Found bug?</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
const app = require('electron').remote.app;
|
||||
const version_tag = document.getElementById('version');
|
||||
version_tag.innerHTML = ' Version : ' + app.getVersion() + ' ';
|
||||
version_tag.innerHTML = 'version ' + app.getVersion();
|
||||
|
||||
function linkInBrowser(event) {
|
||||
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
body {
|
||||
background: #6BB6C7;
|
||||
background: #fafafa;
|
||||
font-family: menu, "Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#version {
|
||||
color: #aaa;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.about {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.left {
|
||||
position: absolute;
|
||||
top:89%;
|
||||
left:76%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about p {
|
||||
@@ -18,10 +25,49 @@ body {
|
||||
}
|
||||
|
||||
.about img {
|
||||
width:160px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.detail {
|
||||
text-align: left;
|
||||
margin-left: 35%;
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail.maintainer {
|
||||
font-size: 1.2em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail.license {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.maintenance-info {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 20px;
|
||||
left: 0px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.maintenance-info p {
|
||||
margin: 0;
|
||||
font-size: 1em;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.maintenance-info .bug {
|
||||
display: inline-block;
|
||||
padding: 8px 15px;
|
||||
margin-top: 30px;
|
||||
text-decoration: none;
|
||||
background-color: #52c2af;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.maintenance-info .bug:hover {
|
||||
background-color: #32a692;
|
||||
}
|
||||
|
||||
@@ -1,375 +1,184 @@
|
||||
@charset "UTF-8";
|
||||
header,
|
||||
section {
|
||||
display: block
|
||||
}
|
||||
html {
|
||||
font-size: 100%;
|
||||
overflow-y: scroll;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
html,
|
||||
button,
|
||||
input {
|
||||
font-family: "Helvetica Neue", Arial, sans-serif
|
||||
}
|
||||
html,
|
||||
body {
|
||||
/*******************
|
||||
* General rules *
|
||||
*******************/
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0
|
||||
}
|
||||
img {
|
||||
border: 0;
|
||||
-ms-interpolation-mode: bicubic
|
||||
}
|
||||
img {
|
||||
vertical-align: middle
|
||||
}
|
||||
form {
|
||||
margin: 0
|
||||
}
|
||||
fieldset {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0
|
||||
cursor: default;
|
||||
user-select:none;
|
||||
}
|
||||
button,
|
||||
input {
|
||||
font-size: 100%;
|
||||
margin: 0;
|
||||
vertical-align: baseline;
|
||||
box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background: #eee url(../img/ic_loading.gif) no-repeat;
|
||||
background-size: 60px 60px;
|
||||
background-position: center;
|
||||
}
|
||||
button,
|
||||
input {
|
||||
line-height: normal
|
||||
|
||||
#sidebar {
|
||||
background: #222c31;
|
||||
width: 54px;
|
||||
padding: 27px 0 20px 0;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
button {
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
/*******************
|
||||
* Left Sidebar *
|
||||
*******************/
|
||||
#tabs-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
/* Preferred icon size */
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.action-button i {
|
||||
color: #6c8592;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.action-button:hover i {
|
||||
color: #98a9b3;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
margin: 5px 0;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0
|
||||
}
|
||||
hr {
|
||||
display: none
|
||||
}
|
||||
img {
|
||||
max-width: 100%
|
||||
}
|
||||
h1 {
|
||||
color: #111;
|
||||
line-height: 1em;
|
||||
font-weight: 400;
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
text-rendering: optimizelegibility;
|
||||
-webkit-text-stroke: none
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 35px
|
||||
}
|
||||
|
||||
body.container-layout header #logo {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top
|
||||
}
|
||||
body.container-layout header #logo {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-position: -790px 0
|
||||
}
|
||||
button[type=submit] {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
font-size-adjust: auto;
|
||||
vertical-align: bottom;
|
||||
background-color: #e6eaef;
|
||||
border: 2px solid #e6eaef;
|
||||
color: #96a0ac;
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
padding: 6px 17px
|
||||
}
|
||||
.desktop button[type=submit] {
|
||||
-webkit-transition: all .2s ease-in-out;
|
||||
transition: all .2s ease-in-out
|
||||
}
|
||||
.desktop button[type=submit]:hover {
|
||||
background-color: #eff2f5;
|
||||
border: 2px solid #eff2f5;
|
||||
color: #96a0ac
|
||||
}
|
||||
button[type=submit]:focus {
|
||||
outline: 0
|
||||
}
|
||||
button[type=submit].btn-primary {
|
||||
background-color: #20b36c;
|
||||
border: 2px solid #20b36c;
|
||||
color: #fff
|
||||
}
|
||||
.desktop button[type=submit].btn-primary:hover {
|
||||
background-color: #39ca83;
|
||||
border: 2px solid #39ca83;
|
||||
color: #fff
|
||||
}
|
||||
button[type=submit].btn-primary:focus {
|
||||
outline: 0
|
||||
}
|
||||
button[type=submit].btn-large {
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
padding: 12px 30px
|
||||
}
|
||||
input[type=text] {
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
font-weight: 200;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border: 1px solid #cad0d7;
|
||||
color: #000;
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
height: 42px;
|
||||
padding: 10px 10px
|
||||
}
|
||||
.desktop input[type=text]:hover {
|
||||
background-color: #fff;
|
||||
border-color: #bbc3cc;
|
||||
color: #111
|
||||
}
|
||||
input[type=text]:focus {
|
||||
background-color: #fff;
|
||||
border-color: #20b36c!important;
|
||||
color: #000;
|
||||
outline: 0
|
||||
}
|
||||
input[type=text]::-webkit-input-placeholder {
|
||||
color: #8e959e
|
||||
}
|
||||
input[type=text]::-moz-placeholder {
|
||||
color: #8e959e
|
||||
}
|
||||
input[type=text]:-ms-input-placeholder {
|
||||
color: #8e959e
|
||||
}
|
||||
.form-large input[type=text] {
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
height: 54px;
|
||||
padding: 12px 15px
|
||||
}
|
||||
.control-group {
|
||||
margin-top: 40px
|
||||
}
|
||||
h1 {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
hyphens: auto
|
||||
}
|
||||
.section-main {
|
||||
position: relative
|
||||
}
|
||||
body {
|
||||
color: #111;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
background: #edf1f3;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box
|
||||
}
|
||||
.desktop body {
|
||||
padding-top: 116px
|
||||
}
|
||||
body.container-layout {
|
||||
padding: 0!important;
|
||||
background-color: #E6EAEF
|
||||
}
|
||||
header {
|
||||
z-index: 800;
|
||||
border-bottom: 1px solid #dae0e7;
|
||||
background: rgba(255, 255, 255, .97);
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%
|
||||
}
|
||||
header .container {
|
||||
position: relative
|
||||
}
|
||||
.desktop header {
|
||||
position: fixed
|
||||
}
|
||||
header .container {
|
||||
position: relative;
|
||||
height: 75px
|
||||
}
|
||||
.container-layout header {
|
||||
position: relative;
|
||||
border: 0;
|
||||
background: 0 0;
|
||||
padding: 30px 0
|
||||
}
|
||||
.container-layout header .container {
|
||||
height: auto
|
||||
}
|
||||
header #logo {
|
||||
display: block;
|
||||
text-indent: -9999px
|
||||
}
|
||||
|
||||
header #logo {
|
||||
position: absolute!important;
|
||||
top: 50%;
|
||||
left: 10px;
|
||||
-webkit-transform: translate(0, -50%);
|
||||
transform: translate(0, -50%)
|
||||
}
|
||||
body.container-layout header #logo {
|
||||
position: relative!important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-transform: translate(0, 0);
|
||||
transform: translate(0, 0);
|
||||
margin: 0 auto;
|
||||
display: block
|
||||
}
|
||||
.content {
|
||||
padding-bottom: 40px;
|
||||
overflow: hidden
|
||||
}
|
||||
.container-layout .content {
|
||||
max-width: 660px;
|
||||
margin: 0 auto
|
||||
}
|
||||
.content .server {
|
||||
position: relative;
|
||||
margin: 0 10px
|
||||
}
|
||||
.content .server .container {
|
||||
.tab.active::before {
|
||||
content: "";
|
||||
background: #fff;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 50px 0 0;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
width: 4px;
|
||||
position: absolute;
|
||||
height: 35px;
|
||||
left: -10px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.tab .server-tab {
|
||||
background: #a4d3c4;
|
||||
background-size: 100%;
|
||||
border-radius: 4px;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
max-width: 580px
|
||||
}
|
||||
.content .server h1 {
|
||||
margin: 5px 0;
|
||||
z-index: 11;
|
||||
line-height: 31px;
|
||||
color: #194a2b;
|
||||
text-align: center;
|
||||
padding: 0 10%
|
||||
overflow: hidden;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.content .server h1 {
|
||||
font-size: 2.4em;
|
||||
line-height: 1.2em;
|
||||
margin-bottom: 10px
|
||||
|
||||
.tab .server-tab:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.content .server fieldset {
|
||||
padding: 25px 10% 80px 39px;
|
||||
position: relative
|
||||
|
||||
.tab .functional-tab {
|
||||
background: #eee;
|
||||
}
|
||||
.content .server fieldset .control-group .control-field input {
|
||||
width: 100%
|
||||
|
||||
.tab .functional-tab i {
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
}
|
||||
.content .server button {
|
||||
width: 100%
|
||||
|
||||
.tab.active .server-tab {
|
||||
opacity: 1;
|
||||
}
|
||||
@media screen and (min-width: 749px) {
|
||||
input[type=text] {
|
||||
width: 60%
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 1071px) {
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.3em
|
||||
}
|
||||
}
|
||||
.container {
|
||||
width: 1070px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.container:before,
|
||||
.container:after {
|
||||
content: "";
|
||||
display: table
|
||||
}
|
||||
.container:after {
|
||||
clear: both
|
||||
}
|
||||
.responsive .container {
|
||||
max-width: 1070px;
|
||||
width: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.responsive .container:before,
|
||||
.responsive .container:after {
|
||||
content: "";
|
||||
display: table
|
||||
}
|
||||
.responsive .container:after {
|
||||
clear: both
|
||||
}
|
||||
@media screen and (max-width: 480px) {
|
||||
.responsive h1 {
|
||||
font-size: 1.7em;
|
||||
line-height: 1.3em
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 481px) and (max-width: 640px) {
|
||||
.responsive h1 {
|
||||
font-size: 1.9em;
|
||||
line-height: 1.3em
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 641px) and (max-width: 748px) {
|
||||
.responsive h1 {
|
||||
font-size: 2.2em;
|
||||
line-height: 1.3em
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 748px) {
|
||||
.responsive input[type=text] {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 749px) and (max-width: 1070px) {
|
||||
.responsive h1 {
|
||||
font-size: 2.6em;
|
||||
line-height: 1.3em
|
||||
}
|
||||
}
|
||||
#server-status {
|
||||
|
||||
.tab .server-tab-badge.active {
|
||||
border-radius: 9px;
|
||||
min-width: 11px;
|
||||
padding: 0 3px;
|
||||
height: 17px;
|
||||
background-color: #f44336;
|
||||
font-size: 10px;
|
||||
font-family: sans-serif;
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
z-index: 15;
|
||||
top: -2px;
|
||||
float: right;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
color: #c71212;
|
||||
line-height: 17px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab .server-tab-badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab .server-tab-badge.close-button {
|
||||
width: 16px;
|
||||
padding: 0 0 0 1px;
|
||||
}
|
||||
|
||||
.tab .server-tab-badge.close-button i {
|
||||
font-size: 13px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
/*******************
|
||||
* Webview Area *
|
||||
*******************/
|
||||
webview {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
webview.disabled {
|
||||
flex: 0 1;
|
||||
height: 0;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
webview:focus {
|
||||
outline: 0px solid transparent;
|
||||
}
|
||||
|
||||
42
app/renderer/css/network.css
Normal file
42
app/renderer/css/network.css
Normal file
@@ -0,0 +1,42 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
cursor: default;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
user-select:none;
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: "Trebuchet MS", Helvetica, sans-serif;
|
||||
margin: 100px 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
#description {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#reconnect {
|
||||
font-size: 16px;
|
||||
background: #009688;
|
||||
color: #fff;
|
||||
width: 84px;
|
||||
height: 32px;
|
||||
border-radius: 5px;
|
||||
line-height: 32px;
|
||||
margin: 20px auto 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#reconnect:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
body{
|
||||
background-color: #6BB6C7;
|
||||
}
|
||||
|
||||
.form {
|
||||
position: absolute;
|
||||
top: 35%;
|
||||
width: 300px;
|
||||
left: 9%;
|
||||
}
|
||||
|
||||
.close {
|
||||
background: transparent url('../img/close.png') no-repeat 4px 4px;
|
||||
background-size: 24px 24px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
text-indent: -10000px;
|
||||
top: 6px;
|
||||
width: 32px;
|
||||
z-index: 1;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-family: sans-serif;
|
||||
font-size: 18px;
|
||||
appearance: none;
|
||||
box-shadow: none;
|
||||
border-radius: none;
|
||||
color: #646464;
|
||||
}
|
||||
input[type="text"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form input[type="text"] {
|
||||
padding: 10px;
|
||||
border: solid 1px #dcdcdc;
|
||||
transition: box-shadow 0.3s, border 0.3s;
|
||||
}
|
||||
.form input[type="text"]:focus,
|
||||
.form input[type="text"].focus {
|
||||
border: solid 1px #707070;
|
||||
box-shadow: 0 0 5px 1px #969696;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
color: #fff;
|
||||
padding: 12px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
margin-left: 107px;
|
||||
margin-top: 24px;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
background: #137b86;
|
||||
}
|
||||
button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
#urladded {
|
||||
font-size: 20px;
|
||||
position: absolute;
|
||||
font-family: 'opensans';
|
||||
top: -61%;
|
||||
left: 25%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#pic {
|
||||
width: 20px;
|
||||
left: 36%;
|
||||
margin-left: 4px;
|
||||
top: 63%;
|
||||
display: none;
|
||||
position: absolute;
|
||||
}
|
||||
181
app/renderer/css/preference.css
Normal file
181
app/renderer/css/preference.css
Normal file
@@ -0,0 +1,181 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
cursor: default;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
width: 80px;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#tabs-container {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 5px 0;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #464e5a;
|
||||
cursor: default;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab.active::before {
|
||||
background: #464e5a;
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
content: '';
|
||||
}
|
||||
|
||||
#settings-header {
|
||||
font-size: 22px;
|
||||
color: #5c6166;
|
||||
}
|
||||
|
||||
#settings-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 30px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.server-info-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 4px 0 6px 0;
|
||||
font-size: 18px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
img.server-info-icon {
|
||||
background: #a4d3c4;
|
||||
background-size: 100%;
|
||||
border-radius: 4px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.server-info-left {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.server-info-right {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.server-info-row {
|
||||
display: flex;
|
||||
line-height: 26px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.server-info-key {
|
||||
width: 40px;
|
||||
margin-right: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.server-info-value {
|
||||
flex-grow: 1;
|
||||
font-size: 14px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
border-bottom: #ddd 1px solid;
|
||||
outline-width: 0;
|
||||
background: transparent;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.server-info-value:focus {
|
||||
border-bottom: #b0d8ce 2px solid;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
color: #235d3a;
|
||||
vertical-align: middle;
|
||||
margin: 10px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.action i {
|
||||
margin-right: 5px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.settings-pane {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.action:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action.disabled:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.action.disabled {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.server-info.active {
|
||||
background: #ecf4ef;
|
||||
}
|
||||
|
||||
.server-info {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.save-server-button {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 7px 14px;
|
||||
background-color: #52c2af;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.save-server-button:hover {
|
||||
background-color: #32a692;
|
||||
}
|
||||
BIN
app/renderer/fonts/MaterialIcons-Regular.ttf
Normal file
BIN
app/renderer/fonts/MaterialIcons-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 803 B |
BIN
app/renderer/img/ic_loading.gif
Normal file
BIN
app/renderer/img/ic_loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
app/renderer/img/ic_server_tab_default.png
Normal file
BIN
app/renderer/img/ic_server_tab_default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/renderer/img/icon.png
Normal file
BIN
app/renderer/img/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
BIN
app/renderer/img/zulip_network.png
Normal file
BIN
app/renderer/img/zulip_network.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
@@ -1,42 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="responsive desktop">
|
||||
<!--<![endif]-->
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Login - Zulip</title>
|
||||
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
|
||||
</head>
|
||||
<body class="container-layout">
|
||||
<div class="section-main">
|
||||
<header>
|
||||
<div class="container">
|
||||
<img src="../resources/zulip.png" id="logo"/>
|
||||
</div>
|
||||
</header>
|
||||
<hr>
|
||||
<section class="content">
|
||||
<section class="server">
|
||||
<div class="container">
|
||||
<h1>Login to Zulip Server</h1>
|
||||
<form id="frm-signInForm" class="form-large" onsubmit="addDomain(); return false">
|
||||
<fieldset>
|
||||
<div class="control-group control-required">
|
||||
<div class="control-field">
|
||||
<input type="text" id="url" autofocus="autofocus" spellcheck="false" placeholder="chat.zulip.org">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="control-submit">
|
||||
<button type="submit" id="main" class="btn-primary btn-large" value="Submit" onclick="addDomain();">Connect</button>
|
||||
</div>
|
||||
</div>
|
||||
<p id="server-status"><p>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
11
app/renderer/js/components/base.js
Normal file
11
app/renderer/js/components/base.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
class BaseComponent {
|
||||
generateNodeFromTemplate(template) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = template;
|
||||
return wrapper.firstElementChild;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseComponent;
|
||||
43
app/renderer/js/components/functional-tab.js
Normal file
43
app/renderer/js/components/functional-tab.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const Tab = require(__dirname + '/../components/tab.js');
|
||||
|
||||
class FunctionalTab extends Tab {
|
||||
template() {
|
||||
return `<div class="tab">
|
||||
<div class="server-tab-badge close-button">
|
||||
<i class="material-icons">close</i>
|
||||
</div>
|
||||
<div class="server-tab functional-tab">
|
||||
<i class="material-icons">${this.props.materialIcon}</i>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.$el = this.generateNodeFromTemplate(this.template());
|
||||
this.props.$root.appendChild(this.$el);
|
||||
|
||||
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
super.registerListeners();
|
||||
|
||||
this.$el.addEventListener('mouseover', () => {
|
||||
this.$closeButton.classList.add('active');
|
||||
});
|
||||
|
||||
this.$el.addEventListener('mouseout', () => {
|
||||
this.$closeButton.classList.remove('active');
|
||||
});
|
||||
|
||||
this.$closeButton.addEventListener('click', e => {
|
||||
this.props.onDestroy();
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FunctionalTab;
|
||||
31
app/renderer/js/components/server-tab.js
Normal file
31
app/renderer/js/components/server-tab.js
Normal file
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const Tab = require(__dirname + '/../components/tab.js');
|
||||
|
||||
class ServerTab extends Tab {
|
||||
template() {
|
||||
return `<div class="tab">
|
||||
<div class="server-tab-badge"></div>
|
||||
<div class="server-tab" style="background-image: url(${this.props.icon});"></div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
|
||||
this.$badge = this.$el.getElementsByClassName('server-tab-badge')[0];
|
||||
}
|
||||
|
||||
updateBadge(count) {
|
||||
if (count > 0) {
|
||||
const formattedCount = count > 999 ? '1K+' : count;
|
||||
|
||||
this.$badge.innerHTML = formattedCount;
|
||||
this.$badge.classList.add('active');
|
||||
} else {
|
||||
this.$badge.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ServerTab;
|
||||
49
app/renderer/js/components/tab.js
Normal file
49
app/renderer/js/components/tab.js
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||
|
||||
class Tab extends BaseComponent {
|
||||
constructor(props) {
|
||||
super();
|
||||
|
||||
this.props = props;
|
||||
this.webview = this.props.webview;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.$el = this.generateNodeFromTemplate(this.template());
|
||||
this.props.$root.appendChild(this.$el);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
this.$el.addEventListener('click', this.props.onClick);
|
||||
}
|
||||
|
||||
isLoading() {
|
||||
return this.webview.isLoading;
|
||||
}
|
||||
|
||||
activate() {
|
||||
this.$el.classList.add('active');
|
||||
this.webview.load();
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.$el.classList.remove('active');
|
||||
this.webview.hide();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.$el.parentNode.removeChild(this.$el);
|
||||
this.webview.$el.parentNode.removeChild(this.webview.$el);
|
||||
}
|
||||
}
|
||||
|
||||
Tab.SERVER_TAB = 0;
|
||||
Tab.SETTINGS_TAB = 1;
|
||||
|
||||
module.exports = Tab;
|
||||
178
app/renderer/js/components/webview.js
Normal file
178
app/renderer/js/components/webview.js
Normal file
@@ -0,0 +1,178 @@
|
||||
'use strict';
|
||||
|
||||
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
|
||||
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||
const LinkUtil = require(__dirname + '/../utils/link-util.js');
|
||||
const {app, dialog, shell} = require('electron').remote;
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||
|
||||
class WebView extends BaseComponent {
|
||||
constructor(props) {
|
||||
super();
|
||||
|
||||
this.props = props;
|
||||
|
||||
this.zoomFactor = 1.0;
|
||||
this.loading = false;
|
||||
this.badgeCount = 0;
|
||||
}
|
||||
|
||||
template() {
|
||||
return `<webview
|
||||
class="disabled"
|
||||
src="${this.props.url}"
|
||||
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
|
||||
disablewebsecurity
|
||||
preload="js/preload.js"
|
||||
webpreferences="allowRunningInsecureContent, javascript=yes">
|
||||
</webview>`;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.$el = this.generateNodeFromTemplate(this.template());
|
||||
this.props.$root.appendChild(this.$el);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
this.$el.addEventListener('new-window', event => {
|
||||
const {url} = event;
|
||||
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
|
||||
|
||||
if (LinkUtil.isInternal(domainPrefix, url)) {
|
||||
event.preventDefault();
|
||||
this.$el.loadURL(url);
|
||||
} else {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.addEventListener('page-title-updated', event => {
|
||||
const {title} = event;
|
||||
this.badgeCount = this.getBadgeCount(title);
|
||||
this.props.onTitleChange();
|
||||
});
|
||||
|
||||
this.$el.addEventListener('dom-ready', this.show.bind(this));
|
||||
|
||||
this.$el.addEventListener('did-fail-load', event => {
|
||||
const {errorDescription} = event;
|
||||
const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0);
|
||||
if (hasConnectivityErr) {
|
||||
console.error('error', errorDescription);
|
||||
this.props.onNetworkError();
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.addEventListener('did-start-loading', () => {
|
||||
let userAgent = SystemUtil.getUserAgent();
|
||||
if (!userAgent) {
|
||||
SystemUtil.setUserAgent(this.$el.getUserAgent());
|
||||
userAgent = SystemUtil.getUserAgent();
|
||||
}
|
||||
this.$el.setUserAgent(userAgent);
|
||||
});
|
||||
}
|
||||
|
||||
getBadgeCount(title) {
|
||||
const messageCountInTitle = (/\(([0-9]+)\)/).exec(title);
|
||||
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
|
||||
}
|
||||
|
||||
show() {
|
||||
// Do not show WebView if another tab was selected and this tab should be in background.
|
||||
if (!this.props.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$el.classList.remove('disabled');
|
||||
this.focus();
|
||||
this.loading = false;
|
||||
this.props.onTitleChange(this.$el.getTitle());
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.$el.focus();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.$el.classList.add('disabled');
|
||||
}
|
||||
|
||||
load() {
|
||||
if (this.$el) {
|
||||
this.show();
|
||||
} else {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
checkConnectivity() {
|
||||
return dialog.showMessageBox({
|
||||
title: 'Internet connection problem',
|
||||
message: 'No internet available! Try again?',
|
||||
type: 'warning',
|
||||
buttons: ['Try again', 'Close'],
|
||||
defaultId: 0
|
||||
}, index => {
|
||||
if (index === 0) {
|
||||
this.reload();
|
||||
ipcRenderer.send('reload');
|
||||
ipcRenderer.send('destroytray');
|
||||
}
|
||||
if (index === 1) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
zoomIn() {
|
||||
this.zoomFactor += 0.1;
|
||||
this.$el.setZoomFactor(this.zoomFactor);
|
||||
}
|
||||
|
||||
zoomOut() {
|
||||
this.zoomFactor -= 0.1;
|
||||
this.$el.setZoomFactor(this.zoomFactor);
|
||||
}
|
||||
|
||||
zoomActualSize() {
|
||||
this.zoomFactor = 1.0;
|
||||
this.$el.setZoomFactor(this.zoomFactor);
|
||||
}
|
||||
|
||||
logOut() {
|
||||
this.$el.executeJavaScript('logout()');
|
||||
}
|
||||
|
||||
showShortcut() {
|
||||
this.$el.executeJavaScript('shortcut()');
|
||||
}
|
||||
|
||||
openDevTools() {
|
||||
this.$el.openDevTools();
|
||||
}
|
||||
|
||||
back() {
|
||||
if (this.$el.canGoBack()) {
|
||||
this.$el.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
forward() {
|
||||
if (this.$el.canGoForward()) {
|
||||
this.$el.goForward();
|
||||
}
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.hide();
|
||||
this.$el.reload();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebView;
|
||||
204
app/renderer/js/main.js
Normal file
204
app/renderer/js/main.js
Normal file
@@ -0,0 +1,204 @@
|
||||
'use strict';
|
||||
|
||||
require(__dirname + '/js/tray.js');
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
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');
|
||||
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
|
||||
|
||||
class ServerManagerView {
|
||||
constructor() {
|
||||
this.$tabsContainer = document.getElementById('tabs-container');
|
||||
|
||||
const $actionsContainer = document.getElementById('actions-container');
|
||||
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
|
||||
this.$addServerButton = $actionsContainer.querySelector('#add-action');
|
||||
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
|
||||
this.$content = document.getElementById('content');
|
||||
|
||||
this.activeTabIndex = -1;
|
||||
this.tabs = [];
|
||||
this.functionalTabs = {};
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initTabs();
|
||||
this.initActions();
|
||||
this.registerIpcs();
|
||||
}
|
||||
|
||||
initTabs() {
|
||||
const servers = DomainUtil.getDomains();
|
||||
if (servers.length > 0) {
|
||||
for (let i = 0; i < servers.length; i++) {
|
||||
this.initServer(servers[i], i);
|
||||
}
|
||||
this.activateTab(0);
|
||||
} else {
|
||||
this.openSettings();
|
||||
}
|
||||
}
|
||||
|
||||
initServer(server, index) {
|
||||
this.tabs.push(new ServerTab({
|
||||
icon: server.icon,
|
||||
$root: this.$tabsContainer,
|
||||
onClick: this.activateTab.bind(this, index),
|
||||
webview: new WebView({
|
||||
$root: this.$content,
|
||||
index,
|
||||
url: server.url,
|
||||
name: server.alias,
|
||||
isActive: () => {
|
||||
return index === this.activeTabIndex;
|
||||
},
|
||||
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
||||
onTitleChange: this.updateBadge.bind(this),
|
||||
nodeIntegration: false
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
initActions() {
|
||||
this.$reloadButton.addEventListener('click', () => {
|
||||
this.tabs[this.activeTabIndex].webview.reload();
|
||||
});
|
||||
this.$addServerButton.addEventListener('click', this.openSettings.bind(this));
|
||||
this.$settingsButton.addEventListener('click', this.openSettings.bind(this));
|
||||
}
|
||||
|
||||
openFunctionalTab(tabProps) {
|
||||
if (this.functionalTabs[tabProps.name]) {
|
||||
this.activateTab(this.functionalTabs[tabProps.name]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.functionalTabs[tabProps.name] = this.tabs.length;
|
||||
|
||||
this.tabs.push(new FunctionalTab({
|
||||
materialIcon: tabProps.materialIcon,
|
||||
$root: this.$tabsContainer,
|
||||
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.$content,
|
||||
index: this.functionalTabs[tabProps.name],
|
||||
url: tabProps.url,
|
||||
name: tabProps.name,
|
||||
isActive: () => {
|
||||
return this.functionalTabs[tabProps.name] === this.activeTabIndex;
|
||||
},
|
||||
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
||||
onTitleChange: this.updateBadge.bind(this),
|
||||
nodeIntegration: true
|
||||
})
|
||||
}));
|
||||
|
||||
this.activateTab(this.functionalTabs[tabProps.name]);
|
||||
}
|
||||
|
||||
openSettings() {
|
||||
this.openFunctionalTab({
|
||||
name: 'Settings',
|
||||
materialIcon: 'settings',
|
||||
url: `file://${__dirname}/preference.html`
|
||||
});
|
||||
}
|
||||
|
||||
openAbout() {
|
||||
this.openFunctionalTab({
|
||||
name: 'About',
|
||||
materialIcon: 'sentiment_very_satisfied',
|
||||
url: `file://${__dirname}/about.html`
|
||||
});
|
||||
}
|
||||
|
||||
openNetworkTroubleshooting() {
|
||||
this.openFunctionalTab({
|
||||
name: 'Network Troubleshooting',
|
||||
materialIcon: 'network_check',
|
||||
url: `file://${__dirname}/network.html`
|
||||
});
|
||||
}
|
||||
|
||||
activateTab(index, hideOldTab = true) {
|
||||
if (this.tabs[index].loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeTabIndex !== -1) {
|
||||
if (this.activeTabIndex === index) {
|
||||
return;
|
||||
} else if (hideOldTab) {
|
||||
this.tabs[this.activeTabIndex].deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
this.activeTabIndex = index;
|
||||
this.tabs[index].activate();
|
||||
}
|
||||
|
||||
destroyTab(name, index) {
|
||||
if (this.tabs[index].loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tabs[index].destroy();
|
||||
|
||||
delete this.tabs[index];
|
||||
delete this.functionalTabs[name];
|
||||
|
||||
this.activateTab(0, false);
|
||||
}
|
||||
|
||||
updateBadge() {
|
||||
let messageCountAll = 0;
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i] && this.tabs[i].updateBadge) {
|
||||
const count = this.tabs[i].webview.badgeCount;
|
||||
messageCountAll += count;
|
||||
this.tabs[i].updateBadge(count);
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.send('update-badge', messageCountAll);
|
||||
}
|
||||
|
||||
registerIpcs() {
|
||||
const webviewListeners = {
|
||||
'webview-reload': 'reload',
|
||||
back: 'back',
|
||||
focus: 'focus',
|
||||
forward: 'forward',
|
||||
zoomIn: 'zoomIn',
|
||||
zoomOut: 'zoomOut',
|
||||
zoomActualSize: 'zoomActualSize',
|
||||
'log-out': 'logOut',
|
||||
shortcut: 'showShortcut',
|
||||
'tab-devtools': 'openDevTools'
|
||||
};
|
||||
|
||||
for (const key in webviewListeners) {
|
||||
ipcRenderer.on(key, () => {
|
||||
const activeWebview = this.tabs[this.activeTabIndex].webview;
|
||||
if (activeWebview) {
|
||||
activeWebview[webviewListeners[key]]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.on('open-settings', this.openSettings.bind(this));
|
||||
ipcRenderer.on('open-about', this.openAbout.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
const serverManagerView = new ServerManagerView();
|
||||
serverManagerView.init();
|
||||
};
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
ipcRenderer.send('reload-main');
|
||||
});
|
||||
20
app/renderer/js/pages/network.js
Normal file
20
app/renderer/js/pages/network.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
class NetworkTroubleshootingView {
|
||||
constructor() {
|
||||
this.$reconnectButton = document.getElementById('reconnect');
|
||||
}
|
||||
|
||||
init() {
|
||||
this.$reconnectButton.addEventListener('click', () => {
|
||||
ipcRenderer.send('reload-main');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
const networkTroubleshootingView = new NetworkTroubleshootingView();
|
||||
networkTroubleshootingView.init();
|
||||
};
|
||||
144
app/renderer/js/pages/preference.js
Normal file
144
app/renderer/js/pages/preference.js
Normal file
@@ -0,0 +1,144 @@
|
||||
'use strict';
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
|
||||
|
||||
class PreferenceView {
|
||||
constructor() {
|
||||
this.$newServerButton = document.getElementById('new-server-action');
|
||||
this.$saveServerButton = document.getElementById('save-server-action');
|
||||
this.$reloadServerButton = document.getElementById('reload-server-action');
|
||||
this.$serverInfoContainer = document.querySelector('.server-info-container');
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initServers();
|
||||
this.initActions();
|
||||
}
|
||||
|
||||
initServers() {
|
||||
const servers = DomainUtil.getDomains();
|
||||
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
|
||||
|
||||
this.initNewServerForm();
|
||||
|
||||
for (const i in servers) {
|
||||
this.initServer(servers[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
initServer(server, index) {
|
||||
const {
|
||||
alias,
|
||||
url,
|
||||
icon
|
||||
} = server;
|
||||
const serverInfoTemplate = `
|
||||
<div class="server-info">
|
||||
<div class="server-info-left">
|
||||
<img class="server-info-icon" src="${icon}"/>
|
||||
</div>
|
||||
<div class="server-info-right">
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Name</span>
|
||||
<input class="server-info-value" disabled value="${alias}"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Url</span>
|
||||
<input class="server-info-value" disabled value="${url}"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Icon</span>
|
||||
<input class="server-info-value" disabled value="${icon}"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Actions</span>
|
||||
<div class="action server-info-value" id="delete-server-action-${index}">
|
||||
<i class="material-icons">indeterminate_check_box</i>
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
this.$serverInfoContainer.appendChild(this.insertNode(serverInfoTemplate));
|
||||
document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => {
|
||||
DomainUtil.removeDomain(index);
|
||||
this.initServers();
|
||||
// alert('Success. Reload to apply changes.');
|
||||
ipcRenderer.send('reload-main');
|
||||
this.$reloadServerButton.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
initNewServerForm() {
|
||||
const newServerFormTemplate = `
|
||||
<div class="server-info active hidden">
|
||||
<div class="server-info-left">
|
||||
<img class="server-info-icon" src="https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png"/>
|
||||
</div>
|
||||
<div class="server-info-right">
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Name</span>
|
||||
<input id="server-info-name" class="server-info-value" placeholder="(Required)"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Url</span>
|
||||
<input id="server-info-url" spellcheck="false" class="server-info-value" placeholder="(Required)"/>
|
||||
</div>
|
||||
<div class="server-info-row">
|
||||
<span class="server-info-key">Icon</span>
|
||||
<input id="server-info-icon" class="server-info-value" placeholder="(Optional)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
this.$serverInfoContainer.appendChild(this.insertNode(newServerFormTemplate));
|
||||
|
||||
this.$newServerForm = document.querySelector('.server-info.active');
|
||||
this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0];
|
||||
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1];
|
||||
this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2];
|
||||
}
|
||||
|
||||
initActions() {
|
||||
this.$newServerButton.addEventListener('click', () => {
|
||||
this.$newServerForm.classList.remove('hidden');
|
||||
this.$saveServerButton.classList.remove('hidden');
|
||||
this.$newServerButton.classList.add('hidden');
|
||||
});
|
||||
this.$saveServerButton.addEventListener('click', () => {
|
||||
DomainUtil.checkDomain(this.$newServerUrl.value).then(domain => {
|
||||
const server = {
|
||||
alias: this.$newServerAlias.value,
|
||||
url: domain,
|
||||
icon: this.$newServerIcon.value
|
||||
};
|
||||
DomainUtil.addDomain(server);
|
||||
this.$saveServerButton.classList.add('hidden');
|
||||
this.$newServerButton.classList.remove('hidden');
|
||||
this.$newServerForm.classList.add('hidden');
|
||||
|
||||
this.initServers();
|
||||
// alert('Success. Reload to apply changes.');
|
||||
ipcRenderer.send('reload-main');
|
||||
this.$reloadServerButton.classList.remove('hidden');
|
||||
}, errorMessage => {
|
||||
alert(errorMessage);
|
||||
});
|
||||
});
|
||||
this.$reloadServerButton.addEventListener('click', () => {
|
||||
ipcRenderer.send('reload-main');
|
||||
});
|
||||
}
|
||||
insertNode(html) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html;
|
||||
return wrapper.firstElementChild;
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
const preferenceView = new PreferenceView();
|
||||
preferenceView.init();
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
'use strict';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const {remote} = require('electron');
|
||||
|
||||
const prefWindow = remote.getCurrentWindow();
|
||||
|
||||
document.getElementById('close-button').addEventListener('click', () => {
|
||||
prefWindow.close();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape' || event.keyCode === 27) {
|
||||
prefWindow.close();
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function addDomain() {
|
||||
const request = require('request');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const JsonDB = require('node-json-db');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const {app} = require('electron').remote;
|
||||
|
||||
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
||||
document.getElementById('main').innerHTML = 'checking...';
|
||||
document.getElementById('pic').style.display = 'block';
|
||||
|
||||
let newDomain = document.getElementById('url').value;
|
||||
newDomain = newDomain.replace(/^https?:\/\//, '');
|
||||
|
||||
const domain = 'https://' + newDomain;
|
||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
||||
|
||||
request(checkDomain, (error, response) => {
|
||||
if (!error && response.statusCode !== 404) {
|
||||
document.getElementById('pic').style.display = 'none';
|
||||
document.getElementById('main').innerHTML = 'Switch';
|
||||
document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
|
||||
db.push('/domain', domain);
|
||||
ipcRenderer.send('new-domain', domain);
|
||||
} else {
|
||||
document.getElementById('pic').style.display = 'none';
|
||||
document.getElementById('main').innerHTML = 'Switch';
|
||||
document.getElementById('urladded').innerHTML = 'Not a vaild Zulip Server.';
|
||||
}
|
||||
});
|
||||
}
|
||||
39
app/renderer/js/preload.js
Normal file
39
app/renderer/js/preload.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
const {ipcRenderer} = require('electron');
|
||||
const {spellChecker} = require('./spellchecker');
|
||||
|
||||
const logout = () => {
|
||||
// Create the menu for the below
|
||||
document.querySelector('.dropdown-toggle').click();
|
||||
|
||||
const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
|
||||
nodes[nodes.length - 1].click();
|
||||
};
|
||||
|
||||
const shortcut = () => {
|
||||
// Create the menu for the below
|
||||
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
|
||||
// Additional check
|
||||
if (node.text.trim().toLowerCase() === 'keyboard shortcuts') {
|
||||
node.click();
|
||||
} else {
|
||||
// Atleast click the dropdown
|
||||
document.querySelector('.dropdown-toggle').click();
|
||||
}
|
||||
};
|
||||
|
||||
process.once('loaded', () => {
|
||||
global.logout = logout;
|
||||
global.shortcut = shortcut;
|
||||
});
|
||||
|
||||
// To prevent failing this script on linux we need to load it after the document loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Init spellchecker
|
||||
spellChecker();
|
||||
|
||||
// redirect users to network troubleshooting page
|
||||
document.querySelector('.restart_get_events_button').addEventListener('click', () => {
|
||||
ipcRenderer.send('reload-main');
|
||||
});
|
||||
});
|
||||
27
app/renderer/js/spellchecker.js
Normal file
27
app/renderer/js/spellchecker.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
|
||||
|
||||
function spellChecker() {
|
||||
// Implement spellcheck using electron api
|
||||
window.spellCheckHandler = new SpellCheckHandler();
|
||||
window.spellCheckHandler.attachToInput();
|
||||
|
||||
// Start off as US English
|
||||
window.spellCheckHandler.switchLanguage('en-US');
|
||||
|
||||
const contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler);
|
||||
const contextMenuListener = new ContextMenuListener(info => {
|
||||
contextMenuBuilder.showPopupMenu(info);
|
||||
});
|
||||
|
||||
// Clean up events after you navigate away from this page;
|
||||
// otherwise, you may experience errors
|
||||
window.addEventListener('beforeunload', () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
spellCheckHandler.unsubscribe();
|
||||
contextMenuListener.unsubscribe();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
spellChecker
|
||||
};
|
||||
206
app/renderer/js/tray.js
Normal file
206
app/renderer/js/tray.js
Normal file
@@ -0,0 +1,206 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
|
||||
const electron = require('electron');
|
||||
|
||||
const {ipcRenderer, remote} = electron;
|
||||
|
||||
const {Tray, Menu, nativeImage, BrowserWindow} = remote;
|
||||
|
||||
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
|
||||
|
||||
const iconPath = () => {
|
||||
if (process.platform === 'linux') {
|
||||
return APP_ICON + 'linux.png';
|
||||
}
|
||||
return APP_ICON + (process.platform === 'win32' ? 'win.ico' : 'osx.png');
|
||||
};
|
||||
|
||||
let unread = 0;
|
||||
|
||||
const trayIconSize = () => {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return 20;
|
||||
case 'win32':
|
||||
return 100;
|
||||
case 'linux':
|
||||
return 100;
|
||||
default: return 80;
|
||||
}
|
||||
};
|
||||
|
||||
// Default config for Icon we might make it OS specific if needed like the size
|
||||
const config = {
|
||||
pixelRatio: window.devicePixelRatio,
|
||||
unreadCount: 0,
|
||||
showUnreadCount: true,
|
||||
unreadColor: '#000000',
|
||||
readColor: '#000000',
|
||||
unreadBackgroundColor: '#B9FEEA',
|
||||
readBackgroundColor: '#B9FEEA',
|
||||
size: trayIconSize(),
|
||||
thick: process.platform === 'win32'
|
||||
};
|
||||
|
||||
const renderCanvas = function (arg) {
|
||||
config.unreadCount = arg;
|
||||
return new Promise((resolve, reject) => {
|
||||
const SIZE = config.size * config.pixelRatio;
|
||||
const PADDING = SIZE * 0.05;
|
||||
const CENTER = SIZE / 2;
|
||||
const HAS_COUNT = config.showUnreadCount && config.unreadCount;
|
||||
const color = config.unreadCount ? config.unreadColor : config.readColor;
|
||||
const backgroundColor = config.unreadCount ? config.unreadBackgroundColor : config.readBackgroundColor;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = SIZE;
|
||||
canvas.height = SIZE;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Circle
|
||||
// If (!config.thick || config.thick && HAS_COUNT) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(CENTER, CENTER, (SIZE / 2) - PADDING, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = SIZE / (config.thick ? 10 : 20);
|
||||
ctx.strokeStyle = backgroundColor;
|
||||
ctx.stroke();
|
||||
// Count or Icon
|
||||
if (HAS_COUNT) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.textAlign = 'center';
|
||||
if (config.unreadCount > 99) {
|
||||
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.4}px Helvetica`;
|
||||
ctx.fillText('99+', CENTER, CENTER + (SIZE * 0.15));
|
||||
} else if (config.unreadCount < 10) {
|
||||
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
|
||||
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.20));
|
||||
} else {
|
||||
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
|
||||
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.15));
|
||||
}
|
||||
|
||||
resolve(canvas);
|
||||
} else {
|
||||
reject(canvas);
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Renders the tray icon as a native image
|
||||
* @param arg: Unread count
|
||||
* @return the native image
|
||||
*/
|
||||
const renderNativeImage = function (arg) {
|
||||
return Promise.resolve()
|
||||
.then(() => renderCanvas(arg))
|
||||
.then(canvas => {
|
||||
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPng();
|
||||
return Promise.resolve(nativeImage.createFromBuffer(pngData, config.pixelRatio));
|
||||
});
|
||||
};
|
||||
|
||||
function sendAction(action) {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
win.restore();
|
||||
}
|
||||
|
||||
win.webContents.send(action);
|
||||
}
|
||||
|
||||
const createTray = function () {
|
||||
window.tray = new Tray(iconPath());
|
||||
const contextMenu = Menu.buildFromTemplate([{
|
||||
label: 'About',
|
||||
click() {
|
||||
// We need to focus the main window first
|
||||
ipcRenderer.send('focus-app');
|
||||
sendAction('open-about');
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Focus',
|
||||
click() {
|
||||
ipcRenderer.send('focus-app');
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Manage Zulip servers',
|
||||
click() {
|
||||
ipcRenderer.send('focus-app');
|
||||
sendAction('open-settings');
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
click() {
|
||||
ipcRenderer.send('quit-app');
|
||||
}
|
||||
}
|
||||
]);
|
||||
window.tray.setContextMenu(contextMenu);
|
||||
window.tray.on('click', () => {
|
||||
// Click event only works on Windows
|
||||
if (process.platform === 'win32') {
|
||||
ipcRenderer.send('toggle-app');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ipcRenderer.on('destroytray', event => {
|
||||
window.tray.destroy();
|
||||
if (window.tray.isDestroyed()) {
|
||||
window.tray = null;
|
||||
} else {
|
||||
throw new Error('Tray icon not properly destroyed.');
|
||||
}
|
||||
|
||||
return event;
|
||||
});
|
||||
|
||||
ipcRenderer.on('tray', (event, arg) => {
|
||||
if (arg === 0) {
|
||||
unread = arg;
|
||||
// Message Count // console.log("message count is zero.");
|
||||
window.tray.setImage(iconPath());
|
||||
window.tray.setToolTip('No unread messages');
|
||||
} else {
|
||||
unread = arg;
|
||||
renderNativeImage(arg).then(image => {
|
||||
window.tray.setImage(image);
|
||||
window.tray.setToolTip(arg + ' unread messages');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('toggletray', event => {
|
||||
if (event) {
|
||||
if (window.tray) {
|
||||
window.tray.destroy();
|
||||
if (window.tray.isDestroyed()) {
|
||||
window.tray = null;
|
||||
}
|
||||
} else {
|
||||
createTray();
|
||||
renderNativeImage(unread).then(image => {
|
||||
window.tray.setImage(image);
|
||||
window.tray.setToolTip(unread + ' unread messages');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
createTray();
|
||||
82
app/renderer/js/utils/domain-util.js
Normal file
82
app/renderer/js/utils/domain-util.js
Normal file
@@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
|
||||
const {app} = require('electron').remote;
|
||||
const JsonDB = require('node-json-db');
|
||||
const request = require('request');
|
||||
|
||||
let instance = null;
|
||||
|
||||
const defaultIconUrl = __dirname + '../../../img/icon.png';
|
||||
class DomainUtil {
|
||||
constructor() {
|
||||
if (instance) {
|
||||
return instance;
|
||||
} else {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
|
||||
// Migrate from old schema
|
||||
if (this.db.getData('/').domain) {
|
||||
this.addDomain({
|
||||
alias: 'Zulip',
|
||||
url: this.db.getData('/domain')
|
||||
});
|
||||
this.db.delete('/domain');
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
getDomains() {
|
||||
if (this.db.getData('/').domains === undefined) {
|
||||
return [];
|
||||
} else {
|
||||
return this.db.getData('/domains');
|
||||
}
|
||||
}
|
||||
|
||||
getDomain(index) {
|
||||
return this.db.getData(`/domains[${index}]`);
|
||||
}
|
||||
|
||||
addDomain(server) {
|
||||
server.icon = server.icon || defaultIconUrl;
|
||||
this.db.push('/domains[]', server, true);
|
||||
}
|
||||
|
||||
removeDomains() {
|
||||
this.db.delete('/domains');
|
||||
}
|
||||
|
||||
removeDomain(index) {
|
||||
this.db.delete(`/domains[${index}]`);
|
||||
}
|
||||
|
||||
checkDomain(domain) {
|
||||
const hasPrefix = (domain.indexOf('http') === 0);
|
||||
if (!hasPrefix) {
|
||||
domain = (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
|
||||
}
|
||||
|
||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(checkDomain, (error, response) => {
|
||||
if (!error && response.statusCode !== 404) {
|
||||
resolve(domain);
|
||||
} else if (error.toString().indexOf('Error: self signed certificate') >= 0) {
|
||||
if (window.confirm(`Do you trust certificate from ${domain}?`)) {
|
||||
resolve(domain);
|
||||
} else {
|
||||
reject('Untrusted Certificate.');
|
||||
}
|
||||
} else {
|
||||
reject('Not a valid Zulip server');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DomainUtil();
|
||||
29
app/renderer/js/utils/link-util.js
Normal file
29
app/renderer/js/utils/link-util.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const wurl = require('wurl');
|
||||
|
||||
let instance = null;
|
||||
|
||||
class LinkUtil {
|
||||
constructor() {
|
||||
if (instance) {
|
||||
return instance;
|
||||
} else {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
isInternal(currentUrl, newUrl) {
|
||||
const currentDomain = wurl('hostname', currentUrl);
|
||||
const newDomain = wurl('hostname', newUrl);
|
||||
|
||||
const skipImages = '.jpg|.gif|.png|.jpeg|.JPG|.PNG';
|
||||
|
||||
// We'll be needing this to open images in default browser
|
||||
return (currentDomain === newDomain) && !newUrl.match(skipImages);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new LinkUtil();
|
||||
55
app/renderer/js/utils/system-util.js
Normal file
55
app/renderer/js/utils/system-util.js
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const {app} = require('electron').remote;
|
||||
|
||||
const os = require('os');
|
||||
|
||||
let instance = null;
|
||||
|
||||
class SystemUtil {
|
||||
constructor() {
|
||||
if (instance) {
|
||||
return instance;
|
||||
} else {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
this.connectivityERR = [
|
||||
'ERR_INTERNET_DISCONNECTED',
|
||||
'ERR_PROXY_CONNECTION_FAILED',
|
||||
'ERR_CONNECTION_RESET',
|
||||
'ERR_NOT_CONNECTED',
|
||||
'ERR_NAME_NOT_RESOLVED',
|
||||
'ERR_NETWORK_CHANGED'
|
||||
];
|
||||
this.userAgent = null;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
getOS() {
|
||||
if (os.platform() === 'darwin') {
|
||||
return 'Mac';
|
||||
}
|
||||
if (os.platform() === 'linux') {
|
||||
return 'Linux';
|
||||
}
|
||||
if (os.platform() === 'win32' || os.platform() === 'win64') {
|
||||
if (parseFloat(os.release()) < 6.2) {
|
||||
return 'Windows 7';
|
||||
} else {
|
||||
return 'Windows 10';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setUserAgent(webViewUserAgent) {
|
||||
this.userAgent = 'ZulipElectron/' + app.getVersion() + ' ' + webViewUserAgent;
|
||||
}
|
||||
|
||||
getUserAgent() {
|
||||
return this.userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new SystemUtil();
|
||||
28
app/renderer/main.html
Normal file
28
app/renderer/main.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="responsive desktop">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Zulip</title>
|
||||
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div id="sidebar">
|
||||
<div id="tabs-container"></div>
|
||||
<div id="actions-container">
|
||||
<div class="action-button" id="reload-action">
|
||||
<i class="material-icons md-48">refresh</i>
|
||||
</div>
|
||||
<div class="action-button" id="add-action">
|
||||
<i class="material-icons md-48">add_circle</i>
|
||||
</div>
|
||||
<div class="action-button" id="settings-action">
|
||||
<i class="material-icons md-48">settings</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="js/main.js"></script>
|
||||
</html>
|
||||
21
app/renderer/network.html
Normal file
21
app/renderer/network.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="responsive desktop">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Zulip - Network Troubleshooting</title>
|
||||
<link rel="stylesheet" href="css/network.css" type="text/css" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div id="picture"><img src="img/zulip_network.png"></div>
|
||||
<div id="title">Zulip can't connect</div>
|
||||
<div id="description">
|
||||
<div>Your computer seems to be offline.</div>
|
||||
<div>We will keep trying to reconnect, or you can try now.</div>
|
||||
</div>
|
||||
<div id="reconnect">Try now</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="js/pages/network.js"></script>
|
||||
</html>
|
||||
@@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="css/pref.css">
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="close" id="close-button">Close</div>
|
||||
<div class="form">
|
||||
<form onsubmit="addDomain(); return false">
|
||||
<input id="url" type="text" placeholder="chat.zulip.org">
|
||||
<button type="submit" id="main" value="Submit" onclick="addDomain();">
|
||||
Switch</button>
|
||||
<img id="pic" src="img/loader.gif" />
|
||||
</form>
|
||||
<p id="urladded"><p>
|
||||
</div>
|
||||
<script src="js/pref.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
44
app/renderer/preference.html
Normal file
44
app/renderer/preference.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="responsive desktop">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Zulip - Settings</title>
|
||||
<link rel="stylesheet" href="css/preference.css" type="text/css" media="screen">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div id="sidebar">
|
||||
<div id="settings-header">Settings</div>
|
||||
<div id="tabs-container">
|
||||
<div class="tab" id="general-settings">General</div>
|
||||
<div class="tab active" id="server-settings">Servers</div>
|
||||
<div class="tab" id="about-settings">About</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-container">
|
||||
<div class="settings-pane" id="server-settings-pane">
|
||||
<div class="title">Manage Servers</div>
|
||||
<div class="actions-container">
|
||||
<div class="action" id="new-server-action">
|
||||
<i class="material-icons">add_box</i>
|
||||
<span>New Server</span>
|
||||
</div>
|
||||
<div class="action hidden" id="save-server-action">
|
||||
<button class="save-server-button">Save</button>
|
||||
</div>
|
||||
<div class="action hidden" id="reload-server-action">
|
||||
<i class="material-icons">refresh</i>
|
||||
<span>Reload to Apply Changes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="server-info-container">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="js/pages/preference.js"></script>
|
||||
</html>
|
||||
16
build/entitlements.mas.plist
Normal file
16
build/entitlements.mas.plist
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
56
development.md
Normal file
56
development.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Development guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||
* [Node.js](https://nodejs.org) >= v6.9.0
|
||||
* [python](https://www.python.org/downloads/release/python-2713/) (v2.7.x recommended)
|
||||
* [node-gyp](https://github.com/nodejs/node-gyp#installation)
|
||||
|
||||
|
||||
## System specific dependencies
|
||||
|
||||
### Linux
|
||||
|
||||
Install following packages:
|
||||
```sh
|
||||
$ sudo apt-get install build-essential libxext-dev libxtst-dev libxkbfile-dev
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Clone the source locally:
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/zulip/zulip-electron
|
||||
$ cd zulip-electron
|
||||
```
|
||||
|
||||
Install project dependencies:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
```
|
||||
Start the app:
|
||||
|
||||
```sh
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Start and watch changes
|
||||
|
||||
```sh
|
||||
$ npm run dev
|
||||
```
|
||||
### Making a release
|
||||
|
||||
To package app into an installer use command:
|
||||
```
|
||||
npm run dist
|
||||
```
|
||||
It will start the packaging process for the operating system you are running this command on. The ready for distribution file (e.g. dmg, windows installer, deb package) will be outputted to the `dist` directory.
|
||||
|
||||
You can create a Windows installer only when running on Windows and similarly for Linux and OSX. So, to generate all three installers, you will need all three operating systems.
|
||||
|
||||
# Troubleshooting
|
||||
If you have any problems running the app please see the [most common issues](./troubleshooting.md).
|
||||
89
package.json
89
package.json
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "0.5.3",
|
||||
"version": "1.1.0-beta",
|
||||
"main": "./app/main",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
"email":"<svnitakash@gmail.com>",
|
||||
"copyright": "©2016 Kandra Labs, Inc.",
|
||||
"email": "<svnitakash@gmail.com>",
|
||||
"copyright": "©2017 Kandra Labs, Inc.",
|
||||
"author": {
|
||||
"name": "Akash Nimare",
|
||||
"email": "svnitakash@gmail.com"
|
||||
@@ -18,36 +19,41 @@
|
||||
"url": "https://github.com/zulip/zulip-electron/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron ./app/main",
|
||||
"start": "electron app --disable-http-cache",
|
||||
"postinstall": "install-app-deps",
|
||||
"test": "gulp test && xo",
|
||||
"test": "xo",
|
||||
"dev": "gulp dev",
|
||||
"pack": "build --dir",
|
||||
"dist": "build",
|
||||
"mas": "build --mac mas",
|
||||
"build:win": "build --win nsis-web --ia32 --x64",
|
||||
"travis": "cd ./scripts && ./travis-build-test.sh"
|
||||
},
|
||||
"build": {
|
||||
"appId": "org.zulip.zulip-electron",
|
||||
"asar": "true",
|
||||
"asar": true,
|
||||
"files": [
|
||||
"**/*",
|
||||
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
|
||||
],
|
||||
"copyright": "©2016 Kandra Labs, Inc.",
|
||||
"copyright": "©2017 Kandra Labs, Inc.",
|
||||
"mac": {
|
||||
"category": "public.app-category.productivity"
|
||||
},
|
||||
"linux" : {
|
||||
"synopsis": "Zulip Desktop App",
|
||||
"linux": {
|
||||
"category": "",
|
||||
"packageCategory": "GNOME;GTK;Network;InstantMessaging",
|
||||
"description": "Zulip Desktop Client for Linux",
|
||||
"target" : ["deb", "AppImage"],
|
||||
"version" : "0.5.3",
|
||||
"title" : "Zulip",
|
||||
"license": "Apache-2.0",
|
||||
"target": [
|
||||
"deb",
|
||||
"zip",
|
||||
"AppImage"
|
||||
],
|
||||
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
|
||||
},
|
||||
"deb": {
|
||||
"synopsis": "Zulip Desktop App"
|
||||
},
|
||||
"dmg": {
|
||||
"background": "build/appdmg.png",
|
||||
"icon": "build/icon.icns",
|
||||
@@ -67,43 +73,60 @@
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"target": "squirrel",
|
||||
"target": "nsis",
|
||||
"icon": "build/icon.ico"
|
||||
},
|
||||
"nsis": {
|
||||
"perMachine": true,
|
||||
"oneClick": false
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"Zulip",
|
||||
"Group Chat app",
|
||||
"electron-app",
|
||||
"electron",
|
||||
"Desktop app",
|
||||
"InstantMessaging"
|
||||
"Zulip",
|
||||
"Group Chat app",
|
||||
"electron-app",
|
||||
"electron",
|
||||
"Desktop app",
|
||||
"InstantMessaging"
|
||||
],
|
||||
"devDependencies": {
|
||||
"assert": "^1.4.1",
|
||||
"devtron": "^1.1.0",
|
||||
"electron-builder": "*",
|
||||
"electron": "1.4.7",
|
||||
"electron-connect": "^0.4.6",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-mocha": "^3.0.1",
|
||||
"spectron": "^3.3.0",
|
||||
"xo": "*"
|
||||
"assert": "1.4.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron-builder": "17.10.0",
|
||||
"electron": "1.6.8",
|
||||
"electron-connect": "0.4.8",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-mocha": "3.0.1",
|
||||
"chai-as-promised": "6.0.0",
|
||||
"chai": "^3.5.0",
|
||||
"spectron": "3.6.4",
|
||||
"xo": "0.18.1"
|
||||
},
|
||||
"xo": {
|
||||
"parserOptions": {
|
||||
"sourceType": "script",
|
||||
"ecmaFeatures": {
|
||||
"globalReturn": true
|
||||
}
|
||||
},
|
||||
"esnext": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "app/main/*.js",
|
||||
"files": "app*/**/*.js",
|
||||
"rules": {
|
||||
"max-lines": [
|
||||
"warn",
|
||||
350
|
||||
500
|
||||
],
|
||||
"no-warning-comments": 0,
|
||||
"capitalized-comments": 0,
|
||||
"no-else-return": 0,
|
||||
"no-path-concat": 0,
|
||||
"no-alert": 0,
|
||||
"guard-for-in": 0,
|
||||
"prefer-promise-reject-errors": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-extraneous-dependencies":0
|
||||
"import/no-extraneous-dependencies": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -116,4 +139,4 @@
|
||||
"mocha"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
export DISPLAY=:99.0
|
||||
export {no_proxy,NO_PROXY}="127.0.0.1,localhost"
|
||||
export DISPLAY=:99.0
|
||||
sh -e /etc/init.d/xvfb start
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
const assert = require('assert')
|
||||
const Application = require('spectron').Application
|
||||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
const chaiAsPromised = require('chai-as-promised')
|
||||
|
||||
chai.should()
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
describe('application launch', function () {
|
||||
this.timeout(15000)
|
||||
@@ -7,11 +13,15 @@ describe('application launch', function () {
|
||||
beforeEach(function () {
|
||||
this.app = new Application({
|
||||
path: require('electron'),
|
||||
args: [__dirname + '/../app/main/index.js']
|
||||
args: [__dirname + '/../app/renderer/main.html']
|
||||
})
|
||||
return this.app.start()
|
||||
})
|
||||
|
||||
beforeEach(function () {
|
||||
chaiAsPromised.transferPromiseness = this.app.transferPromiseness
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
if (this.app && this.app.isRunning()) {
|
||||
return this.app.stop()
|
||||
@@ -19,9 +29,53 @@ describe('application launch', function () {
|
||||
})
|
||||
|
||||
it('shows an initial window', function () {
|
||||
return this.app.client.getWindowCount().then(function (count) {
|
||||
assert.equal(count, 1)
|
||||
})
|
||||
return this.app.client.waitUntilWindowLoaded(5000)
|
||||
.getWindowCount().should.eventually.equal(2)
|
||||
.browserWindow.isMinimized().should.eventually.be.false
|
||||
.browserWindow.isDevToolsOpened().should.eventually.be.false
|
||||
.browserWindow.isVisible().should.eventually.be.true
|
||||
.browserWindow.isFocused().should.eventually.be.true
|
||||
.browserWindow.getBounds().should.eventually.have.property('width').and.be.above(0)
|
||||
.browserWindow.getBounds().should.eventually.have.property('height').and.be.above(0)
|
||||
})
|
||||
|
||||
it('sets up a default organization', function () {
|
||||
let app = this.app
|
||||
let self = this
|
||||
app.client.execute(() => {
|
||||
window.confirm = function () { return true }
|
||||
})
|
||||
|
||||
function createOrg (client, name, url, winIndex) {
|
||||
return client
|
||||
// Focus on settings webview
|
||||
.then(switchToWebviewAtIndex.bind(null, self.app.client, winIndex))
|
||||
.pause(1000) // wait for settings to load
|
||||
|
||||
// Fill settings form
|
||||
.click('#new-server-action')
|
||||
.setValue('input[id="server-info-name"]', name)
|
||||
.setValue('input[id="server-info-url"]', url)
|
||||
.click('#save-server-action')
|
||||
.pause(500) // Need to pause while server verification takes place
|
||||
.then(() => app.browserWindow.reload())
|
||||
.pause(1500) // Wait for webview of org to load
|
||||
}
|
||||
|
||||
function switchToWebviewAtIndex(client, index) {
|
||||
return client
|
||||
.windowHandles()
|
||||
.then(function (session) {
|
||||
this.window(session.value[index])
|
||||
})
|
||||
}
|
||||
|
||||
return this.app.client.waitUntilWindowLoaded(5000)
|
||||
.then(() => createOrg(self.app.client, 'Zulip 1', 'chat.zulip.org', 1))
|
||||
.then(switchToWebviewAtIndex.bind(null, self.app.client, 0))
|
||||
.click('#add-action > i').pause(500)
|
||||
.then(switchToWebviewAtIndex.bind(null, self.app.client, 2))
|
||||
.then(() => createOrg(self.app.client, 'Zulip 2', 'chat.zulip.org', 2))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
5
troubleshooting.md
Normal file
5
troubleshooting.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Troubleshooting
|
||||
|
||||
* App icon will only show in the release version. The dev version will use the Electron icon
|
||||
* If you see issue, try deleting `node_modules` and `npm install`
|
||||
* Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I`
|
||||
@@ -74,11 +74,11 @@ gitCheckout()
|
||||
}
|
||||
|
||||
# }}}
|
||||
# {{{ npmUpgradeStart()
|
||||
# {{{ npmInstallStart()
|
||||
|
||||
npmUpgradeStart()
|
||||
npmInstallStart()
|
||||
{
|
||||
npm upgrade
|
||||
npm install
|
||||
npm start &
|
||||
}
|
||||
|
||||
@@ -105,5 +105,5 @@ cleanUp()
|
||||
|
||||
envSetup $*
|
||||
gitCheckout
|
||||
npmUpgradeStart
|
||||
cleanUp
|
||||
npmInstallStart
|
||||
cleanUp
|
||||
Reference in New Issue
Block a user