Compare commits

..

1 Commits

Author SHA1 Message Date
akashnimare
7194e788e4 Add a setting option to control auto-updates 2017-09-12 21:43:53 +05:30
96 changed files with 488 additions and 14200 deletions

View File

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

View File

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

7
.gitignore vendored
View File

@@ -23,9 +23,4 @@ yarn-error.log*
# miscellaneous
.idea
config.gypi
# Test generated files
tests/package.json
.python-version
config.gypi

1
.python-version Normal file
View File

@@ -0,0 +1 @@
2.7.9

View File

@@ -2,57 +2,32 @@ sudo: required
dist: trusty
os:
- osx
- linux
osx_image: xcode9.0
- osx
- linux
addons:
apt:
packages:
- build-essential
- libxext-dev
- libxtst-dev
- libxkbfile-dev
artifacts:
paths:
- $(ls ./dist/*.AppImage | tr "\n" ":")
- $(ls ./dist/*.deb | tr "\n" ":")
- $(ls ./dist/*.dmg | tr "\n" ":")
- $(ls ./dist/*.zip | tr "\n" ":")
- $(ls ./dist/*.dmg.blockmap | tr "\n" ":")
- $(ls ./dist/github/*.json | tr "\n" ":")
- $(ls ./dist/github/*.yml | tr "\n" ":")
- $(ls ./dist/*.yml | tr "\n" ":")
- $(ls ./dist/mac/*.yml | tr "\n" ":")
- $(ls ./dist/linux/*.yml | tr "\n" ":")
debug: true
- build-essential
- libxext-dev
- libxtst-dev
- libxkbfile-dev
language: node_js
node_js:
- '6'
before_install:
- ./scripts/travis-xvfb.sh
- npm install -g gulp
- npm install
- npm install -g gulp
- npm install
cache:
directories:
- node_modules
- app/node_modules
- ~/.cache
- node_modules
- app/node_modules
script:
- npm run travis
- chmod +x ./scripts/install-release-dependencies.sh
- ./scripts/install-release-dependencies.sh
- npm run dist
- node ./scripts/prepare-artifacts.js
# log out /dist files might be useful to know
# what files are uploaded
- ls dist
- npm run travis
notifications:
webhooks:
urls:

7
ISSUE_TEMPLATE.md Normal file
View File

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

View File

@@ -8,16 +8,23 @@ Desktop client for Zulip. Available for Mac, Linux and Windows.
<img src="http://i.imgur.com/ChzTq4F.png"/>
# Download
Please see [installation guide](https://zulipchat.com/help/desktop-app-install-guide).
Please see [installation guide](./how-to-install.md).
# Features
* Sign in to multiple teams
* Desktop Notifications with inline reply support
* Multilanguage SpellChecker
* Native desktop Notifications
* SpellChecker
* OSX/Win/Linux installers
* Automatic Updates (macOS/Windows/Linux)
* Automatic Updates (macOS/Windows)
* Keyboard shortcuts
Description | Keys
-----------------------| -----------------------
Default shortcuts | <kbd>Cmd/Ctrl</kbd> <kbd>k</kbd>
Manage Zulip Servers | <kbd>Cmd/Ctrl</kbd> <kbd>,</kbd>
Back | <kbd>Cmd/Ctrl</kbd> <kbd>[</kbd>
Forward | <kbd>Cmd/Ctrl</kbd> <kbd>]</kbd>
# Development
Please see our [development guide](./development.md) to get started and run app locally.

View File

@@ -1,4 +1,5 @@
'use strict';
const fs = require('fs');
const { app, dialog } = require('electron');
const { autoUpdater } = require('electron-updater');
const isDev = require('electron-is-dev');
@@ -6,20 +7,19 @@ const isDev = require('electron-is-dev');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
function appUpdater() {
// Don't initiate auto-updates in development
if (isDev) {
return;
}
if (process.platform === 'linux' && !process.env.APPIMAGE) {
const { linuxUpdateNotification } = require('./linuxupdater');
linuxUpdateNotification();
// Don't initiate auto-updates in development and on Linux system
// since autoUpdater doesn't work on Linux
if (isDev || process.platform === 'linux') {
return;
}
// Create Logs directory
const LogsDir = `${app.getPath('userData')}/Logs`;
if (!fs.existsSync(LogsDir)) {
fs.mkdirSync(LogsDir);
}
// Log whats happening
const log = require('electron-log');

View File

@@ -1,16 +0,0 @@
'use strict';
const { crashReporter } = require('electron');
const crashHandler = () => {
crashReporter.start({
productName: 'zulip-electron',
companyName: 'Kandra Labs, Inc.',
submitURL: 'https://zulip-sentry.herokuapp.com/crashreport',
autoSubmit: true
});
};
module.exports = {
crashHandler
};

View File

@@ -1,24 +1,18 @@
'use strict';
const path = require('path');
const electron = require('electron');
const electronLocalshortcut = require('electron-localshortcut');
const windowStateKeeper = require('electron-window-state');
const isDev = require('electron-is-dev');
const appMenu = require('./menu');
const { appUpdater } = require('./autoupdater');
const { crashHandler } = require('./crash-reporter');
const { setAutoLaunch } = require('./startup');
const { app, ipcMain } = electron;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
// Adds debug features like hotkeys for triggering dev tools and reload
// in development mode
if (isDev) {
require('electron-debug')();
}
require('electron-debug')();
// Prevent window being garbage collected
let mainWindow;
@@ -67,8 +61,8 @@ function createMainWindow() {
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
minWidth: 300,
minHeight: 400,
minWidth: 600,
minHeight: 500,
webPreferences: {
plugins: true,
allowDisplayingInsecureContent: true,
@@ -82,11 +76,7 @@ function createMainWindow() {
});
win.once('ready-to-show', () => {
if (ConfigUtil.getConfigItem('startMinimized')) {
win.minimize();
} else {
win.show();
}
win.show();
});
win.loadURL(mainURL);
@@ -102,6 +92,9 @@ function createMainWindow() {
win.hide();
}
}
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
win.setTitle('Zulip');
@@ -129,8 +122,21 @@ function createMainWindow() {
return win;
}
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
function registerLocalShortcuts(page) {
// Somehow, reload action cannot be overwritten by the menu item
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
page.send('reload-viewer');
});
// Also adding these shortcuts because some users might want to use it instead of CMD/Left-Right
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
page.send('back');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
page.send('forward');
});
}
// eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
@@ -138,6 +144,11 @@ app.on('certificate-error', (event, webContents, url, error, certificate, callba
callback(true);
});
app.on('window-all-closed', () => {
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
app.on('activate', () => {
if (!mainWindow) {
mainWindow = createMainWindow();
@@ -152,25 +163,20 @@ app.on('ready', () => {
const page = mainWindow.webContents;
registerLocalShortcuts(page);
page.on('dom-ready', () => {
if (ConfigUtil.getConfigItem('startMinimized')) {
mainWindow.minimize();
} else {
mainWindow.show();
}
mainWindow.show();
});
page.once('did-frame-finish-load', () => {
// Initate auto-updates on MacOS and Windows
appUpdater();
crashHandler();
});
// Temporarily remove this event
// electron.powerMonitor.on('resume', () => {
// mainWindow.reload();
// page.send('destroytray');
// });
electron.powerMonitor.on('resume', () => {
page.send('reload-viewer');
});
ipcMain.on('focus-app', () => {
mainWindow.show();
@@ -222,11 +228,31 @@ app.on('ready', () => {
appMenu.setMenu(props);
});
ipcMain.on('register-server-tab-shortcut', (event, index) => {
electronLocalshortcut.register(mainWindow, `CommandOrControl+${index}`, () => {
// Array index == Shown index - 1
page.send('switch-server-tab', index - 1);
});
});
ipcMain.on('local-shortcuts', (event, enable) => {
if (enable) {
registerLocalShortcuts(page);
} else {
electronLocalshortcut.unregisterAll(mainWindow);
}
});
ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => {
setAutoLaunch(AutoLaunchValue);
});
});
app.on('will-quit', () => {
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
app.on('before-quit', () => {
isQuitting = true;
});

View File

@@ -1,42 +0,0 @@
const { app } = require('electron');
const { Notification } = require('electron');
const request = require('request');
const semver = require('semver');
const ConfigUtil = require('../renderer/js/utils/config-util');
const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
function linuxUpdateNotification() {
let url = 'https://api.github.com/repos/zulip/zulip-electron/releases';
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
const options = {
url,
headers: {'User-Agent': 'request'}
};
request(options, (error, response, body) => {
if (error) {
console.log('Error:', error);
return;
}
if (response.statusCode < 400) {
const data = JSON.parse(body);
const latestVersion = ConfigUtil.getConfigItem('betaUpdate') ? data[0].tag_name : data.tag_name;
if (semver.gt(latestVersion, app.getVersion())) {
const notified = LinuxUpdateUtil.getUpdateItem(latestVersion);
if (notified === null) {
new Notification({title: 'Zulip Update', body: 'A new version ' + latestVersion + ' is available. Please update using your package manager.'}).show();
LinuxUpdateUtil.setUpdateItem(latestVersion, true);
}
}
} else {
console.log('Status:', response.statusCode);
}
});
}
module.exports = {
linuxUpdateNotification
};

View File

@@ -37,7 +37,7 @@ class AppMenu {
accelerator: 'CommandOrControl+R',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('reload-current-viewer');
AppMenu.sendAction('reload-viewer');
}
}
}, {
@@ -54,7 +54,7 @@ class AppMenu {
role: 'togglefullscreen'
}, {
label: 'Zoom In',
accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=',
accelerator: 'CommandOrControl+=',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomIn');
@@ -115,39 +115,26 @@ class AppMenu {
}
getHelpSubmenu() {
return [
{
label: `${appName + ' Desktop-'} v${app.getVersion()}`,
enabled: false
},
{
label: `What's New...`,
click() {
shell.openExternal(`https://github.com/zulip/zulip-electron/releases/tag/v${app.getVersion()}`);
}
},
{
label: `${appName} Help`,
click() {
shell.openExternal('https://zulipchat.com/help/');
}
}, {
label: 'Show App Logs',
click() {
shell.openItem(app.getPath('userData'));
}
}, {
label: 'Report an Issue...',
click() {
const body = `
return [{
label: `${appName} Website`,
click() {
shell.openExternal('https://zulipchat.com/help/');
}
}, {
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
enabled: false
}, {
label: 'Report an Issue...',
click() {
const body = `
<!-- Please succinctly describe your issue and steps to reproduce it. -->
-
${app.getName()} ${app.getVersion()}
Electron ${process.versions.electron}
${process.platform} ${process.arch} ${os.release()}`;
shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`);
}
}];
shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`);
}
}];
}
getWindowSubmenu(tabs, activeTabIndex) {
@@ -163,11 +150,6 @@ class AppMenu {
type: 'separator'
});
for (let i = 0; i < tabs.length; i++) {
// Do not add functional tab settings to list of windows in menu bar
if (tabs[i].props.role === 'function' && tabs[i].webview.props.name === 'Settings') {
continue;
}
initialSubmenu.push({
label: tabs[i].webview.props.name,
accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`,
@@ -177,7 +159,7 @@ class AppMenu {
AppMenu.sendAction('switch-server-tab', tabs[i].props.index);
}
},
type: 'checkbox'
type: 'radio'
});
}
}
@@ -191,7 +173,7 @@ class AppMenu {
return [{
label: `${app.getName()}`,
submenu: [{
label: 'About Zulip',
label: 'Zulip Desktop',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
@@ -200,7 +182,7 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: 'Desktop App Settings',
label: 'Settings',
accelerator: 'Cmd+,',
click(item, focusedWindow) {
if (focusedWindow) {
@@ -209,7 +191,7 @@ class AppMenu {
}
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Cmd+Shift+K',
accelerator: 'Cmd+K',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
@@ -291,7 +273,7 @@ class AppMenu {
return [{
label: 'File',
submenu: [{
label: 'About Zulip',
label: 'Zulip Desktop',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
@@ -300,7 +282,7 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: 'Desktop App Settings',
label: 'Settings',
accelerator: 'Ctrl+,',
click(item, focusedWindow) {
if (focusedWindow) {
@@ -311,7 +293,7 @@ class AppMenu {
type: 'separator'
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Ctrl+Shift+K',
accelerator: 'Ctrl+K',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
@@ -388,20 +370,10 @@ class AppMenu {
}
static resetAppSettings() {
// We save App's settings/configurations in following files
const settingFiles = ['window-state.json', 'domain.json', 'settings.json'];
const getAppPath = path.join(app.getPath('appData'), appName, 'window-state.json');
settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, error => {
if (error) {
console.log(error);
} else {
fs.unlink(getSettingFilesPath, () => {
AppMenu.sendAction('clear-app-data');
});
}
});
fs.unlink(getAppPath, () => {
setTimeout(() => AppMenu.sendAction('clear-app-data'), 1000);
});
}

1380
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,14 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.8.2",
"version": "1.4.0",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
"email": "<svnitakash@gmail.com>",
"copyright": "©2017 Kandra Labs, Inc.",
"author": {
"name": "Kandra Labs, Inc.",
"email": "support@zulipchat.com"
"email": "svnitakash@gmail.com"
},
"repository": {
"type": "git",
@@ -26,19 +27,16 @@
"InstantMessaging"
],
"dependencies": {
"auto-launch": "5.0.1",
"electron-debug": "1.4.0",
"electron-is-dev": "0.3.0",
"electron-localshortcut": "2.0.2",
"electron-log": "2.2.7",
"electron-spellchecker": "1.1.2",
"electron-updater": "2.21.0",
"electron-window-state": "4.1.1",
"is-online": "7.0.0",
"electron-spellchecker": "1.2.0",
"electron-updater": "2.8.9",
"node-json-db": "0.7.3",
"request": "2.81.0",
"semver": "5.4.1",
"wurl": "2.5.0"
},
"optionalDependencies": {
"node-mac-notifier": "0.0.13"
"wurl": "2.5.0",
"electron-window-state": "4.1.1",
"auto-launch": "5.0.1"
}
}

View File

@@ -7,37 +7,25 @@
<body>
<div class="about">
<img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version">v?.?.?</p>
<p class="detail" id="version">version ?.?.?</p>
<div class="maintenance-info">
<p class="detail maintainer">
Maintained by <a onclick="linkInBrowser('website')">Zulip</a>
</p>
<p class="detail license">
Available under the <a onclick="linkInBrowser('license')">Apache 2.0 License</a>
</p>
<a class="bug" onclick="linkInBrowser('bug')" href="#">Found bug?</a>
<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;
const { shell } = require('electron');
const version_tag = document.querySelector('#version');
version_tag.innerHTML = 'v' + app.getVersion();
const app = require('electron').remote.app;
const version_tag = document.getElementById('version');
version_tag.innerHTML = 'version ' + app.getVersion();
function linkInBrowser(event) {
const shell = require('electron').shell;
const url = "https://github.com/zulip/zulip-electron/issues/new?body=Please describe your issue and steps to reproduce it."
function linkInBrowser(type) {
let url;
switch (type) {
case 'website':
url = "https://zulipchat.com";
break;
case 'license':
url = "https://github.com/zulip/zulip-electron/blob/master/LICENSE";
break;
default:
url = 'https://github.com/zulip/zulip-electron/issues/new?body=' +
'%3C!--Please%20describe%20your%20issue%20and%20steps%20to%20reproduce%20it.--%3E';
}
shell.openExternal(url);
}
</script>

View File

@@ -1,23 +1,21 @@
body {
background: #fafafa;
font-family: menu, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: subpixel-antialiased;
-webkit-font-smoothing: antialiased;
}
.logo {
display: block;
margin: -40px auto;
margin: 0 auto;
}
#version {
color: #444343;
font-size: 1.3em;
padding-top: 40px;
color: #aaa;
font-size: 0.9em;
}
.about {
margin: 25vh auto;
height: 25vh;
margin-top: 50px;
text-align: center;
}
@@ -44,9 +42,9 @@ body {
}
.maintenance-info {
cursor: pointer;
position: absolute;
width: 100%;
bottom: 20px;
left: 0px;
color: #444;
}
@@ -54,6 +52,7 @@ body {
.maintenance-info p {
margin: 0;
font-size: 1em;
width: 100%;
}
@@ -72,11 +71,3 @@ body {
.maintenance-info .bug:hover {
background-color: #32a692;
}
p.detail a {
color: #355f4c;
}
p.detail a:hover {
text-decoration: underline;
}

View File

@@ -13,12 +13,12 @@ body {
#content {
display: flex;
height: 100%;
background: #fff url(../img/ic_loading.gif) no-repeat;
background: #eee url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
}
.toggle-sidebar {
#sidebar {
background: #222c31;
width: 54px;
padding: 27px 0 20px 0;
@@ -27,22 +27,6 @@ body {
flex-direction: column;
-webkit-app-region: drag;
overflow: hidden;
transition: all 0.5s ease;
z-index: 2;
}
.toggle-sidebar div {
transition: all 0.5s ease-out;
}
.sidebar-hide {
width: 0;
transition: all 0.8s ease;
}
.sidebar-hide div {
transform: translateX(-100%);
transition: all 0.6s ease-out;
}
@font-face {
@@ -54,8 +38,8 @@ body {
/*******************
* Left Sidebar *
*******************/
* Left Sidebar *
*******************/
#tabs-container {
display: flex;
@@ -82,17 +66,11 @@ body {
text-rendering: optimizeLegibility;
}
#actions-container {
display: flex;
flex-direction: column;
position: fixed;
bottom: 0;
}
.action-button {
display: flex;
flex-direction: column;
padding: 12px;
align-items: center;
padding: 10px;
}
.action-button:hover {
@@ -108,26 +86,6 @@ body {
color: #98a9b3;
}
.action-button.disable {
opacity: 0.6;
}
.action-button.disable:hover {
cursor: not-allowed;
}
.action-button.disable:hover i {
color: #6c8592;
}
.action-button.active {
background-color: rgba(255, 255, 255, 0.25);
}
.action-button.active i {
color: #eee;
}
.tab:first-child {
margin-top: 8px;
}
@@ -239,8 +197,8 @@ body {
/*******************
* Webview Area *
*******************/
* Webview Area *
*******************/
#webviews-container {
display: flex;
@@ -249,42 +207,35 @@ body {
}
webview {
/* transition: opacity 0.3s ease-in; */
opacity: 1;
transition: opacity 0.3s ease-in;
flex-grow: 1;
position: absolute;
width: 100%;
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
}
webview.onload {
transition: opacity 1s cubic-bezier(0.95, 0.05, 0.795, 0.035);
}
webview.active {
opacity: 1;
z-index: 1;
visibility: visible;
}
webview.disabled {
flex: 0 1;
height: 0;
width: 0;
opacity: 0;
transition: opacity 0.3s ease-out;
}
webview.focus {
webview:focus {
outline: 0px solid transparent;
}
/* Tooltip styling */
#back-tooltip,
#reload-tooltip,
#setting-tooltip {
font-family: sans-serif;
background: #222c31;
margin-left: 45px;
margin-left: 68px;
padding: 6px 8px;
position: absolute;
margin-top: 0px;
@@ -296,7 +247,6 @@ webview.focus {
font-size: 14px;
}
#back-tooltip:after,
#reload-tooltip:after,
#setting-tooltip:after {
content: " ";
@@ -308,33 +258,6 @@ webview.focus {
right: 68px;
}
#add-server-tooltip,
.server-tooltip {
font-family: 'arial';
background: #222c31;
left: 56px;
padding: 10px 20px;
position: fixed;
margin-top: 8px;
z-index: 5000 !important;
color: #fff;
border-radius: 4px;
text-align: center;
width: max-content;
font-size: 14px;
}
#add-server-tooltip:after,
.server-tooltip:after {
content: " ";
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid #222c31;
position: absolute;
top: 10px;
left: -5px;
}
#collapse-button {
bottom: 30px;
left: 20px;
@@ -362,9 +285,7 @@ webview.focus {
display: none !important;
}
/* Full screen Popup container */
.popup .popuptext {
visibility: hidden;
background-color: #555;

View File

@@ -5,103 +5,88 @@ body {
cursor: default;
user-select: none;
font-family: menu, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-webkit-font-smoothing: antialiased;
font-size: 14px;
color: #333;
background: #efefef;
}
kbd {
display: inline-block;
padding: 0.1em 0.6em;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 15px;
font-family: Courier New, Courier, monospace;
font-weight: bold;
white-space: nowrap;
font-size: 12px;
font-family: Arial,Helvetica,sans-serif;
background-color: #f7f7f7;
color: #333;
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset;
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset;
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
display: inline-block;
margin: 0 0.1em;
padding: 0.3em 0.8em;
text-shadow: 0 1px 0 #fff;
line-height: 1.4;
white-space: nowrap;
}
table,
th,
td {
table, th, td {
border: 1px solid #ddd;
border-collapse: collapse;
color: #383430;
}
table {
width: 100%;
margin-top: 18px;
margin-bottom: 18px;
}
table { width: 85%; }
table tr:nth-child(even) {
background-color: #fafafa;
}
table tr:nth-child(even) { background-color: #f2f2f2; }
table tr:nth-child(odd) {
background-color: #fff;
}
td {
padding: 5px;
}
td { padding: 5px; }
td:nth-child(odd) {
text-align: right;
width: 50%;
text-align: right;
width: 50%;
}
@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');
}
@font-face {
font-family: 'Montserrat';
src: url(../fonts/Montserrat-Regular.ttf) format('truetype');
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
}
.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;
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;
}
#content {
display: flex;
height: 100%;
font-family: 'Montserrat';
font-family: sans-serif;
}
#sidebar {
width: 150px;
min-width: 100px;
padding: 30px 30px 30px 35px;
width: 80px;
padding: 30px;
display: flex;
flex-direction: column;
font-size: 16px;
background: #f2f2f2;
}
#nav-container {
@@ -131,9 +116,7 @@ td:nth-child(odd) {
#settings-header {
font-size: 22px;
color: #222c31;
font-weight: bold;
text-transform: uppercase;
color: #5c6166;
}
#settings-container {
@@ -150,15 +133,8 @@ td:nth-child(odd) {
.title {
padding: 4px 0 6px 0;
font-weight: 500;
color: #222c31;
}
.page-title {
color: #222c31;
font-size: 15px;
font-weight: bold;
padding: 4px 0 6px 0;
color: #1e1e1e;
}
.sub-title {
@@ -205,18 +181,16 @@ img.server-info-icon {
.setting-input-value {
flex-grow: 1;
font-size: 14px;
height: 22px;
border-radius: 3px;
padding: 7px;
border: #ededed 2px solid;
height: 24px;
border: none;
border-bottom: #ededed 1px solid;
outline-width: 0;
background: transparent;
max-width: 500px;
}
.setting-input-value:focus {
border: #7cb980 2px solid;
border-radius: 3px;
border-bottom: #7cb980 1px solid;
}
.setting-block {
@@ -236,13 +210,14 @@ img.server-info-icon {
display: flex;
align-items: center;
padding: 0 10px;
border-radius: 2px;
margin-right: 10px;
}
.action i {
margin-right: 5px;
font-size: 18px;
line-height: 26px;
line-height: 27px;
}
.settings-pane {
@@ -267,13 +242,9 @@ img.server-info-icon {
padding: 12px 30px;
margin: 10px 0 20px 0;
background: #fff;
width: 70%;
transition: all 0.2s;
}
.settings-card:hover {
border-left: 8px solid #bcbcbc;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 0px 0px rgba(0, 0, 0, 0.12);
border-radius: 2px;
width: 540px;
box-shadow: 1px 2px 4px #bcbcbc;
}
.hidden {
@@ -282,19 +253,15 @@ img.server-info-icon {
}
.red {
color: #ffffff;
background: #ef5350;
padding: 3px;
padding-right: 10px;
padding-left: 10px;
color: #ef5350;
background: #ffebee;
border: 1px solid #ef5350;
}
.blue {
color: #ffffff;
background: #4EBFAC;
padding: 3px;
padding-right: 10px;
padding-left: 10px;
.green {
color: #388E3C;
background: #E8F5E9;
border: 1px solid #388E3C;
}
.grey {
@@ -305,10 +272,9 @@ img.server-info-icon {
.setting-row {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin: 6px;
margin: 6px 0;
}
.code {
@@ -321,130 +287,28 @@ i.open-tab-button {
cursor: pointer;
}
.reset-data-button,
.custom-css-button {
.reset-data-button {
display: inline-block;
border: none;
padding: 10px;
width: 120px;
cursor: pointer;
font-size: 13px;
font-size: 11px;
transition: background-color 0.2s ease;
text-decoration: none;
}
.css-delete-action {
margin-bottom: 10px;
}
.reset-data-button:hover {
background-color: #3c9f8d;
background-color: #32a692;
color: #fff;
}
.selected-css-path {
background: #eeeeee;
padding: 10px;
margin-top: 10px;
margin-right: 10px;
display: flex;
width: 90%;
justify-content: space-between;
#open-shortcuts-url {
color: #08c;
cursor: pointer;
text-decoration: none;
}
#remove-custom-css {
align-items: flex-end;
}
#server-info-container {
min-height: calc(100% - 260px);
}
#create-organization-container {
font-size: 1.15em;
}
#create-organization-container i {
position: relative;
top: 3px;
}
#open-create-org-link {
color: #666;
cursor: pointer;
text-decoration: none;
}
#open-create-org-link:hover {
color: #005580;
;
text-decoration: underline;
}
.toggle {
position: absolute;
margin-left: -9999px;
visibility: hidden;
}
.toggle+label {
display: block;
position: relative;
cursor: pointer;
outline: none;
user-select: none;
}
input.toggle-round+label {
padding: 2px;
width: 50px;
height: 25px;
background-color: #dddddd;
border-radius: 25px;
}
input.toggle-round+label:before,
input.toggle-round+label:after {
display: block;
position: absolute;
top: 2px;
left: 2px;
bottom: 2px;
content: "";
}
input.toggle-round+label:before {
right: 2px;
background-color: #f1f1f1;
border-radius: 25px;
}
input.toggle-round+label:after {
width: 25px;
height: 25px;
background-color: #fff;
border-radius: 100%;
}
input.toggle-round:checked+label:before {
background-color: #4EBFAC;
}
input.toggle-round:checked+label:after {
margin-left: 25px;
}
/* responsive grid */
@media (max-width: 650px) {
.selected-css-path {
margin-right: 15px;
}
#css-delete-action {
margin-left: 10px;
}
#css-delete-action span {
display: none;
}
#open-shortcuts-url:hover {
color: #005580;;
text-decoration: underline;
}

View File

@@ -4,7 +4,7 @@ const Tab = require(__dirname + '/../components/tab.js');
class FunctionalTab extends Tab {
template() {
return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}">
return `<div class="tab functional-tab">
<div class="server-tab-badge close-button">
<i class="material-icons">close</i>
</div>
@@ -16,11 +16,10 @@ class FunctionalTab extends Tab {
init() {
this.$el = this.generateNodeFromTemplate(this.template());
if (this.props.name !== 'Settings') {
this.props.$root.appendChild(this.$el);
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
this.registerListeners();
}
this.props.$root.appendChild(this.$el);
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
this.registerListeners();
}
registerListeners() {

View File

@@ -7,8 +7,7 @@ const {ipcRenderer} = require('electron');
class ServerTab extends Tab {
template() {
return `<div class="tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tooltip" style="display:none"></div>
return `<div class="tab">
<div class="server-tab-badge"></div>
<div class="server-tab">
<img class="server-icons" src='${this.props.icon}'/>
@@ -50,8 +49,7 @@ class ServerTab extends Tab {
shortcutText = `Ctrl+${shownIndex}`;
}
// Array index == Shown index - 1
ipcRenderer.send('switch-server-tab', shownIndex - 1);
ipcRenderer.send('register-server-tab-shortcut', shownIndex);
return shortcutText;
}

View File

@@ -21,8 +21,6 @@ class Tab extends BaseComponent {
registerListeners() {
this.$el.addEventListener('click', this.props.onClick);
this.$el.addEventListener('mouseover', this.props.onHover);
this.$el.addEventListener('mouseout', this.props.onHoverOut);
}
isLoading() {

View File

@@ -4,14 +4,12 @@ const path = require('path');
const fs = require('fs');
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
const ConfigUtil = require(__dirname + '/../utils/config-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const LinkUtil = require(__dirname + '/../utils/link-util.js');
const { shell, app, dialog } = require('electron').remote;
const {shell} = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js');
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
class WebView extends BaseComponent {
constructor(props) {
super();
@@ -21,13 +19,11 @@ class WebView extends BaseComponent {
this.zoomFactor = 1.0;
this.loading = false;
this.badgeCount = 0;
this.customCSS = ConfigUtil.getConfigItem('customCSS');
}
template() {
return `<webview
class="disabled"
data-tab-id="${this.props.tabIndex}"
src="${this.props.url}"
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
disablewebsecurity
@@ -46,7 +42,7 @@ class WebView extends BaseComponent {
registerListeners() {
this.$el.addEventListener('new-window', event => {
const { url } = event;
const {url} = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) {
@@ -58,40 +54,12 @@ class WebView extends BaseComponent {
}
});
if (shouldSilentWebview) {
this.$el.addEventListener('dom-ready', () => {
this.$el.setAudioMuted(true);
});
}
this.$el.addEventListener('page-title-updated', event => {
const { title } = event;
const {title} = event;
this.badgeCount = this.getBadgeCount(title);
this.props.onTitleChange();
});
this.$el.addEventListener('did-navigate-in-page', event => {
const isSettingPage = event.url.includes('renderer/preference.html');
if (isSettingPage) {
return;
}
this.canGoBackButton();
});
this.$el.addEventListener('did-navigate', () => {
this.canGoBackButton();
});
this.$el.addEventListener('page-favicon-updated', event => {
const { favicons } = event;
// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like
// https://chat.zulip.org/static/images/favicon/favicon-pms.png
if (favicons[0].indexOf('favicon-pms') > 0 && process.platform === 'darwin') {
// This api is only supported on macOS
app.dock.setBadge('●');
}
});
this.$el.addEventListener('dom-ready', () => {
if (this.props.role === 'server') {
this.$el.classList.add('onload');
@@ -100,7 +68,7 @@ class WebView extends BaseComponent {
});
this.$el.addEventListener('did-fail-load', event => {
const { errorDescription } = event;
const {errorDescription} = event;
const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0);
if (hasConnectivityErr) {
console.error('error', errorDescription);
@@ -130,7 +98,6 @@ class WebView extends BaseComponent {
}
this.$el.classList.remove('disabled');
this.$el.classList.add('active');
setTimeout(() => {
if (this.props.role === 'server') {
this.$el.classList.remove('onload');
@@ -141,21 +108,6 @@ class WebView extends BaseComponent {
this.props.onTitleChange();
// Injecting preload css in webview to override some css rules
this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8'));
// get customCSS again from config util to avoid warning user again
this.customCSS = ConfigUtil.getConfigItem('customCSS');
if (this.customCSS) {
if (!fs.existsSync(this.customCSS)) {
this.customCSS = null;
ConfigUtil.setConfigItem('customCSS', null);
const errMsg = 'The custom css previously set is deleted!';
dialog.showErrorBox('custom css file deleted!', errMsg);
return;
}
this.$el.insertCSS(fs.readFileSync(path.resolve(__dirname, this.customCSS), 'utf8'));
}
}
focus() {
@@ -169,7 +121,6 @@ class WebView extends BaseComponent {
hide() {
this.$el.classList.add('disabled');
this.$el.classList.remove('active');
}
load() {
@@ -213,15 +164,6 @@ class WebView extends BaseComponent {
}
}
canGoBackButton() {
const $backButton = document.querySelector('#actions-container #back-action');
if (this.$el.canGoBack()) {
$backButton.classList.remove('disable');
} else {
$backButton.classList.add('disable');
}
}
forward() {
if (this.$el.canGoForward()) {
this.$el.goForward();

View File

@@ -1,17 +1,15 @@
'use strict';
require(__dirname + '/js/tray.js');
const { ipcRenderer, remote } = require('electron');
const isDev = require('electron-is-dev');
const { session } = remote;
require(__dirname + '/js/tray.js');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
const WebView = require(__dirname + '/js/components/webview.js');
const ServerTab = require(__dirname + '/js/components/server-tab.js');
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
class ServerManagerView {
constructor() {
@@ -22,14 +20,9 @@ class ServerManagerView {
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$webviewsContainer = document.getElementById('webviews-container');
this.$backButton = $actionsContainer.querySelector('#back-action');
this.$addServerTooltip = document.getElementById('add-server-tooltip');
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip');
this.$backTooltip = $actionsContainer.querySelector('#back-tooltip');
this.$sidebar = document.getElementById('sidebar');
this.$fullscreenPopup = document.getElementById('fullscreen-popup');
@@ -39,7 +32,6 @@ class ServerManagerView {
this.activeTabIndex = -1;
this.tabs = [];
this.functionalTabs = {};
this.tabIndex = 0;
}
init() {
@@ -48,7 +40,6 @@ class ServerManagerView {
this.initTabs();
this.initActions();
this.registerIpcs();
this.initDefaultSettings();
});
}
@@ -71,39 +62,6 @@ class ServerManagerView {
});
}
// Settings are initialized only when user clicks on General/Server/Network section settings
// In case, user doesn't visit these section, those values set to be null automatically
// This will make sure the default settings are correctly set to either true or false
initDefaultSettings() {
// Default settings which should be respected
const settingOptions = {
trayIcon: true,
useProxy: false,
showSidebar: true,
badgeOption: true,
startAtLogin: false,
startMinimized: false,
enableSpellchecker: true,
showNotification: true,
betaUpdate: false,
silent: false,
lastActiveTab: 0
};
// Platform specific settings
if (process.platform === 'win32') {
// Only available on Windows
settingOptions.flashTaskbarOnMessage = true;
}
for (const i in settingOptions) {
if (ConfigUtil.getConfigItem(i) === null) {
ConfigUtil.setConfigItem(i, settingOptions[i]);
}
}
}
initSidebar() {
const showSidebar = ConfigUtil.getConfigItem('showSidebar', true);
this.toggleSidebar(showSidebar);
@@ -117,30 +75,24 @@ class ServerManagerView {
DomainUtil.updateSavedServer(servers[i].url, i);
this.activateTab(i);
}
// Open last active tab
this.activateTab(ConfigUtil.getConfigItem('lastActiveTab'));
// Remove focus from the settings icon at sidebar bottom
this.$settingsButton.classList.remove('active');
this.activateTab(0);
} else {
this.openSettings('Servers');
}
ipcRenderer.send('local-shortcuts', true);
}
initServer(server, index) {
const tabIndex = this.getTabIndex();
this.tabs.push(new ServerTab({
role: 'server',
icon: server.icon,
$root: this.$tabsContainer,
onClick: this.activateLastTab.bind(this, index),
onClick: this.activateTab.bind(this, index),
index,
tabIndex,
onHover: this.onHover.bind(this, index, server.alias),
onHoverOut: this.onHoverOut.bind(this, index),
webview: new WebView({
$root: this.$webviewsContainer,
index,
tabIndex,
url: server.url,
name: server.alias,
isActive: () => {
@@ -164,27 +116,9 @@ class ServerManagerView {
this.$settingsButton.addEventListener('click', () => {
this.openSettings('General');
});
this.$backButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.back();
});
const $serverImgs = document.querySelectorAll('.server-icons');
$serverImgs.forEach($serverImg => {
$serverImg.addEventListener('error', () => {
$serverImg.src = 'img/icon.png';
});
});
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip);
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
}
getTabIndex() {
const currentIndex = this.tabIndex;
this.tabIndex++;
return currentIndex;
}
sidebarHoverEvent(SidebarButton, SidebarTooltip) {
@@ -196,15 +130,6 @@ class ServerManagerView {
});
}
onHover(index, serverName) {
this.$serverIconTooltip[index].innerHTML = serverName;
this.$serverIconTooltip[index].removeAttribute('style');
}
onHoverOut(index) {
this.$serverIconTooltip[index].style.display = 'none';
}
openFunctionalTab(tabProps) {
if (this.functionalTabs[tabProps.name] !== undefined) {
this.activateTab(this.functionalTabs[tabProps.name]);
@@ -213,20 +138,16 @@ class ServerManagerView {
this.functionalTabs[tabProps.name] = this.tabs.length;
const tabIndex = this.getTabIndex();
this.tabs.push(new FunctionalTab({
role: 'function',
materialIcon: tabProps.materialIcon,
name: tabProps.name,
$root: this.$tabsContainer,
index: this.functionalTabs[tabProps.name],
tabIndex,
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
webview: new WebView({
$root: this.$webviewsContainer,
index: this.functionalTabs[tabProps.name],
tabIndex,
url: tabProps.url,
name: tabProps.name,
isActive: () => {
@@ -238,6 +159,7 @@ class ServerManagerView {
preload: false
})
}));
this.activateTab(this.functionalTabs[tabProps.name]);
}
@@ -247,7 +169,6 @@ class ServerManagerView {
materialIcon: 'settings',
url: `file://${__dirname}/preference.html#${nav}`
});
this.$settingsButton.classList.add('active');
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
}
@@ -267,15 +188,8 @@ class ServerManagerView {
});
}
activateLastTab(index) {
// Open last active tab
ConfigUtil.setConfigItem('lastActiveTab', index);
// Open all the tabs in background
this.activateTab(index);
}
activateTab(index, hideOldTab = true) {
if (!this.tabs[index]) {
if (this.tabs[index].webview.loading) {
return;
}
@@ -283,19 +197,10 @@ class ServerManagerView {
if (this.activeTabIndex === index) {
return;
} else if (hideOldTab) {
// If old tab is functional tab Settings, remove focus from the settings icon at sidebar bottom
if (this.tabs[this.activeTabIndex].props.role === 'function' && this.tabs[this.activeTabIndex].props.name === 'Settings') {
this.$settingsButton.classList.remove('active');
}
this.tabs[this.activeTabIndex].deactivate();
}
}
try {
this.tabs[index].webview.canGoBackButton();
} catch (err) {
}
this.activeTabIndex = index;
this.tabs[index].activate();
@@ -303,27 +208,6 @@ class ServerManagerView {
tabs: this.tabs,
activeTabIndex: this.activeTabIndex
});
ipcRenderer.on('toggle-sidebar', (event, state) => {
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-sidebar', state);
});
ipcRenderer.on('toogle-silent', (event, state) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
try {
webview.setAudioMuted(state);
} catch (err) {
// webview is not ready yet
webview.addEventListener('dom-ready', () => {
webview.isAudioMuted();
});
}
});
});
}
destroyTab(name, index) {
@@ -351,24 +235,16 @@ class ServerManagerView {
// Clear DOM elements
this.$tabsContainer.innerHTML = '';
this.$webviewsContainer.innerHTML = '';
// Destroy shortcuts
ipcRenderer.send('local-shortcuts', false);
}
reloadView() {
// Save and remember the index of last active tab so that we can use it later
const lastActiveTab = this.tabs[this.activeTabIndex].props.index;
ConfigUtil.setConfigItem('lastActiveTab', lastActiveTab);
// Destroy the current view and re-initiate it
this.destroyView();
this.initTabs();
}
// This will trigger when pressed CTRL/CMD + R [WIP]
// It won't reload the current view properly when you add/delete a server.
reloadCurrentView() {
this.$reloadButton.click();
}
updateBadge() {
let messageCountAll = 0;
for (let i = 0; i < this.tabs.length; i++) {
@@ -384,9 +260,9 @@ class ServerManagerView {
toggleSidebar(show) {
if (show) {
this.$sidebar.classList.remove('sidebar-hide');
this.$sidebar.classList.remove('hidden');
} else {
this.$sidebar.classList.add('sidebar-hide');
this.$sidebar.classList.add('hidden');
}
}
@@ -419,9 +295,7 @@ class ServerManagerView {
ipcRenderer.on('open-about', this.openAbout.bind(this));
ipcRenderer.on('reload-viewer', this.reloadView.bind(this, this.tabs[this.activeTabIndex].props.index));
ipcRenderer.on('reload-current-viewer', this.reloadCurrentView.bind(this));
ipcRenderer.on('reload-viewer', this.reloadView.bind(this));
ipcRenderer.on('hard-reload', () => {
ipcRenderer.send('reload-full-app');
@@ -432,7 +306,7 @@ class ServerManagerView {
});
ipcRenderer.on('switch-server-tab', (event, index) => {
this.activateLastTab(index);
this.activateTab(index);
});
ipcRenderer.on('reload-proxy', (event, showAlert) => {
@@ -456,18 +330,6 @@ class ServerManagerView {
this.$fullscreenPopup.classList.remove('show');
});
ipcRenderer.on('focus-webview-with-id', (event, webviewId) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
const currentId = webview.getWebContents().id;
const tabId = webview.getAttribute('data-tab-id');
const concurrentTab = document.querySelector(`div[data-tab-id="${tabId}"]`);
if (currentId === webviewId) {
concurrentTab.click();
}
});
});
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
// Create a canvas from unread messagecounts
function createOverlayIcon(messageCount) {
@@ -501,24 +363,9 @@ class ServerManagerView {
window.onload = () => {
const serverManagerView = new ServerManagerView();
const reconnectUtil = new ReconnectUtil(serverManagerView);
serverManagerView.init();
window.addEventListener('online', () => {
reconnectUtil.pollInternetAndReload();
serverManagerView.reloadView();
});
window.addEventListener('offline', () => {
reconnectUtil.clearState();
console.log('No internet connection, you are offline.');
});
// only start electron-connect (auto reload on change) when its ran
// from `npm run dev` or `gulp dev` and not from `npm start` when
// app is started `npm start` main process's proces.argv will have
// `--no-electron-connect`
const mainProcessArgv = remote.getGlobal('process').argv;
if (isDev && !mainProcessArgv.includes('--no-electron-connect')) {
const electronConnect = require('electron-connect');
electronConnect.client.create();
}
};

View File

@@ -0,0 +1,24 @@
'use strict';
const { remote } = require('electron');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const app = remote.app;
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
app.setAppUserModelId('org.zulip.zulip-electron');
const NativeNotification = window.Notification;
class baseNotification extends NativeNotification {
constructor(title, opts) {
opts.silent = ConfigUtil.getConfigItem('silent') || false;
// calling super without passing arguments will fail to constuct Notification
// and will result in no notification. It's a hack (not an ideal way) for deleting the window notification
ConfigUtil.getConfigItem('showNotification') ? super(title, opts) : super(); // eslint-disable-line no-unused-expressions
}
}
window.Notification = baseNotification;

View File

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

View File

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

View File

@@ -1,149 +0,0 @@
const { remote } = require('electron');
// Do not change this
const appId = 'org.zulip.zulip-electron';
const botsList = [];
let botsListLoaded = false;
// this function load list of bots from the server
// sync=True for a synchronous getJSON request
// in case botsList isn't already completely loaded when required in parseRely
function loadBots(sync = false) {
const { $ } = window;
botsList.length = 0;
if (sync) {
$.ajaxSetup({async: false});
}
$.getJSON('/json/users')
.done(data => {
const members = data.members;
members.forEach(membersRow => {
if (membersRow.is_bot) {
const bot = `@${membersRow.full_name}`;
const mention = `@**${bot.replace(/^@/, '')}**`;
botsList.push([bot, mention]);
}
});
botsListLoaded = true;
})
.fail(error => {
console.log('Request failed: ', error.responseText);
console.log('Request status: ', error.statusText);
});
if (sync) {
$.ajaxSetup({async: true});
}
}
function checkElements(...elements) {
let status = true;
elements.forEach(element => {
if (element === null || element === undefined) {
status = false;
}
});
return status;
}
function customReply(reply) {
// server does not support notification reply yet.
const buttonSelector = '.messagebox #send_controls button[type=submit]';
const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content';
const textarea = document.querySelector('#compose-textarea');
const messagebox = document.querySelector(messageboxSelector);
const sendButton = document.querySelector(buttonSelector);
// sanity check for old server versions
const elementsExists = checkElements(textarea, messagebox, sendButton);
if (!elementsExists) {
return;
}
textarea.value = reply;
messagebox.click();
sendButton.click();
}
const currentWindow = remote.getCurrentWindow();
const webContents = remote.getCurrentWebContents();
const webContentsId = webContents.id;
// this function will focus the server that sent
// the notification. Main function implemented in main.js
function focusCurrentServer() {
currentWindow.send('focus-webview-with-id', webContentsId);
}
// this function parses the reply from to notification
// making it easier to reply from notification eg
// @username in reply will be converted to @**username**
// #stream in reply will be converted to #**stream**
// bot mentions are not yet supported
function parseReply(reply) {
const usersDiv = document.querySelectorAll('#user_presences li');
const streamHolder = document.querySelectorAll('#stream_filters li');
const users = [];
const streams = [];
usersDiv.forEach(userRow => {
const anchor = userRow.querySelector('span a');
if (anchor !== null) {
const user = `@${anchor.textContent.trim()}`;
const mention = `@**${user.replace(/^@/, '')}**`;
users.push([user, mention]);
}
});
streamHolder.forEach(stream => {
const streamAnchor = stream.querySelector('div a');
if (streamAnchor !== null) {
const streamName = `#${streamAnchor.textContent.trim()}`;
const streamMention = `#**${streamName.replace(/^#/, '')}**`;
streams.push([streamName, streamMention]);
}
});
users.forEach(([user, mention]) => {
if (reply.includes(user)) {
const regex = new RegExp(user, 'g');
reply = reply.replace(regex, mention);
}
});
streams.forEach(([stream, streamMention]) => {
const regex = new RegExp(stream, 'g');
reply = reply.replace(regex, streamMention);
});
// If botsList isn't completely loaded yet, make a synchronous getJSON request for list
if (botsListLoaded === false) {
loadBots(true);
}
// Iterate for every bot name and replace in reply
// @botname with @**botname**
botsList.forEach(([bot, mention]) => {
if (reply.includes(bot)) {
const regex = new RegExp(bot, 'g');
reply = reply.replace(regex, mention);
}
});
reply = reply.replace(/\\n/, '\n');
return reply;
}
function setupReply(id) {
const { narrow } = window;
narrow.by_subject(id, { trigger: 'notification' });
}
module.exports = {
appId,
checkElements,
customReply,
parseReply,
setupReply,
focusCurrentServer,
loadBots
};

View File

@@ -1,27 +0,0 @@
'use strict';
const {
remote: { app }
} = require('electron');
const DefaultNotification = require('./default-notification');
const { appId, loadBots } = require('./helpers');
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
app.setAppUserModelId(appId);
window.Notification = DefaultNotification;
if (process.platform === 'darwin') {
const DarwinNotification = require('./darwin-notifications');
window.Notification = DarwinNotification;
}
window.addEventListener('load', () => {
// Call this function only when user is logged in
// eslint-disable-next-line no-undef, camelcase
if (page_params.realm_uri) {
loadBots();
}
});

View File

@@ -45,7 +45,7 @@ class BadgeSettings {
updateOverlayIcon(messageCount, mainWindow) {
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(ConfigUtil.getConfigItem('flashTaskbarOnMessage'));
mainWindow.flashFrame(true);
}
if (messageCount === 0) {
mainWindow.setOverlayIcon(null, '');

View File

@@ -19,20 +19,14 @@ class BaseSection extends BaseComponent {
generateOptionTemplate(settingOption) {
if (settingOption) {
return `
<div class="action">
<div class="switch">
<input class="toggle toggle-round" type="checkbox" checked>
<label></label>
</div>
<div class="action green">
<span>On</span>
</div>
`;
} else {
return `
<div class="action">
<div class="switch">
<input class="toggle toggle-round" type="checkbox">
<label></label>
</div>
<div class="action red">
<span>Off</span>
</div>
`;
}

View File

@@ -1,37 +0,0 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
const shell = require('electron').shell;
class CreateOrganziation extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="setting-row">
<div class="setting-description">
<span id="open-create-org-link">Or create a new organization on zulipchat.com<i class="material-icons open-tab-button">open_in_new</i></span>
</div>
<div class="setting-control"></div>
</div>
`;
}
init() {
this.props.$root.innerHTML = this.template();
this.openCreateNewOrgExternalLink();
}
openCreateNewOrgExternalLink() {
const link = 'https://zulipchat.com/beta/';
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
}
module.exports = CreateOrganziation;

View File

@@ -1,10 +1,11 @@
'use strict';
const path = require('path');
const { ipcRenderer, remote } = require('electron');
const { ipcRenderer } = require('electron');
const { app, dialog } = require('electron').remote;
const fs = require('fs-extra');
const { app, dialog } = remote;
const currentBrowserWindow = remote.getCurrentWindow();
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
@@ -24,17 +25,13 @@ class GeneralSection extends BaseSection {
<div class="setting-control"></div>
</div>
<div class="setting-row" id="sidebar-option">
<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+S</span>)</div>
<div class="setting-description">Show sidebar (<span class="code">CmdOrCtrl+S</span>)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="badge-option">
<div class="setting-description">Show app unread badge</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="flash-taskbar-option" style= "display:${process.platform === 'win32' ? '' : 'none'}">
<div class="setting-description">Flash taskbar on new message</div>
<div class="setting-control"></div>
</div>
<div class="setting-description">Show app unread badge</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Desktop Notification</div>
<div class="settings-card">
@@ -53,6 +50,10 @@ class GeneralSection extends BaseSection {
<div class="setting-description">Get beta updates</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="autoupdate-option">
<div class="setting-description">Automatically install new updates</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Functionality</div>
<div class="settings-card">
@@ -60,39 +61,13 @@ class GeneralSection extends BaseSection {
<div class="setting-description">Start app at login</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="start-minimize-option">
<div class="setting-description">Always start minimized</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="enable-spellchecker-option">
<div class="setting-description">Enable Spellchecker (requires restart)</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Add custom CSS</div>
<div class="settings-card">
<div class="setting-row" id="add-custom-css">
<div class="setting-description">
This will inject the selected css stylesheet in all the added accounts
</div>
<button class="custom-css-button blue">Add</button>
</div>
<div class="setting-row" id="remove-custom-css">
<div class="setting-description">
<div class="selected-css-path" id="custom-css-path">${ConfigUtil.getConfigItem('customCSS')}</div>
</div>
<div class="action red" id="css-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
<div class="title">Reset Application Data</div>
<div class="settings-card">
<div class="setting-row" id="resetdata-option">
<div class="setting-description">This will delete all application data including all added accounts and preferences
</div>
<button class="reset-data-button blue">Reset App Data</button>
<button class="reset-data-button green">Reset App Data</button>
</div>
</div>
</div>
@@ -103,23 +78,13 @@ class GeneralSection extends BaseSection {
this.props.$root.innerHTML = this.template();
this.updateTrayOption();
this.updateBadgeOption();
this.updateSilentOption();
this.updateUpdateOption();
this.updateAutoUpdateOption();
this.updateSilentOption();
this.updateSidebarOption();
this.updateStartAtLoginOption();
this.updateResetDataOption();
this.showDesktopNotification();
this.enableSpellchecker();
this.minimizeOnStart();
this.addCustomCSS();
this.showCustomCSSPath();
this.removeCustomCSS();
// Platform specific settings
// Flashing taskbar on Windows
if (process.platform === 'win32') {
this.updateFlashTaskbar();
}
}
updateTrayOption() {
@@ -148,18 +113,6 @@ class GeneralSection extends BaseSection {
});
}
updateFlashTaskbar() {
this.generateSettingOption({
$element: document.querySelector('#flash-taskbar-option .setting-control'),
value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('flashTaskbarOnMessage');
ConfigUtil.setConfigItem('flashTaskbarOnMessage', newValue);
this.updateFlashTaskbar();
}
});
}
updateUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#betaupdate-option .setting-control'),
@@ -172,6 +125,18 @@ class GeneralSection extends BaseSection {
});
}
updateAutoUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#autoupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('autoUpdate', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('autoUpdate');
ConfigUtil.setConfigItem('autoUpdate', newValue);
this.updateAutoUpdateOption();
}
});
}
updateSilentOption() {
this.generateSettingOption({
$element: document.querySelector('#silent-option .setting-control'),
@@ -180,7 +145,6 @@ class GeneralSection extends BaseSection {
const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption();
currentBrowserWindow.send('toogle-silent', newValue);
}
});
}
@@ -223,18 +187,6 @@ class GeneralSection extends BaseSection {
});
}
enableSpellchecker() {
this.generateSettingOption({
$element: document.querySelector('#enable-spellchecker-option .setting-control'),
value: ConfigUtil.getConfigItem('enableSpellchecker', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('enableSpellchecker');
ConfigUtil.setConfigItem('enableSpellchecker', newValue);
this.enableSpellchecker();
}
});
}
clearAppDataDialog() {
const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
const getAppPath = path.join(app.getPath('appData'), app.getName());
@@ -253,22 +205,6 @@ class GeneralSection extends BaseSection {
});
}
customCssDialog() {
const showDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'CSS file', extensions: ['css'] }]
};
dialog.showOpenDialog(showDialogOptions, selectedFile => {
if (selectedFile) {
ConfigUtil.setConfigItem('customCSS', selectedFile[0]);
ipcRenderer.send('forward-message', 'hard-reload');
}
});
}
updateResetDataOption() {
const resetDataButton = document.querySelector('#resetdata-option .reset-data-button');
resetDataButton.addEventListener('click', () => {
@@ -276,40 +212,6 @@ class GeneralSection extends BaseSection {
});
}
minimizeOnStart() {
this.generateSettingOption({
$element: document.querySelector('#start-minimize-option .setting-control'),
value: ConfigUtil.getConfigItem('startMinimized', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('startMinimized');
ConfigUtil.setConfigItem('startMinimized', newValue);
this.minimizeOnStart();
}
});
}
addCustomCSS() {
const customCSSButton = document.querySelector('#add-custom-css .custom-css-button');
customCSSButton.addEventListener('click', () => {
this.customCssDialog();
});
}
showCustomCSSPath() {
if (!ConfigUtil.getConfigItem('customCSS')) {
const cssPATH = document.getElementById('remove-custom-css');
cssPATH.style.display = 'none';
}
}
removeCustomCSS() {
const removeCSSButton = document.getElementById('css-delete-action');
removeCSSButton.addEventListener('click', () => {
ConfigUtil.setConfigItem('customCSS');
ipcRenderer.send('forward-message', 'hard-reload');
});
}
}
module.exports = GeneralSection;

View File

@@ -34,7 +34,7 @@ class NetworkSection extends BaseSection {
<input class="setting-input-value" placeholder="e.g. foobar.com"/>
</div>
<div class="setting-row">
<div class="action blue" id="proxy-save-action">
<div class="action green" id="proxy-save-action">
<i class="material-icons">check_box</i>
<span>Save</span>
</div>

View File

@@ -13,14 +13,13 @@ class NewServerForm extends BaseComponent {
return `
<div class="settings-card">
<div class="server-info-right">
<div class="title">URL of Zulip organization</div>
<div class="server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or chat.your-organization.com"/>
<input class="setting-input-value" autofocus placeholder="Entert the URL of your Zulip organization..."/>
</div>
<div class="server-info-row">
<div class="action blue server-save-action">
<i class="material-icons">add_box</i>
<span>Add</span>
<div class="action green server-save-action">
<i class="material-icons">check_box</i>
<span>Save</span>
</div>
</div>
</div>
@@ -43,13 +42,11 @@ class NewServerForm extends BaseComponent {
}
submitFormHandler() {
this.$saveServerButton.children[1].innerHTML = 'Adding...';
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
DomainUtil.addDomain(serverConf).then(() => {
this.props.onChange(this.props.index);
});
}, errorMessage => {
this.$saveServerButton.children[1].innerHTML = 'Add';
alert(errorMessage);
});
}

View File

@@ -1,7 +1,7 @@
'use strict';
const BaseComponent = require(__dirname + '/js/components/base.js');
const { ipcRenderer } = require('electron');
const {ipcRenderer} = require('electron');
const Nav = require(__dirname + '/js/pages/preference/nav.js');
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
@@ -69,27 +69,10 @@ class PreferenceView extends BaseComponent {
window.location.hash = `#${navItem}`;
}
// Handle toggling and reflect changes in preference page
handleToggle(elementName, state) {
const inputSelector = `#${elementName} .action .switch input`;
const input = document.querySelector(inputSelector);
if (input) {
input.checked = state;
}
}
registerIpcs() {
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
this.handleNavigation(navItem);
});
ipcRenderer.on('toggle-sidebar', (event, state) => {
this.handleToggle('sidebar-option', state);
});
ipcRenderer.on('toggletray', (event, state) => {
this.handleToggle('tray-option', state);
});
}
}

View File

@@ -1,6 +1,6 @@
'use strict';
const { dialog } = require('electron').remote;
const { ipcRenderer } = require('electron');
const {dialog} = require('electron').remote;
const {ipcRenderer} = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
@@ -20,7 +20,7 @@ class ServerInfoForm extends BaseComponent {
<div class="server-info-right">
<div class="server-info-row">
<span class="server-info-alias">${this.props.server.alias}</span>
<i class="material-icons open-tab-button">open_in_new</i>
<i class="material-icons open-tab-button">open_in_new</i>
</div>
<div class="server-info-row">
<input class="setting-input-value" disabled value="${this.props.server.url}"/>
@@ -55,7 +55,7 @@ class ServerInfoForm extends BaseComponent {
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure you want to disconnect this organization?'
message: 'Are you sure you want to delete this server?'
}, response => {
if (response === 0) {
DomainUtil.removeDomain(this.props.index);

View File

@@ -4,7 +4,6 @@ const BaseSection = require(__dirname + '/base-section.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js');
const NewServerForm = require(__dirname + '/new-server-form.js');
const CreateOrganziation = require(__dirname + '/create-new-org.js');
class ServersSection extends BaseSection {
constructor(props) {
@@ -15,11 +14,10 @@ class ServersSection extends BaseSection {
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">Register or login to a Zulip organization to get started</div>
<div class="title">Add Server</div>
<div id="new-server-container"></div>
<div class="title" id="existing-servers"></div>
<div id="server-info-container"></div>
<div id="create-organization-container"></div>
</div>
`;
}
@@ -38,14 +36,11 @@ class ServersSection extends BaseSection {
this.$newServerContainer = document.getElementById('new-server-container');
this.$newServerButton = document.getElementById('new-server-action');
this.$serverInfoContainer.innerHTML = servers.length ? '' : '';
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
// Show Existing servers if servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Connected organizations';
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing Servers';
this.initNewServerForm();
this.$createOrganizationContainer = document.getElementById('create-organization-container');
this.initCreateNewOrganization();
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
@@ -56,12 +51,6 @@ class ServersSection extends BaseSection {
}
}
initCreateNewOrganization() {
new CreateOrganziation({
$root: this.$createOrganizationContainer
}).init();
}
initNewServerForm() {
new NewServerForm({
$root: this.$newServerContainer,

View File

@@ -109,7 +109,7 @@ class ShortcutsSection extends BaseSection {
<td>Enter Full Screen</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>+</kbd></td>
<td><kbd>${userOSKey}</kbd><kbd>=</kbd></td>
<td>Zoom In</td>
</tr>
<tr>

View File

@@ -1,10 +1,7 @@
'use strict';
const { ipcRenderer } = require('electron');
const SetupSpellChecker = require('./spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const { spellChecker } = require('./spellchecker');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
@@ -35,15 +32,8 @@ process.once('loaded', () => {
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', () => {
// Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// Init spellchecker
spellChecker();
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
@@ -53,10 +43,3 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
});
// Clean up spellchecker events after you navigate away from this page;
// otherwise, you may experience errors
window.addEventListener('beforeunload', () => {
SetupSpellChecker.unsubscribeSpellChecker();
});

View File

@@ -1,56 +1,29 @@
'use strict';
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
function spellChecker() {
// Implement spellcheck using electron api
window.spellCheckHandler = new SpellCheckHandler();
window.spellCheckHandler.attachToInput();
class SetupSpellChecker {
init() {
if (ConfigUtil.getConfigItem('enableSpellchecker')) {
this.enableSpellChecker();
}
this.enableContextMenu();
}
// Start off as US English
window.spellCheckHandler.switchLanguage('en-US');
enableSpellChecker() {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
console.log(err);
}
}
const contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler);
const contextMenuListener = new ContextMenuListener(info => {
contextMenuBuilder.showPopupMenu(info);
});
enableContextMenu() {
if (this.SpellCheckHandler) {
this.SpellCheckHandler.attachToInput();
const userLanguage = ConfigUtil.getConfigItem('spellcheckerLanguage');
// eslint-disable-next-line no-unused-expressions
process.platform === 'darwin' ?
// On macOS, spellchecker fails to auto-detect the lanugage user is typing in
// that's why we need to mention it explicitly
this.SpellCheckHandler.switchLanguage(userLanguage) :
// On Linux and Windows, spellchecker can automatically detects the language the user is typing in
// and silently switches on the fly; thus we can start off as US English
this.SpellCheckHandler.switchLanguage('en-US');
}
const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler);
this.contextMenuListener = new ContextMenuListener(info => {
contextMenuBuilder.showPopupMenu(info);
});
}
unsubscribeSpellChecker() {
// eslint-disable-next-line no-undef
if (this.SpellCheckHandler) {
this.SpellCheckHandler.unsubscribe();
}
if (this.contextMenuListener) {
this.contextMenuListener.unsubscribe();
}
}
// Clean up events after you navigate away from this page;
// otherwise, you may experience errors
window.addEventListener('beforeunload', () => {
// eslint-disable-next-line no-undef
spellCheckHandler.unsubscribe();
contextMenuListener.unsubscribe();
});
}
module.exports = new SetupSpellChecker();
module.exports = {
spellChecker
};

View File

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

View File

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

View File

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

View File

@@ -5,12 +5,6 @@ const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
const Logger = require('./logger-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
let instance = null;
@@ -99,7 +93,8 @@ class DomainUtil {
checkDomain(domain, silent = false) {
if (!silent && this.duplicateDomain(domain)) {
// Do not check duplicate in silent mode
return Promise.reject('This server has been added.');
alert('This server has been added.');
return;
}
domain = this.formatUrl(domain);
@@ -115,28 +110,17 @@ class DomainUtil {
return new Promise((resolve, reject) => {
request(checkDomain, (error, response) => {
const certsError =
[
'Error: self signed certificate',
'Error: unable to verify the first certificate',
'Error: unable to get local issuer certificate'
['Error: self signed certificate',
'Error: unable to verify the first certificate'
];
// If the domain contains following strings we just bypass the server
const whitelistDomains = [
'zulipdev.org'
];
// make sure that error is a error or string not undefined
// so validation does not throw error.
error = error || '';
if (!error && response.statusCode < 400) {
if (!error && response.statusCode !== 404) {
// Correct
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError.indexOf(error.toString()) >= 0) {
} else if (certsError.indexOf(error.toString()) >= 0) {
if (silent) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
@@ -168,9 +152,7 @@ class DomainUtil {
});
}
} else {
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \
\n(1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server`;
reject(invalidZulipServerError);
reject('Not a valid Zulip server');
}
});
});
@@ -184,9 +166,7 @@ class DomainUtil {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
resolve({
// Some Zulip Servers use absolute URL for server icon whereas others use relative URL
// Following check handles both the cases
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
icon: data.realm_uri + data.realm_icon,
url: data.realm_uri,
alias: data.realm_name
});
@@ -235,23 +215,7 @@ class DomainUtil {
}
reloadDB() {
const domainJsonPath = path.join(app.getPath('userData'), '/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(domainJsonPath)) {
fs.unlinkSync(domainJsonPath);
dialog.showErrorBox(
'Error saving new organization',
'There seems to be error while saving new organization, ' +
'you may have to re-add your previous organizations back.'
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
}
}
this.db = new JsonDB(domainJsonPath, true, true);
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
}
generateFilePath(url) {

View File

@@ -1,74 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const process = require('process');
const remote =
process.type === 'renderer' ? require('electron').remote : require('electron');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const logger = new Logger({
file: 'linux-update-util.log',
timestamp: true
});
/* To make the util runnable in both main and renderer process */
const { dialog, app } = remote;
let instance = null;
class LinuxUpdateUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
return instance;
}
getUpdateItem(key, defaultValue = null) {
this.reloadDB();
const value = this.db.getData('/')[key];
if (value === undefined) {
this.setUpdateItem(key, defaultValue);
return defaultValue;
} else {
return value;
}
}
setUpdateItem(key, value) {
this.db.push(`/${key}`, value, true);
this.reloadDB();
}
removeUpdateItem(key) {
this.db.delete(`/${key}`);
this.reloadDB();
}
reloadDB() {
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/updates.json');
try {
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(linuxUpdateJsonPath)) {
fs.unlinkSync(linuxUpdateJsonPath);
dialog.showErrorBox(
'Error saving update notifications.',
'We encountered error while saving update notifications.'
);
logger.error('Error while JSON parsing updates.json: ');
logger.error(err);
}
}
this.db = new JsonDB(linuxUpdateJsonPath, true, true);
}
}
module.exports = new LinuxUpdateUtil();

View File

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

View File

@@ -1,52 +0,0 @@
const isOnline = require('is-online');
class ReconnectUtil {
constructor(serverManagerView) {
this.serverManagerView = serverManagerView;
this.alreadyReloaded = false;
}
clearState() {
this.alreadyReloaded = false;
}
pollInternetAndReload() {
const pollInterval = setInterval(() => {
this._checkAndReload()
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
}, 1500);
}
_checkAndReload() {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
isOnline()
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
console.log('You\'re back online.');
return resolve(true);
}
console.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
errMsgHolder.innerHTML = `
<div>You internet connection does't seem to work properly!</div>
</div>Verify that it works and then click try again.</div>`;
return resolve(false);
});
} else {
return resolve(true);
}
});
}
}
module.exports = ReconnectUtil;

View File

@@ -13,14 +13,13 @@
<div class="popup">
<span class="popuptext hidden" id="fullscreen-popup"></span>
</div>
<div id="sidebar" class="toggle-sidebar">
<div id="sidebar">
<div id="view-controls-container">
<div id="tabs-container"></div>
<div id="add-tab" class="tab functional-tab">
<div class="server-tab" id="add-action">
<i class="material-icons">add</i>
</div>
<span id="add-server-tooltip" style="display:none">Add organization</span>
</div>
</div>
<div id="actions-container">
@@ -28,10 +27,6 @@
<i class="material-icons md-48">refresh</i>
<span id="reload-tooltip" style="display:none">Reload</span>
</div>
<div class="action-button disable" id="back-action">
<i class="material-icons md-48">arrow_back</i>
<span id="back-tooltip" style="display:none">Go Back</span>
</div>
<div class="action-button" id="settings-action">
<i class="material-icons md-48">settings</i>
<span id="setting-tooltip" style="display:none">Settings</span>
@@ -44,4 +39,5 @@
</div>
</body>
<script src="js/main.js"></script>
</html>

View File

@@ -12,5 +12,5 @@
<div id="settings-container"></div>
</div>
</body>
<script src="js/pages/preference/preference.js"></script>
<script src="js/pages/preference/preference.js"></script>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "За Зулип",
"Actual Size": "Реален размер",
"Back": "обратно",
"Close": "Близо",
"Copy": "копие",
"Cut": "Разрез",
"Delete": "Изтрий",
"Desktop App Settings": "Настройки за настолни приложения",
"Edit": "редактиране",
"File": "досие",
"Forward": "напред",
"Hard Reload": "Трудно презареждане",
"Help": "Помогне",
"History": "история",
"Keyboard Shortcuts": "Комбинация от клавиши",
"Log Out": "Излез от профила си",
"Minimize": "Минимизиране",
"Paste": "паста",
"Paste and Match Style": "Поставяне и съвпадение на стила",
"Quit": "напускам",
"Redo": "ремонтирам",
"Reload": "Презареди",
"Report an issue...": "Подаване на сигнал за проблем ...",
"Reset App Settings": "Нулирайте настройките на приложението",
"Select All": "Избери всички",
"Show App Logs": "Показване на регистрационните файлове на приложения",
"Toggle DevTools for Active Tab": "Превключете DevTools за активен раздел",
"Toggle DevTools for Zulip App": "Превключете DevTools за Zulip App",
"Toggle Full Screen": "Превключване на цял екран",
"Toggle Sidebar": "Превключване на страничната лента",
"Toggle Tray Icon": "Превключване на иконата на тавата",
"Undo": "премахвам",
"View": "изглед",
"Window": "прозорец",
"Zoom In": "Увеличавам",
"Zoom Out": "Отдалечавам",
"Zulip Help": "Помощ за Zulip"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "O společnosti Zulip",
"Actual Size": "Aktuální velikost",
"Back": "Zadní",
"Close": "Zavřít",
"Copy": "kopírovat",
"Cut": "Střih",
"Delete": "Vymazat",
"Desktop App Settings": "Nastavení aplikace Desktop",
"Edit": "Upravit",
"File": "Soubor",
"Forward": "Vpřed",
"Hard Reload": "Hard Reload",
"Help": "Pomoc",
"History": "Dějiny",
"Keyboard Shortcuts": "Klávesové zkratky",
"Log Out": "Odhlásit se",
"Minimize": "Minimalizujte",
"Paste": "Vložit",
"Paste and Match Style": "Vložit a přizpůsobit styl",
"Quit": "Přestat",
"Redo": "Předělat",
"Reload": "Znovu načíst",
"Report an issue...": "Nahlásit problém...",
"Reset App Settings": "Obnovit nastavení aplikace",
"Select All": "Vybrat vše",
"Show App Logs": "Zobrazit protokoly aplikace",
"Toggle DevTools for Active Tab": "Přepínač DevTools pro aktivní kartu",
"Toggle DevTools for Zulip App": "Přepnout nástroj DevTools pro aplikaci Zulip App",
"Toggle Full Screen": "Přepnout na celou obrazovku",
"Toggle Sidebar": "Přepnout postranní panel",
"Toggle Tray Icon": "Přepnout ikonu zásobníku",
"Undo": "vrátit",
"View": "Pohled",
"Window": "Okno",
"Zoom In": "Přiblížit",
"Zoom Out": "Oddálit",
"Zulip Help": "Zulip Nápověda"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "Über Zulip",
"Actual Size": "Tatsächliche Größe",
"Back": "Zurück",
"Close": "Schließen",
"Copy": "Kopieren",
"Cut": "Schnitt",
"Delete": "Löschen",
"Desktop App Settings": "Desktop App Einstellungen",
"Edit": "Bearbeiten",
"File": "Datei",
"Forward": "Nach vorne",
"Hard Reload": "Hard Reload",
"Help": "Hilfe",
"History": "Geschichte",
"Keyboard Shortcuts": "Tastatürkürzel",
"Log Out": "Ausloggen",
"Minimize": "Minimieren",
"Paste": "Einfügen",
"Paste and Match Style": "Einfügen und Anpassen von Stilen",
"Quit": "Verlassen",
"Redo": "Wiederholen",
"Reload": "Neu laden",
"Report an issue...": "Ein Problem melden...",
"Reset App Settings": "App-Einstellungen zurücksetzen",
"Select All": "Wählen Sie Alle",
"Show App Logs": "App-Logs anzeigen",
"Toggle DevTools for Active Tab": "Schalten Sie DevTools für Active Tab ein",
"Toggle DevTools for Zulip App": "Umschalt DevTools für Zulip App",
"Toggle Full Screen": "Vollbild umschalten",
"Toggle Sidebar": "Toggle Seitenleiste",
"Toggle Tray Icon": "Toggle Tray-Symbol",
"Undo": "Rückgängig machen",
"View": "Aussicht",
"Window": "Fenster",
"Zoom In": "Hineinzoomen",
"Zoom Out": "Rauszoomen",
"Zulip Help": "Zulip Hilfe"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "About Zulip",
"Actual Size": "Actual Size",
"Back": "Back",
"Close": "Close",
"Copy": "Copy",
"Cut": "Cut",
"Delete": "Delete",
"Desktop App Settings": "Desktop App Settings",
"Edit": "Edit",
"File": "File",
"Forward": "Forward",
"Hard Reload": "Hard Reload",
"Help": "Help",
"History": "History",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Log Out": "Log Out",
"Minimize": "Minimize",
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Quit": "Quit",
"Redo": "Redo",
"Reload": "Reload",
"Report an issue...": "Report an issue...",
"Reset App Settings": "Reset App Settings",
"Select All": "Select All",
"Show App Logs": "Show App Logs",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Undo": "Undo",
"View": "View",
"Window": "Window",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "Acerca de Zulip",
"Actual Size": "Tamaño real",
"Back": "Espalda",
"Close": "Cerca",
"Copy": "Dupdo",
"Cut": "Cortar",
"Delete": "Borrar",
"Desktop App Settings": "Configuración de la aplicación de escritorio",
"Edit": "Editar",
"File": "Archivo",
"Forward": "Adelante",
"Hard Reload": "Recargar duro",
"Help": "Ayuda",
"History": "Historia",
"Keyboard Shortcuts": "Atajos de teclado",
"Log Out": "Cerrar sesión",
"Minimize": "Minimizar",
"Paste": "Pegar",
"Paste and Match Style": "Pegar y combinar estilo",
"Quit": "Dejar",
"Redo": "Rehacer",
"Reload": "Recargar",
"Report an issue...": "Reportar un problema...",
"Reset App Settings": "Restablecer configuraciones",
"Select All": "Seleccionar todo",
"Show App Logs": "Mostrar registros de aplicaciones",
"Toggle DevTools for Active Tab": "Alternar DevTools para Active Tab",
"Toggle DevTools for Zulip App": "Alternar DevTools para la aplicación Zulip",
"Toggle Full Screen": "Alternar pantalla completa",
"Toggle Sidebar": "Alternar barra lateral",
"Toggle Tray Icon": "Alternar el icono de la bandeja",
"Undo": "Deshacer",
"View": "Ver",
"Window": "Ventana",
"Zoom In": "Acercarse",
"Zoom Out": "Disminuir el zoom",
"Zulip Help": "Ayuda de Zulip"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "À propos de Zulip",
"Actual Size": "Taille actuelle",
"Back": "Arrière",
"Close": "Fermer",
"Copy": "Copie",
"Cut": "Couper",
"Delete": "Effacer",
"Desktop App Settings": "Paramètres de l'application de bureau",
"Edit": "modifier",
"File": "Fichier",
"Forward": "Vers l'avant",
"Hard Reload": "Rechargement dur",
"Help": "Aidez-moi",
"History": "Histoire",
"Keyboard Shortcuts": "Raccourcis clavier",
"Log Out": "Connectez - Out",
"Minimize": "Minimiser",
"Paste": "Coller",
"Paste and Match Style": "Coller et assortir le style",
"Quit": "Quitter",
"Redo": "Refaire",
"Reload": "Recharger",
"Report an issue...": "Signaler un problème...",
"Reset App Settings": "Réinitialiser les paramètres",
"Select All": "Tout sélectionner",
"Show App Logs": "Afficher les journaux d'applications",
"Toggle DevTools for Active Tab": "Basculer DevTools pour l'onglet actif",
"Toggle DevTools for Zulip App": "Basculer DevTools pour l'application Zulip",
"Toggle Full Screen": "Basculer en plein écran",
"Toggle Sidebar": "Basculer la barre latérale",
"Toggle Tray Icon": "Toggle Tray Icône",
"Undo": "annuler",
"View": "Vue",
"Window": "Fenêtre",
"Zoom In": "Agrandir",
"Zoom Out": "Dézoomer",
"Zulip Help": "Aide Zulip"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "Zulip के बारे में",
"Actual Size": "वास्तविक आकार",
"Back": "वापस",
"Close": "बंद करे",
"Copy": "प्रतिलिपि",
"Cut": "कट गया",
"Delete": "हटाना",
"Desktop App Settings": "डेस्कटॉप ऐप सेटिंग्स",
"Edit": "संपादित करें",
"File": "फ़ाइल",
"Forward": "आगे",
"Hard Reload": "कठिन पुन: लोड करें",
"Help": "मदद",
"History": "इतिहास",
"Keyboard Shortcuts": "कुंजीपटल अल्प मार्ग",
"Log Out": "लोग आउट",
"Minimize": "छोटा करना",
"Paste": "चिपकाएं",
"Paste and Match Style": "चिपकाएं और शैली का मिलान करें",
"Quit": "छोड़ना",
"Redo": "फिर से करना",
"Reload": "सीमा से अधिक लादना",
"Report an issue...": "मामले की रिपोर्ट करें...",
"Reset App Settings": "ऐप सेटिंग्स रीसेट करें",
"Select All": "सभी का चयन करे",
"Show App Logs": "ऐप लॉग दिखाएं",
"Toggle DevTools for Active Tab": "सक्रिय टैब के लिए DevTools टॉगल करें",
"Toggle DevTools for Zulip App": "Zulip ऐप के लिए DevTools टॉगल करें",
"Toggle Full Screen": "पूर्णस्क्रीन चालू करें",
"Toggle Sidebar": "साइडबार टॉगल करें",
"Toggle Tray Icon": "ट्रे आइकन टॉगल करें",
"Undo": "पूर्ववत करें",
"View": "राय",
"Window": "खिड़की",
"Zoom In": "ज़ूम इन",
"Zoom Out": "ज़ूम आउट",
"Zulip Help": "Zulip सहायता"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "A Zulipról",
"Actual Size": "Valódi méret",
"Back": "Hát",
"Close": "Bezárás",
"Copy": "Másolat",
"Cut": "Vágott",
"Delete": "Töröl",
"Desktop App Settings": "Asztali alkalmazások beállításai",
"Edit": "szerkesztése",
"File": "fájl",
"Forward": "Előre",
"Hard Reload": "Hard Reload",
"Help": "Segítség",
"History": "Történelem",
"Keyboard Shortcuts": "Gyorsbillentyűket",
"Log Out": "Kijelentkezés",
"Minimize": "Kis méret",
"Paste": "Paszta",
"Paste and Match Style": "Beillesztés és stílusok illesztése",
"Quit": "quit",
"Redo": "Újra",
"Reload": "Reload",
"Report an issue...": "Hibajelentés ...",
"Reset App Settings": "Az alkalmazás beállításainak visszaállítása",
"Select All": "Mindet kiválaszt",
"Show App Logs": "Alkalmazásnaplók megjelenítése",
"Toggle DevTools for Active Tab": "A DevTools aktiválása az Aktív laphoz",
"Toggle DevTools for Zulip App": "Kapcsolja a DevTools-ot a Zulip App-hoz",
"Toggle Full Screen": "Teljes képernyőre váltás",
"Toggle Sidebar": "Oldalsáv átkapcsolása",
"Toggle Tray Icon": "Tálcaikon átkapcsolása",
"Undo": "kibont",
"View": "Kilátás",
"Window": "Ablak",
"Zoom In": "Nagyítás",
"Zoom Out": "Kicsinyítés",
"Zulip Help": "Zulip Súgó"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "Tentang Zulip",
"Actual Size": "Ukuran sebenarnya",
"Back": "Kembali",
"Close": "Dekat",
"Copy": "Salinan",
"Cut": "Memotong",
"Delete": "Menghapus",
"Desktop App Settings": "Setelan Aplikasi Desktop",
"Edit": "Edit",
"File": "Mengajukan",
"Forward": "Meneruskan",
"Hard Reload": "Hard Reload",
"Help": "Membantu",
"History": "Sejarah",
"Keyboard Shortcuts": "Cara pintas keyboard",
"Log Out": "Keluar",
"Minimize": "Memperkecil",
"Paste": "Pasta",
"Paste and Match Style": "Tempel dan Cocokkan Gaya",
"Quit": "Berhenti",
"Redo": "Mengulangi",
"Reload": "Reload",
"Report an issue...": "Laporkan masalah ...",
"Reset App Settings": "Setel ulang Pengaturan Aplikasi",
"Select All": "Pilih Semua",
"Show App Logs": "Tampilkan log aplikasi",
"Toggle DevTools for Active Tab": "Toggle DevTools untuk Tab Aktif",
"Toggle DevTools for Zulip App": "Toggle DevTools untuk Aplikasi Zulip",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Undo": "Membuka",
"View": "Melihat",
"Window": "Jendela",
"Zoom In": "Perbesar",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "チューリップについて",
"Actual Size": "実際のサイズ",
"Back": "バック",
"Close": "閉じる",
"Copy": "コピー",
"Cut": "カット",
"Delete": "削除",
"Desktop App Settings": "デスクトップアプリケーションの設定",
"Edit": "編集",
"File": "ファイル",
"Forward": "フォワード",
"Hard Reload": "ハードリロード",
"Help": "助けて",
"History": "歴史",
"Keyboard Shortcuts": "キーボードショートカット",
"Log Out": "ログアウト",
"Minimize": "最小化する",
"Paste": "ペースト",
"Paste and Match Style": "スタイルの貼り付けと一致",
"Quit": "終了する",
"Redo": "やり直し",
"Reload": "リロード",
"Report an issue...": "問題を報告する...",
"Reset App Settings": "アプリの設定をリセットする",
"Select All": "すべて選択",
"Show App Logs": "アプリケーションログを表示する",
"Toggle DevTools for Active Tab": "DevTools for Activeタブを切り替える",
"Toggle DevTools for Zulip App": "Zulip AppのDevToolsの切り替え",
"Toggle Full Screen": "フルスクリーン切り替え",
"Toggle Sidebar": "サイドバーの切り替え",
"Toggle Tray Icon": "トレイアイコンを切り替える",
"Undo": "元に戻す",
"View": "ビュー",
"Window": "窓",
"Zoom In": "ズームイン",
"Zoom Out": "ズームアウトする",
"Zulip Help": "チューリップヘルプ"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "튤립 소개",
"Actual Size": "실제 크기",
"Back": "뒤로",
"Close": "닫기",
"Copy": "부",
"Cut": "절단",
"Delete": "지우다",
"Desktop App Settings": "데스크톱 앱 설정",
"Edit": "편집하다",
"File": "파일",
"Forward": "앞으로",
"Hard Reload": "하드 다시로드",
"Help": "도움",
"History": "역사",
"Keyboard Shortcuts": "키보드 단축키",
"Log Out": "로그 아웃",
"Minimize": "최소화",
"Paste": "풀",
"Paste and Match Style": "붙여 넣기 및 스타일 일치",
"Quit": "떠나다",
"Redo": "다시 하다",
"Reload": "다시로드",
"Report an issue...": "문제 신고 ...",
"Reset App Settings": "앱 설정 재설정",
"Select All": "모두 선택",
"Show App Logs": "앱 로그 표시",
"Toggle DevTools for Active Tab": "DevTools for Active Tab 토글",
"Toggle DevTools for Zulip App": "Zulip App 용 DevTools 토글",
"Toggle Full Screen": "전체 화면 토글",
"Toggle Sidebar": "사이드 바 전환",
"Toggle Tray Icon": "트레이 아이콘 토글",
"Undo": "끄르다",
"View": "전망",
"Window": "창문",
"Zoom In": "확대",
"Zoom Out": "축소",
"Zulip Help": "튤립 도움말"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "സുലിപ്",
"Actual Size": "യഥാർത്ഥ വലുപ്പം",
"Back": "തിരികെ",
"Close": "അടയ്ക്കുക",
"Copy": "പകർത്തുക",
"Cut": "മുറിക്കുക",
"Delete": "ഇല്ലാതാക്കുക",
"Desktop App Settings": "ഡെസ്ക്ടോപ്പ് അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങൾ",
"Edit": "എഡിറ്റുചെയ്യുക",
"File": "ഫയൽ",
"Forward": "മുന്നോട്ട്",
"Hard Reload": "ഹാർഡ് റീലോഡ്",
"Help": "സഹായിക്കൂ",
"History": "ചരിത്രം",
"Keyboard Shortcuts": "കീബോർഡ് കുറുക്കുവഴികൾ",
"Log Out": "ലോഗ് ഔട്ട്",
"Minimize": "ചെറുതാക്കുക",
"Paste": "പേസ്റ്റ്",
"Paste and Match Style": "ശൈലി ഒട്ടിക്കുകയും പൊരുത്തപ്പെടുത്തുകയും ചെയ്യുക",
"Quit": "പുറത്തുകടക്കുക",
"Redo": "വീണ്ടും ചെയ്യുക",
"Reload": "വീണ്ടും ലോഡുചെയ്യുക",
"Report an issue...": "ഒരു പ്രശ്നം റിപ്പോർട്ടുചെയ്യുക ...",
"Reset App Settings": "അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങൾ പുനഃസജ്ജമാക്കുക",
"Select All": "എല്ലാം തിരഞ്ഞെടുക്കുക",
"Show App Logs": "അപ്ലിക്കേഷൻ ലോഗുകൾ കാണിക്കുക",
"Toggle DevTools for Active Tab": "സജീവ ടാബിനായി DevTools ടോഗിൾ ചെയ്യുക",
"Toggle DevTools for Zulip App": "സുലിപ്പ് ആപ്ലിക്കേഷനായി DevTools ടോഗിൾ ചെയ്യുക",
"Toggle Full Screen": "പൂർണ്ണ സ്ക്രീൻ ടോഗിൾ ചെയ്യുക",
"Toggle Sidebar": "സൈഡ്ബാർ ടോഗിൾ ചെയ്യുക",
"Toggle Tray Icon": "ട്രേ ഐക്കൺ ടോഗിൾ ചെയ്യുക",
"Undo": "പഴയപടിയാക്കുക",
"View": "കാണുക",
"Window": "ജാലകം",
"Zoom In": "വലുതാക്കുക",
"Zoom Out": "സൂം ഔട്ട്",
"Zulip Help": "സുലിപ്പ് സഹായം"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "Over Zulip",
"Actual Size": "Daadwerkelijke grootte",
"Back": "Terug",
"Close": "Dichtbij",
"Copy": "Kopiëren",
"Cut": "Besnoeiing",
"Delete": "Verwijder",
"Desktop App Settings": "Desktop-app-instellingen",
"Edit": "Bewerk",
"File": "het dossier",
"Forward": "Vooruit",
"Hard Reload": "Moeilijke herladen",
"Help": "Helpen",
"History": "Geschiedenis",
"Keyboard Shortcuts": "Toetsenbord sneltoetsen",
"Log Out": "Uitloggen",
"Minimize": "verkleinen",
"Paste": "Plakken",
"Paste and Match Style": "Plak en match stijl",
"Quit": "ophouden",
"Redo": "Opnieuw doen",
"Reload": "vernieuwen",
"Report an issue...": "Een probleem melden...",
"Reset App Settings": "App-instellingen opnieuw instellen",
"Select All": "Selecteer alles",
"Show App Logs": "App-logs weergeven",
"Toggle DevTools for Active Tab": "DevTools voor actieve tabblad omschakelen",
"Toggle DevTools for Zulip App": "DevTools voor Zulip-app omschakelen",
"Toggle Full Screen": "Volledig scherm activeren",
"Toggle Sidebar": "Zijbalk verschuiven",
"Toggle Tray Icon": "Pictogram Lade wisselen",
"Undo": "ongedaan maken",
"View": "Uitzicht",
"Window": "Venster",
"Zoom In": "In zoomen",
"Zoom Out": "Uitzoomen",
"Zulip Help": "Zulip Help"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "O Zulip",
"Actual Size": "Rzeczywisty rozmiar",
"Back": "Plecy",
"Close": "Blisko",
"Copy": "Kopiuj",
"Cut": "Ciąć",
"Delete": "Kasować",
"Desktop App Settings": "Ustawienia aplikacji na komputer",
"Edit": "Edytować",
"File": "Plik",
"Forward": "Naprzód",
"Hard Reload": "Trudne przeładowanie",
"Help": "Wsparcie",
"History": "Historia",
"Keyboard Shortcuts": "Skróty klawiszowe",
"Log Out": "Wyloguj",
"Minimize": "Zminimalizować",
"Paste": "Pasta",
"Paste and Match Style": "Wklej i dopasuj styl",
"Quit": "Porzucić",
"Redo": "Przerobić",
"Reload": "Przeładować",
"Report an issue...": "Zgłoś problem...",
"Reset App Settings": "Zresetuj ustawienia aplikacji",
"Select All": "Zaznacz wszystko",
"Show App Logs": "Pokaż dzienniki aplikacji",
"Toggle DevTools for Active Tab": "Przełącz DevTools dla Active Tab",
"Toggle DevTools for Zulip App": "Przełącz DevTools dla aplikacji Zulip",
"Toggle Full Screen": "Przełącz tryb pełnoekranowy",
"Toggle Sidebar": "Przełącz boczny pasek",
"Toggle Tray Icon": "Przełącz ikonę tacy",
"Undo": "Cofnij",
"View": "Widok",
"Window": "Okno",
"Zoom In": "Zbliżenie",
"Zoom Out": "Pomniejsz",
"Zulip Help": "Zulip Help"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "Sobre Zulip",
"Actual Size": "Tamanho atual",
"Back": "Costas",
"Close": "Fechar",
"Copy": "cópia de",
"Cut": "Cortar",
"Delete": "Excluir",
"Desktop App Settings": "Configurações da aplicação Desktop",
"Edit": "Editar",
"File": "Arquivo",
"Forward": "frente",
"Hard Reload": "Hard Recargar",
"Help": "Socorro",
"History": "História",
"Keyboard Shortcuts": "Atalhos do teclado",
"Log Out": "Sair",
"Minimize": "Minimizar",
"Paste": "Colar",
"Paste and Match Style": "Estilo de colar e combinar",
"Quit": "Sair",
"Redo": "Refazer",
"Reload": "recarregar",
"Report an issue...": "Relatar um problema ...",
"Reset App Settings": "Redefinir as configurações da aplicação",
"Select All": "Selecionar tudo",
"Show App Logs": "Mostrar logs de aplicativos",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Full Screen": "Alternar para o modo tela cheia",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Ícone Toggle Tray",
"Undo": "Desfazer",
"View": "Visão",
"Window": "Janela",
"Zoom In": "Mais Zoom",
"Zoom Out": "Reduzir o zoom",
"Zulip Help": "Zulip Help"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "О пользователе Zulip",
"Actual Size": "Фактический размер",
"Back": "назад",
"Close": "Закрыть",
"Copy": "копия",
"Cut": "Порез",
"Delete": "Удалить",
"Desktop App Settings": "Настройки настольных приложений",
"Edit": "редактировать",
"File": "файл",
"Forward": "Вперед",
"Hard Reload": "Hard Reload",
"Help": "Помогите",
"History": "история",
"Keyboard Shortcuts": "Горячие клавиши",
"Log Out": "Выйти",
"Minimize": "Минимизировать",
"Paste": "Вставить",
"Paste and Match Style": "Стиль вставки и совпадения",
"Quit": "Уволиться",
"Redo": "переделывать",
"Reload": "перезагружать",
"Report an issue...": "Сообщить о проблеме...",
"Reset App Settings": "Сбросить настройки приложения",
"Select All": "Выбрать все",
"Show App Logs": "Показать журналы приложений",
"Toggle DevTools for Active Tab": "Toggle DevTools для активной вкладки",
"Toggle DevTools for Zulip App": "Toggle DevTools для приложения Zulip",
"Toggle Full Screen": "Включить полноэкранный режим",
"Toggle Sidebar": "Переключить боковую панель",
"Toggle Tray Icon": "Иконка Toggle Tray",
"Undo": "расстегивать",
"View": "Посмотреть",
"Window": "Окно",
"Zoom In": "Приблизить",
"Zoom Out": "Уменьшить",
"Zulip Help": "Zulip Help"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "О Зулипу",
"Actual Size": "Стварна величина",
"Back": "Назад",
"Close": "Близу",
"Copy": "Копирај",
"Cut": "Цут",
"Delete": "Избриши",
"Desktop App Settings": "Подешавања за десктоп апликације",
"Edit": "Уредити",
"File": "Филе",
"Forward": "Напријед",
"Hard Reload": "Хард Релоад",
"Help": "Помоћ",
"History": "Историја",
"Keyboard Shortcuts": "Пречице на тастатури",
"Log Out": "Одјавити се",
"Minimize": "Минимизирај",
"Paste": "Пасте",
"Paste and Match Style": "Пасте и Матцх Стиле",
"Quit": "Одустати",
"Redo": "Поново",
"Reload": "Освежи",
"Report an issue...": "Пријавите проблем...",
"Reset App Settings": "Поново поставите подешавања апликације",
"Select All": "Изабери све",
"Show App Logs": "Прикажи евиденције апликација",
"Toggle DevTools for Active Tab": "Пребаците ДевТоолс за Ацтиве Таб",
"Toggle DevTools for Zulip App": "Пребаците ДевТоолс за Зулип Апп",
"Toggle Full Screen": "Пребаците цео екран",
"Toggle Sidebar": "Пребаците бочну траку",
"Toggle Tray Icon": "Тоггле Траи Ицон",
"Undo": "Ундо",
"View": "Поглед",
"Window": "Прозор",
"Zoom In": "Увеличати",
"Zoom Out": "Зумирај",
"Zulip Help": "Зулип Хелп"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "ஜுலிப் பற்றி",
"Actual Size": "உண்மையான அளவு",
"Back": "மீண்டும்",
"Close": "நெருக்கமான",
"Copy": "நகல்",
"Cut": "வெட்டு",
"Delete": "அழி",
"Desktop App Settings": "டெஸ்க்டாப் பயன்பாட்டு அமைப்புகள்",
"Edit": "தொகு",
"File": "கோப்பு",
"Forward": "முன்னோக்கி",
"Hard Reload": "கடினமாக மீண்டும் ஏற்றவும்",
"Help": "உதவி",
"History": "வரலாறு",
"Keyboard Shortcuts": "விசைப்பலகை குறுக்குவழிகள்",
"Log Out": "வெளியேறு",
"Minimize": "குறைத்தல்",
"Paste": "ஒட்டு",
"Paste and Match Style": "உடை ஒட்டு மற்றும் பொருந்தும்",
"Quit": "விட்டுவிட",
"Redo": "மீண்டும் செய்",
"Reload": "ஏற்றவும்",
"Report an issue...": "சிக்கலைப் புகார ...",
"Reset App Settings": "பயன்பாட்டு அமைப்புகளை மீட்டமைக்கவும்",
"Select All": "அனைத்தையும் தெரிவுசெய்",
"Show App Logs": "பயன்பாட்டு பதிவுகள் காட்டு",
"Toggle DevTools for Active Tab": "செயலில் தாவலுக்கு DevTools ஐ மாற்று",
"Toggle DevTools for Zulip App": "Zulip பயன்பாட்டிற்கான DevTools ஐ மாற்று",
"Toggle Full Screen": "மாற்று முழுத்திரை",
"Toggle Sidebar": "பக்கப்பட்டி மாறு",
"Toggle Tray Icon": "ட்ரே ஐகானை மாற்று",
"Undo": "செயல்தவிர்",
"View": "காண்க",
"Window": "ஜன்னல்",
"Zoom In": "பெரிதாக்க",
"Zoom Out": "பெரிதாக்குக",
"Zulip Help": "Zulip உதவி"
}

View File

@@ -1,39 +0,0 @@
{
"About Zulip": "Zulip Hakkinda",
"Actual Size": "Gerçek boyutu",
"Back": "Geri",
"Close": "Kapat",
"Copy": "kopya",
"Cut": "Kesmek",
"Delete": "silmek",
"Desktop App Settings": "Masaüstü Uygulama Ayarları",
"Edit": "Düzenle",
"File": "Dosya",
"Forward": "ileri",
"Hard Reload": "Sert Yeniden Yükle",
"Help": "yardım et",
"History": "Tarihçe",
"Keyboard Shortcuts": "Klavye kısayolları",
"Log Out": ıkış Yap",
"Minimize": "küçültmek",
"Paste": "Yapıştırmak",
"Paste and Match Style": "Stili Yapıştır ve Eşleştir",
"Quit": ıkmak",
"Redo": "yeniden yapmak",
"Reload": "Tekrar yükle",
"Report an issue...": "Bir sorun bildir ...",
"Reset App Settings": "Uygulama Ayarlarını Sıfırla",
"Select All": "Hepsini seç",
"Show App Logs": "Uygulama Günlüğlerini Göster",
"Toggle DevTools for Active Tab": "Aktif Sekme İçin DevTools'a Geçiş Yap",
"Toggle DevTools for Zulip App": "Zulip App için DevTools'a Geçiş Yap",
"Toggle Full Screen": "Tam ekrana geç",
"Toggle Sidebar": "Kenar Çubuğunu Aç / Kapat",
"Toggle Tray Icon": "Tepsi Simgesini Aç / Kapat",
"Undo": "Geri alma",
"View": "Görünüm",
"Window": "pencere",
"Zoom In": "Yakınlaştır",
"Zoom Out": "Uzaklaştır",
"Zulip Help": "Zulip Yardımı"
}

View File

@@ -17,8 +17,7 @@ install:
- npm install
- npm install -g gulp
build: off
build: off
test_script:
- npm run test
- npm run test-e2e

View File

@@ -1,10 +1,9 @@
'use strict';
const gulp = require('gulp');
const mocha = require('gulp-mocha');
const electron = require('electron-connect').server.create({
verbose: true
});
const tape = require('gulp-tape');
const tapColorize = require('tap-colorize');
gulp.task('dev', () => {
// Start browser process
@@ -14,7 +13,7 @@ gulp.task('dev', () => {
// Reload renderer process
gulp.watch('app/renderer/css/*.css', ['reload:renderer']);
gulp.watch('app/renderer/*.html', ['reload:renderer']);
gulp.watch('app/renderer/js/**/*.js', ['reload:renderer']);
gulp.watch('app/renderer/js/*.js', ['reload:renderer']);
});
gulp.task('restart:browser', done => {
@@ -29,11 +28,9 @@ gulp.task('reload:renderer', done => {
done();
});
gulp.task('test-e2e', () => {
return gulp.src('tests/*.js')
.pipe(tape({
reporter: tapColorize()
}));
// Test app using mocha+spectron
gulp.task('test', () => {
return gulp.src('tests/index.js').pipe(mocha());
});
gulp.task('default', ['dev', 'test-e2e']);
gulp.task('default', ['dev', 'test']);

9854
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,15 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.8.2",
"version": "1.4.0",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
"email": "<svnitakash@gmail.com>",
"copyright": "©2017 Kandra Labs, Inc.",
"author": {
"name": "Kandra Labs, Inc.",
"email": "support@zulipchat.com"
"email": "svnitakash@gmail.com"
},
"repository": {
"type": "git",
@@ -17,21 +18,15 @@
"bugs": {
"url": "https://github.com/zulip/zulip-electron/issues"
},
"engines": {
"node": ">=6.0.0"
},
"scripts": {
"start": "electron app --disable-http-cache --no-electron-connect",
"reinstall": "./tools/reinstall-node-modules",
"start": "electron app --disable-http-cache",
"postinstall": "electron-builder install-app-deps",
"test": "xo",
"test-e2e": "gulp test-e2e",
"dev": "gulp dev & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js",
"dev": "gulp dev",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"mas": "electron-builder --mac mas",
"travis": "cd ./scripts && ./travis-build-test.sh",
"build-locales": "node tools/locale-helper"
"travis": "cd ./scripts && ./travis-build-test.sh"
},
"pre-commit": [
"test"
@@ -60,9 +55,7 @@
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
},
"deb": {
"synopsis": "Zulip Desktop App",
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
"synopsis": "Zulip Desktop App"
},
"dmg": {
"background": "build/appdmg.png",
@@ -111,22 +104,17 @@
],
"devDependencies": {
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "1.8.2",
"electron-builder": "20.4.1",
"electron-builder": "19.27.3",
"electron": "1.6.11",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",
"gulp": "3.9.1",
"gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"nodemon": "^1.14.11",
"pre-commit": "1.2.2",
"gulp-mocha": "4.3.1",
"chai-as-promised": "7.1.1",
"chai": "4.1.1",
"spectron": "3.7.2",
"tap-colorize": "^1.2.0",
"tape": "^4.8.0",
"xo": "0.18.2"
"xo": "0.18.2",
"pre-commit": "1.2.2"
},
"xo": {
"parserOptions": {
@@ -142,11 +130,7 @@
"rules": {
"max-lines": [
"warn",
{
"max": 500,
"skipBlankLines": true,
"skipComments": true
}
500
],
"no-warning-comments": 0,
"object-curly-spacing": 0,
@@ -163,9 +147,7 @@
}
],
"ignore": [
"tests/*.js",
"tools/locale-helper/*.js",
"scripts/*.js"
"tests/*.js"
],
"envs": [
"node",
@@ -173,4 +155,4 @@
"mocha"
]
}
}
}

View File

@@ -1,10 +0,0 @@
#!/bin/bash
# This script runs when user install the debian package
# Install apt repository source list if it does not exist
if ! grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then
sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9
echo "deb https://dl.bintray.com/zulip/debian/ stable main" | \
sudo tee -a /etc/apt/sources.list.d/zulip.list;
fi

View File

@@ -1,31 +0,0 @@
#!/bin/bash
# This script runs when user uninstall the debian package.
# It will remove all the config files and anything which was added by the app.
# Remove apt repository source list when user uninstalls Zulip app
if grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then
sudo apt-key del 69AD12704E71A4803DCA3A682424BE5AE9BD10D9;
sudo rm /etc/apt/sources.list.d/zulip.list;
fi
# Get the root user
if [ $SUDO_USER ];
then getSudoUser=$SUDO_USER;
else getSudoUser=`whoami`;
fi
# Get the path for Zulip's desktop entry which is created by auto-launch script
getDesktopEntry=/home/$getSudoUser/.config/autostart/zulip.desktop;
# Remove desktop entry if exists
if [ -f $getDesktopEntry ]; then
sudo rm $getDesktopEntry;
fi
# App directory which contains all the config, setting files
appDirectory=/home/$getSudoUser/.config/Zulip/;
if [ -d $appDirectory ]; then
sudo rm -rf $appDirectory;
fi

View File

@@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -e
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
sudo apt-get install --no-install-recommends -y icnsutils
# to build 32 bit from a machine with 64 bit
sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib
fi

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env node
if (!process.env.TRAVIS_OS_NAME === 'linux') {
process.exit(0);
}
const fs = require('fs');
const path = require('path');
// go to dist directory
process.chdir(path.resolve(__dirname, '../dist'));
const extensions = /\.deb|\.AppImage|\.zip/;
const files = fs.readdirSync(process.cwd()).filter(file => file.includes);
function changeFileName(file) {
file = file.replace(/^z/, 'Z');
file = file.replace(/_{2}/, '-');
return file;
}
// Change file name to what we want
// eg zulip_1.8.2_amd64.deb -> Zulip-1.8.2-amd64.deb
// and change file name
files.map(file => {
const newFileName = changeFileName(file);
fs.renameSync(file, newFileName);
});

View File

@@ -1,20 +1,10 @@
#!/usr/bin/env bash
# exit script if fails
set -e;
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
export {no_proxy,NO_PROXY}="127.0.0.1,localhost"
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
sleep 3
echo 'Travis Screen Resolution:'
xdpyinfo | grep dimensions
export {no_proxy,NO_PROXY}="127.0.0.1,localhost"
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
sleep 3
fi
npm run test
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
npm run test-e2e
fi

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env bash
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16
fi

View File

@@ -1,7 +0,0 @@
const path = require('path')
const TEST_APP_PRODUCT_NAME = 'ZulipTest'
module.exports = {
TEST_APP_PRODUCT_NAME
}

View File

@@ -1,13 +1,81 @@
const test = require('tape')
const setup = require('./setup')
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)
beforeEach(function () {
this.app = new Application({
path: require('electron'),
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()
}
})
it('shows an initial window', function () {
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))
})
})
test('app runs', function (t) {
t.timeoutAfter(10e3)
setup.resetTestDataDir()
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.waitForExist('//*[@id="new-server-container"]/div/div/div[2]/input'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

View File

@@ -1,99 +0,0 @@
const Application = require('spectron').Application
const cpFile = require('cp-file')
const fs = require('fs')
const isCI = require('is-ci')
const mkdirp = require('mkdirp')
const path = require('path')
const rimraf = require('rimraf')
const config = require('./config')
module.exports = {
createApp,
endTest,
waitForLoad,
wait,
resetTestDataDir
}
// Runs Zulip Desktop.
// Returns a promise that resolves to a Spectron Application once the app has loaded.
// Takes a Tape test. Makes some basic assertions to verify that the app loaded correctly.
function createApp (t) {
generateTestAppPackageJson()
return new Application({
path: path.join(__dirname, '..', 'node_modules', '.bin',
'electron' + (process.platform === 'win32' ? '.cmd' : '')),
args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont
env: {NODE_ENV: 'test'},
waitTimeout: 10e3
})
}
// Generates package.json for test app
// Reads app package.json and updates the productName to config.TEST_APP_PRODUCT_NAME
// We do this so that the app integration doesn't doesn't share the same appDataDir as the dev application
function generateTestAppPackageJson () {
let packageJson = require(path.join(__dirname, '../package.json'))
packageJson.productName = config.TEST_APP_PRODUCT_NAME
packageJson.main = '../app/main'
const testPackageJsonPath = path.join(__dirname, 'package.json')
fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8')
}
// Starts the app, waits for it to load, returns a promise
function waitForLoad (app, t, opts) {
if (!opts) opts = {}
return app.start().then(function () {
return app.client.waitUntilWindowLoaded()
})
.then(function() {
return app.client.pause(2000);
})
.then(function () {
return app.webContents.getTitle()
}).then(function (title) {
t.equal(title, 'Zulip', 'html title')
})
}
// Returns a promise that resolves after 'ms' milliseconds. Default: 1 second
function wait (ms) {
if (ms === undefined) ms = 1000 // Default: wait long enough for the UI to update
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms)
})
}
// Quit the app, end the test, either in success (!err) or failure (err)
function endTest (app, t, err) {
return app.stop().then(function () {
t.end(err)
})
}
function getAppDataDir () {
let base
if (process.platform === 'darwin') {
base = path.join(process.env.HOME, 'Library', 'Application Support')
} else if (process.platform === 'linux') {
base = process.env.XDG_CONFIG_HOME ?
process.env.XDG_CONFIG_HOME : path.join(process.env.HOME, '.config')
} else if (process.platform === 'win32') {
base = process.env.APPDATA
} else {
console.log('Could not detect app data dir base. Exiting...')
process.exit(1)
}
console.log('Detected App Data Dir base:', base)
return path.join(base, config.TEST_APP_PRODUCT_NAME)
}
// Resets the test directory, containing domain.json, window-state.json, etc
function resetTestDataDir () {
appDataDir = getAppDataDir()
rimraf.sync(appDataDir)
rimraf.sync(path.join(__dirname, 'package.json'))
}

View File

@@ -1,19 +0,0 @@
const test = require('tape')
const setup = require('./setup')
test('add-organization', function (t) {
t.timeoutAfter(50e3)
setup.resetTestDataDir()
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.setValue('.setting-input-value', 'chat.zulip.org'))
.then(() => app.client.click('.server-save-action'))
.then(() => setup.wait(5000))
.then(() => app.client.windowByIndex(0)) // Switch focus back to main win
.then(() => app.client.windowByIndex(1)) // Switch focus back to org webview
.then(() => app.client.waitForExist('//*[@id="id_username"]'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

View File

@@ -1,16 +0,0 @@
#!/bin/bash
set -e
set -x
if ! git diff-index --quiet HEAD; then
set +x
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit 1
fi
request_id="$1"
remote=${2:-"upstream"}
git fetch "$remote" "pull/$request_id/head"
git checkout -B "review-original-${request_id}"
git reset --hard FETCH_HEAD

View File

@@ -1,23 +0,0 @@
@echo off
git diff-index --quiet HEAD
if %ERRORLEVEL% NEQ 0 (
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit /B 1
)
if "%~1"=="" (
echo "Error you must specify the PR number"
)
if "%~2"=="" (
set remote="upstream"
) else (
set remote=%2
)
set request_id="%1"
git fetch "%remote%" "pull/%request_id%/head"
git checkout -B "review-%request_id%"
git reset --hard FETCH_HEAD

View File

@@ -1,17 +0,0 @@
#!/bin/bash
set -e
set -x
if ! git diff-index --quiet HEAD; then
set +x
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit 1
fi
request_id="$1"
remote=${2:-"upstream"}
git fetch "$remote" "pull/$request_id/head"
git checkout -B "review-${request_id}" $remote/master
git reset --hard FETCH_HEAD
git pull --rebase

View File

@@ -1,24 +0,0 @@
@echo off
git diff-index --quiet HEAD
if %errorlevel% neq 0 (
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit \B 1
)
if "%~1"=="" (
echo "Error you must specify the PR number"
)
if "%~2"=="" (
set remote="upstream"
) else (
set remote=%2
)
set request_id="%1"
git fetch "%remote%" "pull/%request_id%/head"
git checkout -B "review-%request_id%" %remote%/master
git reset --hard FETCH_HEAD
git pull --rebase

View File

@@ -1,30 +0,0 @@
const translate = require('google-translate-api');
const path = require('path');
const fs = require('fs');
const translationDir = path.resolve(__dirname, '../../app/translations');
function writeJSON(file, data) {
const filePath = path.resolve(translationDir, file);
fs.writeFileSync(filePath, `${JSON.stringify(data, null, '\t')}\n`, 'utf8');
}
const { phrases } = require('./locale-template');
const supportedLocales = require('./supported-locales');
phrases.sort();
for (let locale in supportedLocales) {
console.log(`fetching translation for: ${supportedLocales[locale]} - ${locale}..`);
translate(phrases.join('\n'), { to: locale })
.then(res => {
const localeFile = `${locale}.json`;
const translatedText = res.text.split('\n');
const translationJSON = {};
phrases.forEach((phrase, index) => {
translationJSON[phrase] = translatedText[index];
});
writeJSON(localeFile, translationJSON);
console.log(`create: ${localeFile}`);
});
}

View File

@@ -1,41 +0,0 @@
{
"phrases": [
"About Zulip",
"Actual Size",
"Back",
"Close",
"Copy",
"Cut",
"Delete",
"Desktop App Settings",
"Edit",
"File",
"Forward",
"Hard Reload",
"Help",
"History",
"Keyboard Shortcuts",
"Log Out",
"Minimize",
"Paste",
"Paste and Match Style",
"Quit",
"Redo",
"Reload",
"Report an issue...",
"Reset App Settings",
"Select All",
"Show App Logs",
"Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App",
"Toggle Full Screen",
"Toggle Sidebar",
"Toggle Tray Icon",
"Undo",
"View",
"Window",
"Zoom In",
"Zoom Out",
"Zulip Help"
]
}

View File

@@ -1,21 +0,0 @@
module.exports = {
"de": "Deutsch",
"pl": "polski",
"en": "English",
"cs": "česky",
"hi": "Hindi",
"ru": "Русский",
"id": "Indonesia",
"bg": "български",
"hu": "Magyar",
"sr": "српски",
"ml": "Malayalam",
"ta": "தமிழ்",
"nl": "Nederlands",
"ja": "日本語",
"pt": "Português",
"tr": "Türkçe",
"es": "español",
"ko": "한국어",
"fr": "français"
};

View File

@@ -1,10 +0,0 @@
#!/bin/bash
set -e
set -x
echo "Removing node_modules and app/node_modules"
rm -rf node_modules
rm -rf app/node_modules
echo "node_modules removed reinstalling npm packages"
npm i

View File

@@ -1,8 +0,0 @@
@echo off
echo "Removing node_modules and app/node_modules"
rmdir /s /q node_modules
rmdir /s /q app/node_modules
echo "node_modules removed reinstalling npm packages"
npm i

View File

@@ -5,11 +5,7 @@
* Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I`
### Error : ChecksumMismatchError
- Try deleteing `node_modules` && `app/node_modules` directories. Re-install dependencies using `npm install`
- Try deleteing `node_modules` && `app/node_modules` directories. Re-install dependencies using `npm install`.
### Error : Module version mismatch. Expected 50, got 51
- Make sure you have installed [node-gyp](https://github.com/nodejs/node-gyp#installation) dependencies properly
### Error: Desktop Notifications not working
- Make sure the **Show Desktop Notifications** setting option is set to be true
- Check your OS notifications center settings
- Make sure you have installed [node-gyp](https://github.com/nodejs/node-gyp#installation) dependencies properly.