mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-10-27 10:03:38 +00:00
Compare commits
208 Commits
v0.5.9
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
d4448ba086 | ||
|
|
ca078cbbfd | ||
|
|
6d45105b69 | ||
|
|
40f81af2dd | ||
|
|
d5e6184e75 | ||
|
|
0ec38ba41d | ||
|
|
460a64710a | ||
|
|
9ec62a748f | ||
|
|
a7a80cef99 |
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
|
||||
@@ -25,8 +25,6 @@ cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- app/node_modules
|
||||
- $HOME/.electron
|
||||
- $HOME/.cache
|
||||
|
||||
script:
|
||||
- npm run travis
|
||||
@@ -35,4 +33,4 @@ notifications:
|
||||
urls:
|
||||
- https://zulip.org/zulipbot/travis
|
||||
on_success: always
|
||||
on_failure: always
|
||||
on_failure: always
|
||||
@@ -2,44 +2,46 @@
|
||||
|
||||
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 judgement 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 development environment, setting up with zulip project, testing, code of conduct can be read [here](https://zulip.readthedocs.io).
|
||||
* 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 [here](https://chat.zulip.org/#narrow/stream/electron).
|
||||
* 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 the 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).
|
||||
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.
|
||||
|
||||
81
README.md
81
README.md
@@ -1,82 +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/).eeee
|
||||
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/bDtK47q.png"/>
|
||||
|
||||
## Prerequisites
|
||||
* node >= v6.3.1
|
||||
> Use [nvm](https://github.com/creationix/nvm) to install the current stable version of node
|
||||
# Download
|
||||
You can download the latest version from the [Releases](https://github.com/zulip/zulip-electron/releases/latest) page.
|
||||
|
||||
|
||||
* python (v2.7.x recommended)
|
||||
* If you're on Debian or Ubuntu, you'll need to 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 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/Windows)
|
||||
- [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.
|
||||
|
||||
@@ -7,24 +7,11 @@ function appUpdater() {
|
||||
const log = require('electron-log');
|
||||
log.transports.file.level = 'info';
|
||||
autoUpdater.logger = log;
|
||||
/*
|
||||
autoUpdater.on('error', err => log.info(err));
|
||||
autoUpdater.on('checking-for-update', () => log.info('checking-for-update'));
|
||||
autoUpdater.on('update-available', () => log.info('update-available'));
|
||||
autoUpdater.on('update-not-available', () => log.info('update-not-available'));
|
||||
*/
|
||||
autoUpdater.allowPrerelease = false;
|
||||
|
||||
// Ask the user if update is available
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
autoUpdater.on('update-downloaded', (event, info) => {
|
||||
// let message = app.getName() + ' ' + info.releaseName + ' is now available. It will be installed the next time you restart the application.';
|
||||
// if (info.releaseNotes) {
|
||||
// const splitNotes = info.releaseNotes.split(/[^\r]\n/);
|
||||
// message += '\n\nRelease notes:\n';
|
||||
// splitNotes.forEach(notes => {
|
||||
// message += notes + '\n\n';
|
||||
// });
|
||||
// }
|
||||
// Ask user to update the app
|
||||
dialog.showMessageBox({
|
||||
type: 'question',
|
||||
@@ -38,10 +25,10 @@ function appUpdater() {
|
||||
}
|
||||
});
|
||||
});
|
||||
// init for updates
|
||||
// Init for updates
|
||||
autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
exports = module.exports = {
|
||||
module.exports = {
|
||||
appUpdater
|
||||
};
|
||||
|
||||
@@ -1,48 +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);
|
||||
window.addDomain = function () {
|
||||
let newDomain = document.getElementById('url').value;
|
||||
newDomain = newDomain.replace(/^https?:\/\//, '');
|
||||
newDomain = newDomain.replace(/^http?:\/\//, '');
|
||||
if (newDomain === '') {
|
||||
document.getElementById('server-status').innerHTML = 'Please input a value';
|
||||
} else {
|
||||
document.getElementById('main').innerHTML = 'Checking...';
|
||||
if (newDomain.indexOf('localhost:') >= 0) {
|
||||
const domain = 'http://' + 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 valid Zulip Local Server.';
|
||||
}
|
||||
});
|
||||
// });
|
||||
} else {
|
||||
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 valid Zulip Server.';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,114 +1,28 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const electron = require('electron');
|
||||
const {app} = require('electron');
|
||||
const ipc = require('electron').ipcMain;
|
||||
const {dialog} = require('electron');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const electronLocalshortcut = require('electron-localshortcut');
|
||||
const Configstore = require('electron-config');
|
||||
const JsonDB = require('node-json-db');
|
||||
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('/');
|
||||
|
||||
// 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();
|
||||
|
||||
function userOS() {
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
// Setting userAgent so that server-side code can identify the desktop app
|
||||
|
||||
// setting userAgent so that server-side code can identify the desktop app
|
||||
const isUserAgent = 'ZulipElectron/' + app.getVersion() + ' ' + userOS();
|
||||
|
||||
// prevent window being garbage collected
|
||||
// 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;
|
||||
};
|
||||
|
||||
function serverError(targetURL) {
|
||||
if (targetURL.indexOf('localhost:') < 0 && data.domain) {
|
||||
const req = https.request(targetURL + '/static/audio/zulip.ogg', res => {
|
||||
console.log('Server StatusCode:', res.statusCode);
|
||||
console.log('You are connected to:', res.req._headers.host);
|
||||
if (res.statusCode >= 500 && res.statusCode <= 599) {
|
||||
return dialog.showErrorBox('SERVER IS DOWN!', 'We are getting a ' + res.statusCode + ' error status from the server ' + res.req._headers.host + '. Please try again after some time or you may switch server.');
|
||||
}
|
||||
});
|
||||
req.on('error', e => {
|
||||
console.error(e);
|
||||
});
|
||||
req.end();
|
||||
} else if (data.domain) {
|
||||
const req = http.request(targetURL + '/static/audio/zulip.ogg', res => {
|
||||
console.log('Server StatusCode:', res.statusCode);
|
||||
console.log('You are connected to:', res.req._headers.host);
|
||||
});
|
||||
req.on('error', e => {
|
||||
console.error(e);
|
||||
});
|
||||
req.end();
|
||||
}
|
||||
}
|
||||
|
||||
function 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) {
|
||||
mainWindow.webContents.reload();
|
||||
}
|
||||
if (index === 1) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkConnection() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
|
||||
if (errorDescription === 'ERR_INTERNET_DISCONNECTED' || errorDescription === 'ERR_PROXY_CONNECTION_FAILED') {
|
||||
console.log('Error Description:' + errorDescription);
|
||||
checkConnectivity();
|
||||
}
|
||||
});
|
||||
}
|
||||
const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
|
||||
|
||||
const isAlreadyRunning = app.makeSingleInstance(() => {
|
||||
if (mainWindow) {
|
||||
@@ -124,13 +38,6 @@ if (isAlreadyRunning) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
function checkWindowURL() {
|
||||
if (data.domain !== undefined) {
|
||||
return data.domain;
|
||||
}
|
||||
return targetLink;
|
||||
}
|
||||
|
||||
function isWindowsOrmacOS() {
|
||||
return process.platform === 'darwin' || process.platform === 'win32';
|
||||
}
|
||||
@@ -141,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
|
||||
@@ -168,27 +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();
|
||||
});
|
||||
|
||||
serverError(targetURL());
|
||||
win.loadURL(mainURL);
|
||||
|
||||
win.loadURL(targetURL(), {
|
||||
userAgent: isUserAgent + ' ' + win.webContents.getUserAgent()
|
||||
// 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.on('closed', onClosed);
|
||||
win.setTitle('Zulip');
|
||||
|
||||
// Let's save browser window position
|
||||
@@ -217,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({
|
||||
@@ -226,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', () => {
|
||||
@@ -255,41 +155,28 @@ app.on('activate', () => {
|
||||
app.on('ready', () => {
|
||||
electron.Menu.setApplicationMenu(appMenu);
|
||||
mainWindow = createMainWindow();
|
||||
tray.create();
|
||||
|
||||
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) {
|
||||
@@ -297,25 +184,44 @@ app.on('ready', () => {
|
||||
appUpdater();
|
||||
}
|
||||
});
|
||||
checkConnection();
|
||||
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);
|
||||
serverError(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
|
||||
};
|
||||
@@ -8,9 +8,6 @@ const app = electron.app;
|
||||
const BrowserWindow = electron.BrowserWindow;
|
||||
const shell = electron.shell;
|
||||
const appName = app.getName();
|
||||
const tray = require('./tray');
|
||||
|
||||
const {addDomain, about} = require('./windowmanager');
|
||||
|
||||
function sendAction(action) {
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
@@ -35,7 +32,7 @@ const viewSubmenu = [
|
||||
label: 'Reload',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
sendAction('reload');
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -79,18 +76,27 @@ const viewSubmenu = [
|
||||
label: 'Toggle Tray Icon',
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
tray.toggle();
|
||||
focusedWindow.webContents.send('toggletray');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -127,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');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -260,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');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/* We'll be overriding default styling so that app look more native * /
|
||||
@@ -1,66 +0,0 @@
|
||||
'use strict';
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const {webFrame} = require('electron');
|
||||
const {spellChecker} = require('./spellchecker');
|
||||
|
||||
const _setImmediate = setImmediate;
|
||||
const _clearImmediate = clearImmediate;
|
||||
process.once('loaded', () => {
|
||||
global.setImmediate = _setImmediate;
|
||||
global.clearImmediate = _clearImmediate;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
require('./domain');
|
||||
|
||||
// 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
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
// To prevent failing this script on linux we need to load it after the document loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// init spellchecker
|
||||
spellChecker();
|
||||
});
|
||||
@@ -1,81 +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');
|
||||
};
|
||||
|
||||
const createHandler = () => {
|
||||
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);
|
||||
};
|
||||
|
||||
const destroyHandler = () => {
|
||||
tray.destroy();
|
||||
if (tray.isDestroyed()) {
|
||||
tray = null;
|
||||
} else {
|
||||
throw new Error('Tray icon not properly destroyed.');
|
||||
}
|
||||
};
|
||||
|
||||
const toggleHandler = () => {
|
||||
if (tray) {
|
||||
destroyHandler();
|
||||
} else {
|
||||
createHandler();
|
||||
}
|
||||
};
|
||||
|
||||
exports.create = createHandler;
|
||||
exports.destroy = destroyHandler;
|
||||
exports.toggle = toggleHandler;
|
||||
@@ -1,85 +0,0 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const electron = require('electron');
|
||||
|
||||
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
|
||||
|
||||
const iconPath = () => {
|
||||
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
|
||||
};
|
||||
let domainWindow;
|
||||
let aboutWindow;
|
||||
|
||||
function onClosed() {
|
||||
// dereference the window
|
||||
domainWindow = null;
|
||||
aboutWindow = null;
|
||||
}
|
||||
|
||||
// Change Zulip server Window
|
||||
function createdomainWindow() {
|
||||
const domainwin = new electron.BrowserWindow({
|
||||
title: 'Switch Server',
|
||||
frame: false,
|
||||
height: 300,
|
||||
resizable: false,
|
||||
width: 400,
|
||||
show: false,
|
||||
icon: iconPath()
|
||||
|
||||
});
|
||||
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.once('ready-to-show', () => {
|
||||
domainWindow.show();
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (domainWindow !== null) {
|
||||
if (!domainWindow.isDestroyed()) {
|
||||
domainWindow.destroy();
|
||||
}
|
||||
}
|
||||
}, 15000);
|
||||
}
|
||||
// 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.once('ready-to-show', () => {
|
||||
aboutWindow.show();
|
||||
});
|
||||
}
|
||||
|
||||
exports = module.exports = {addDomain, about};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "0.5.9",
|
||||
"version": "1.0.0-beta",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
"email": "<svnitakash@gmail.com>",
|
||||
@@ -27,12 +27,12 @@
|
||||
"InstantMessaging"
|
||||
],
|
||||
"dependencies": {
|
||||
"electron-config":"0.2.1",
|
||||
"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.5",
|
||||
"electron-spellchecker": "1.0.8",
|
||||
"electron-updater": "1.11.2",
|
||||
"https": "^1.0.0",
|
||||
"node-json-db": "0.7.3",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,74 +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;
|
||||
}
|
||||
162
app/renderer/css/preference.css
Normal file
162
app/renderer/css/preference.css
Normal file
@@ -0,0 +1,162 @@
|
||||
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;
|
||||
}
|
||||
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 |
@@ -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>Enter your Zulip Server URL</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="Server URL">
|
||||
</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.checkConnectivity();
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
190
app/renderer/js/main.js
Normal file
190
app/renderer/js/main.js
Normal file
@@ -0,0 +1,190 @@
|
||||
'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;
|
||||
},
|
||||
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;
|
||||
},
|
||||
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`
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
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,67 +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
|
||||
window.prefDomain = function () {
|
||||
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);
|
||||
|
||||
let newDomain = document.getElementById('url').value;
|
||||
newDomain = newDomain.replace(/^https?:\/\//, '');
|
||||
newDomain = newDomain.replace(/^http?:\/\//, '');
|
||||
|
||||
if (newDomain === '') {
|
||||
document.getElementById('urladded').innerHTML = 'Please input a value';
|
||||
} else {
|
||||
document.getElementById('main').innerHTML = 'Checking...';
|
||||
if (newDomain.indexOf('localhost:') >= 0) {
|
||||
const domain = 'http://' + newDomain;
|
||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
||||
request(checkDomain, (error, response) => {
|
||||
if (!error && response.statusCode !== 404) {
|
||||
document.getElementById('main').innerHTML = 'Switch';
|
||||
document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
|
||||
db.push('/domain', domain);
|
||||
ipcRenderer.send('new-domain', domain);
|
||||
} else {
|
||||
document.getElementById('main').innerHTML = 'Switch';
|
||||
document.getElementById('urladded').innerHTML = 'Not a valid Zulip Local Server.';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const domain = 'https://' + newDomain;
|
||||
const checkDomain = domain + '/static/audio/zulip.ogg';
|
||||
request(checkDomain, (error, response) => {
|
||||
if (!error && response.statusCode !== 404) {
|
||||
document.getElementById('main').innerHTML = 'Switch';
|
||||
document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
|
||||
db.push('/domain', domain);
|
||||
ipcRenderer.send('new-domain', domain);
|
||||
} else {
|
||||
document.getElementById('main').innerHTML = 'Switch';
|
||||
document.getElementById('urladded').innerHTML = 'Not a valid Zulip Server.';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
33
app/renderer/js/preload.js
Normal file
33
app/renderer/js/preload.js
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
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();
|
||||
});
|
||||
@@ -22,6 +22,6 @@ function spellChecker() {
|
||||
});
|
||||
}
|
||||
|
||||
exports = module.exports = {
|
||||
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>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="css/pref.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="close" id="close-button">Close</div>
|
||||
<div class="form">
|
||||
<form onsubmit="prefDomain(); return false">
|
||||
<input id="url" type="text" placeholder="Server URL">
|
||||
<button type="submit" id="main" value="Submit">
|
||||
Switch</button>
|
||||
</form>
|
||||
<p id="urladded"><p>
|
||||
</div>
|
||||
<script src="js/pref.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
45
app/renderer/preference.html
Normal file
45
app/renderer/preference.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!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">
|
||||
<i class="material-icons">check_box</i>
|
||||
<span>Save</span>
|
||||
</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).
|
||||
40
package.json
40
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "0.5.9",
|
||||
"version": "1.0.0-beta",
|
||||
"main": "./app/main",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
@@ -19,12 +19,13 @@
|
||||
"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"
|
||||
},
|
||||
@@ -45,6 +46,7 @@
|
||||
"description": "Zulip Desktop Client for Linux",
|
||||
"target": [
|
||||
"deb",
|
||||
"zip",
|
||||
"AppImage"
|
||||
],
|
||||
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
|
||||
@@ -73,7 +75,7 @@
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"icon": "build/icon.ico"
|
||||
},
|
||||
},
|
||||
"nsis": {
|
||||
"perMachine": true,
|
||||
"oneClick": false
|
||||
@@ -88,36 +90,42 @@
|
||||
"InstantMessaging"
|
||||
],
|
||||
"devDependencies": {
|
||||
"assert": "^1.4.1",
|
||||
"devtron": "^1.1.0",
|
||||
"electron-builder": "16.6.0",
|
||||
"electron": "1.4.15",
|
||||
"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": {
|
||||
"esnext": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "app/main/*.js",
|
||||
"files": "app*/**/*.js",
|
||||
"rules": {
|
||||
"max-lines": [
|
||||
"warn",
|
||||
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
|
||||
}
|
||||
}
|
||||
],
|
||||
"ignore": [
|
||||
"tests/*.js",
|
||||
"app/main/macos-swipe-navigation.js"
|
||||
"tests/*.js"
|
||||
],
|
||||
"envs": [
|
||||
"node",
|
||||
|
||||
@@ -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/renderer/index.html']
|
||||
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`
|
||||
Reference in New Issue
Block a user