Compare commits

..

1 Commits

Author SHA1 Message Date
Akash Nimare
c7460693b6 webview: Fix webview glitches. 2018-02-23 23:33:05 +05:30
96 changed files with 5079 additions and 9093 deletions

7
.gitignore vendored
View File

@@ -7,13 +7,6 @@ node_modules/
# Compiled binary build directory
dist/
#snap generated files
snap/parts
snap/prime
snap/snap
snap/stage
snap/*.snap
# Logs
logs
*.log

View File

@@ -15,7 +15,7 @@ addons:
language: node_js
node_js:
- '8'
- '6'
before_install:
- ./scripts/travis-xvfb.sh

View File

@@ -12,7 +12,7 @@ Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If yo
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/16-desktop).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/electron).
## Issue
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-electron/issues/new).

View File

@@ -1,11 +1,11 @@
'use strict';
const { app, dialog, shell } = require('electron');
const { app, dialog } = require('electron');
const { autoUpdater } = require('electron-updater');
const isDev = require('electron-is-dev');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
function appUpdater(updateFromMenu = false) {
function appUpdater() {
// Don't initiate auto-updates in development
if (isDev) {
return;
@@ -17,8 +17,6 @@ function appUpdater(updateFromMenu = false) {
return;
}
let updateAvailable = false;
// Create Logs directory
const LogsDir = `${app.getPath('userData')}/Logs`;
@@ -30,58 +28,7 @@ function appUpdater(updateFromMenu = false) {
autoUpdater.logger = log;
// Handle auto updates for beta/pre releases
const isBetaUpdate = ConfigUtil.getConfigItem('betaUpdate');
autoUpdater.allowPrerelease = isBetaUpdate || false;
const eventsListenerRemove = ['update-available', 'update-not-available'];
autoUpdater.on('update-available', info => {
if (updateFromMenu) {
dialog.showMessageBox({
message: `A new version ${info.version}, of Zulip Desktop is available`,
detail: 'The update will be downloaded in the background. You will be notified when it is ready to be installed.'
});
updateAvailable = true;
// This is to prevent removal of 'update-downloaded' and 'error' event listener.
eventsListenerRemove.forEach(event => {
autoUpdater.removeAllListeners(event);
});
}
});
autoUpdater.on('update-not-available', () => {
if (updateFromMenu) {
dialog.showMessageBox({
message: 'No updates available',
detail: `You are running the latest version of Zulip Desktop.\nVersion: ${app.getVersion()}`
});
// Remove all autoUpdator listeners so that next time autoUpdator is manually called these
// listeners don't trigger multiple times.
autoUpdater.removeAllListeners();
}
});
autoUpdater.on('error', error => {
if (updateFromMenu) {
const messageText = (updateAvailable) ? ('Unable to download the updates') : ('Unable to check for updates');
dialog.showMessageBox({
type: 'error',
buttons: ['Manual Download', 'Cancel'],
message: messageText,
detail: (error).toString() + `\n\nThe latest version of Zulip Desktop is available at -\nhttps://zulipchat.com/apps/.\n
Current Version: ${app.getVersion()}`
}, response => {
if (response === 0) {
shell.openExternal('https://zulipchat.com/apps/');
}
});
// Remove all autoUpdator listeners so that next time autoUpdator is manually called these
// listeners don't trigger multiple times.
autoUpdater.removeAllListeners();
}
});
autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false;
// Ask the user if update is available
// eslint-disable-next-line no-unused-vars

View File

@@ -0,0 +1,16 @@
'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

@@ -5,6 +5,7 @@ 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');
@@ -12,8 +13,6 @@ const { app, ipcMain } = electron;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
const ProxyUtil = require('./../renderer/js/utils/proxy-util.js');
const { sentryInit } = require('./../renderer/js/utils/sentry-util.js');
// Adds debug features like hotkeys for triggering dev tools and reload
// in development mode
@@ -53,9 +52,8 @@ const iconPath = () => {
function createMainWindow() {
// Load the previous state with fallback to defaults
const mainWindowState = windowStateKeeper({
defaultWidth: 1100,
defaultHeight: 720,
path: `${app.getPath('userData')}/config`
defaultWidth: 1000,
defaultHeight: 600
});
// Let's keep the window position global so that we can access it in other process
@@ -73,8 +71,8 @@ function createMainWindow() {
minHeight: 400,
webPreferences: {
plugins: true,
nodeIntegration: true,
partition: 'persist:webviewsession'
allowDisplayingInsecureContent: true,
nodeIntegration: true
},
show: false
});
@@ -134,10 +132,6 @@ function createMainWindow() {
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
// Temporary fix for Electron render colors differently
// More info here - https://github.com/electron/electron/issues/10732
app.commandLine.appendSwitch('force-color-profile', 'srgb');
// eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
@@ -156,15 +150,6 @@ app.on('ready', () => {
});
mainWindow = createMainWindow();
// Initialize sentry for main process
sentryInit();
const isSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
if (isSystemProxy) {
ProxyUtil.resolveSystemProxy(mainWindow);
}
const page = mainWindow.webContents;
page.on('dom-ready', () => {
@@ -176,10 +161,9 @@ app.on('ready', () => {
});
page.once('did-frame-finish-load', () => {
// Initiate auto-updates on MacOS and Windows
if (ConfigUtil.getConfigItem('autoUpdate')) {
appUpdater();
}
// Initate auto-updates on MacOS and Windows
appUpdater();
crashHandler();
});
// Temporarily remove this event
@@ -196,29 +180,6 @@ app.on('ready', () => {
app.quit();
});
// Show pdf in a new BrowserWindow
ipcMain.on('pdf-view', (event, url) => {
// Paddings for pdfWindow so that it fits into the main browserWindow
const paddingWidth = 55;
const paddingHeight = 22;
// Get the config of main browserWindow
const mainWindowState = global.mainWindowState;
// Window to view the pdf file
const pdfWindow = new electron.BrowserWindow({
x: mainWindowState.x + paddingWidth,
y: mainWindowState.y + paddingHeight,
width: mainWindowState.width - paddingWidth,
height: mainWindowState.height - paddingHeight,
webPreferences: {
plugins: true,
partition: 'persist:webviewsession'
}
});
pdfWindow.loadURL(url);
});
// Reload full app not just webview, useful in debugging
ipcMain.on('reload-full-app', () => {
mainWindow.reload();
@@ -264,48 +225,6 @@ app.on('ready', () => {
ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => {
setAutoLaunch(AutoLaunchValue);
});
ipcMain.on('downloadFile', (event, url, downloadPath) => {
page.downloadURL(url);
page.session.once('will-download', (event, item) => {
const filePath = path.join(downloadPath, item.getFilename());
item.setSavePath(filePath);
item.on('updated', (event, state) => {
switch (state) {
case 'interrupted': {
// Can interrupted to due to network error, cancel download then
console.log('Download interrupted, cancelling and fallback to dialog download.');
item.cancel();
break;
}
case 'progressing': {
if (item.isPaused()) {
item.cancel();
}
// This event can also be used to show progres in percentage in future.
break;
}
default: {
console.info('Unknown updated state of download item');
}
}
});
item.once('done', (event, state) => {
if (state === 'completed') {
page.send('downloadFileCompleted', item.getSavePath(), item.getFilename());
} else {
console.log('Download failed state: ', state);
page.send('downloadFileFailed');
}
// To stop item for listening to updated events of this file
item.removeAllListeners('updated');
});
});
});
ipcMain.on('realm-icon-changed', (event, serverURL, iconURL) => {
page.send('update-realm-icon', serverURL, iconURL);
});
});
app.on('before-quit', () => {

View File

@@ -4,30 +4,20 @@ const { Notification } = require('electron');
const request = require('request');
const semver = require('semver');
const ConfigUtil = require('../renderer/js/utils/config-util');
const ProxyUtil = require('../renderer/js/utils/proxy-util');
const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
const Logger = require('../renderer/js/utils/logger-util');
const logger = new Logger({
file: 'linux-update-util.log',
timestamp: true
});
function linuxUpdateNotification() {
let url = 'https://api.github.com/repos/zulip/zulip-electron/releases';
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const options = {
url,
headers: {'User-Agent': 'request'},
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : ''
headers: {'User-Agent': 'request'}
};
request(options, (error, response, body) => {
if (error) {
logger.error('Linux update error.');
logger.error(error);
console.log('Error:', error);
return;
}
if (response.statusCode < 400) {
@@ -42,7 +32,7 @@ function linuxUpdateNotification() {
}
}
} else {
logger.log('Linux update response status: ', response.statusCode);
console.log('Status:', response.statusCode);
}
});
}

View File

@@ -1,23 +1,15 @@
'use strict';
const os = require('os');
const path = require('path');
const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
const { app, shell, BrowserWindow, Menu } = require('electron');
const fs = require('fs-extra');
const AdmZip = require('adm-zip');
const { appUpdater } = require('./autoupdater');
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/../renderer/js/utils/dnd-util.js');
const Logger = require(__dirname + '/../renderer/js/utils/logger-util.js');
const appName = app.getName();
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class AppMenu {
getHistorySubmenu() {
return [{
@@ -95,7 +87,7 @@ class AppMenu {
}
}, {
label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+Shift+S',
accelerator: 'CommandOrControl+S',
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
@@ -128,12 +120,6 @@ class AppMenu {
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() {
@@ -142,29 +128,18 @@ class AppMenu {
}, {
label: 'Show App Logs',
click() {
const zip = new AdmZip();
let date = new Date();
date = date.toLocaleDateString().replace(/\//g, '-');
// Create a zip file of all the logs and config data
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/Logs`);
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/config`);
// Put the log file in downloads folder
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${date}.zip`;
zip.writeZip(logFilePath);
// Open and select the log file
shell.showItemInFolder(logFilePath);
shell.openItem(app.getPath('userData'));
}
}, {
label: 'Report an Issue...',
click() {
// the goal is to notify the main.html BrowserWindow
// which may not be the focused window.
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send('open-feedback-modal');
});
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)}`);
}
}];
}
@@ -216,11 +191,6 @@ class AppMenu {
AppMenu.sendAction('open-about');
}
}
}, {
label: `Check for Update`,
click() {
AppMenu.checkForUpdate();
}
}, {
type: 'separator'
}, {
@@ -233,7 +203,7 @@ class AppMenu {
}
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Cmd+Shift+K',
accelerator: 'Cmd+K',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
@@ -241,13 +211,6 @@ class AppMenu {
}
}, {
type: 'separator'
}, {
label: 'Toggle Do Not Disturb',
accelerator: 'Command+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, {
label: 'Reset App Settings',
accelerator: 'Command+Shift+D',
@@ -328,11 +291,6 @@ class AppMenu {
AppMenu.sendAction('open-about');
}
}
}, {
label: `Check for Update`,
click() {
AppMenu.checkForUpdate();
}
}, {
type: 'separator'
}, {
@@ -347,7 +305,7 @@ class AppMenu {
type: 'separator'
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Ctrl+Shift+K',
accelerator: 'Ctrl+K',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
@@ -355,13 +313,6 @@ class AppMenu {
}
}, {
type: 'separator'
}, {
label: 'Toggle Do Not Disturb',
accelerator: 'Ctrl+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, {
label: 'Reset App Settings',
accelerator: 'Ctrl+Shift+D',
@@ -430,37 +381,21 @@ class AppMenu {
win.webContents.send(action, ...params);
}
static checkForUpdate() {
appUpdater(true);
}
static resetAppSettings() {
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
// We save App's settings/configurations in following files
const settingFiles = ['config/window-state.json', 'config/domain.json', 'config/settings.json', 'config/certificates.json'];
const settingFiles = ['window-state.json', 'domain.json', 'settings.json'];
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure?',
detail: resetAppSettingsMessage
}, response => {
if (response === 0) {
settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, error => {
if (error) {
logger.error('Error while resetting app settings.');
logger.error(error);
} else {
fs.unlink(getSettingFilesPath, () => {
AppMenu.sendAction('clear-app-data');
});
}
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');
});
});
}
}
});
});
}

1191
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.3.6",
"version": "1.8.1",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -26,23 +26,19 @@
"InstantMessaging"
],
"dependencies": {
"@electron-elements/send-feedback": "1.0.7",
"@sentry/electron": "0.8.1",
"adm-zip": "0.4.11",
"auto-launch": "5.0.5",
"auto-launch": "5.0.1",
"electron-is-dev": "0.3.0",
"electron-log": "2.2.14",
"electron-log": "2.2.7",
"electron-spellchecker": "1.1.2",
"electron-updater": "2.23.3",
"electron-updater": "2.18.2",
"electron-window-state": "4.1.1",
"escape-html": "1.0.3",
"is-online": "7.0.0",
"node-json-db": "0.7.3",
"request": "2.85.0",
"request": "2.81.0",
"semver": "5.4.1",
"wurl": "2.5.0"
},
"optionalDependencies": {
"node-mac-notifier": "0.1.0"
"node-mac-notifier": "0.0.13"
}
}

View File

@@ -1,50 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/about.css">
</head>
<body>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/about.css">
</head>
<body>
<div class="about">
<img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version">v?.?.?</p>
<div class="maintenance-info">
<p class="detail maintainer">
Maintained by
<a onclick="linkInBrowser('website')">Zulip</a>
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>
Available under the <a onclick="linkInBrowser('license')">Apache 2.0 License</a>
</p>
<a class="bug" onclick="linkInBrowser('bug')" 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;
const { shell } = require('electron');
const version_tag = document.querySelector('#version');
version_tag.innerHTML = 'v' + app.getVersion();
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);
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>
<script>require('./js/shared/preventdrag.js')</script>
</body>
</body>
</html>

View File

@@ -15,6 +15,12 @@ body {
height: 100%;
}
.loading {
background: #fff url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
}
.toggle-sidebar {
background: #222c31;
width: 54px;
@@ -25,7 +31,6 @@ body {
-webkit-app-region: drag;
overflow: hidden;
transition: all 0.5s ease;
z-index: 2;
}
.toggle-sidebar div {
@@ -42,28 +47,6 @@ body {
transition: all 0.6s ease-out;
}
#view-controls-container {
height: calc(100% - 208px);
overflow-y: hidden;
}
#view-controls-container:hover {
overflow-y: overlay;
}
#view-controls-container::-webkit-scrollbar {
width: 4px;
}
#view-controls-container::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
}
#view-controls-container::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid slategrey;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
@@ -101,17 +84,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 {
@@ -140,14 +117,11 @@ body {
}
.action-button.active {
/* background-color: rgba(255, 255, 255, 0.25); */
background-color: #efefef;
opacity: 0.9;
padding-right: 14px;
background-color: rgba(255, 255, 255, 0.25);
}
.action-button.active i {
color: #1c262b;
color: #eee;
}
.tab:first-child {
@@ -270,63 +244,37 @@ body {
width: 100%;
}
/*Pseudo element for loading indicator*/
#webviews-container::before {
content: "";
position: absolute;
z-index: 1;
background: #fff url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
width: 100%;
height: 100%;
}
/*When the active webview is loaded*/
#webviews-container.loaded::before {
opacity: 0;
z-index: -1;
visibility: hidden;
}
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 */
#dnd-tooltip,
#back-tooltip,
#reload-tooltip,
#setting-tooltip {
font-family: sans-serif;
background: #222c31;
margin-left: 48px;
margin-left: 68px;
padding: 6px 8px;
position: absolute;
margin-top: 0px;
@@ -338,7 +286,6 @@ webview.focus {
font-size: 14px;
}
#dnd-tooltip:after,
#back-tooltip:after,
#reload-tooltip:after,
#setting-tooltip:after {
@@ -399,8 +346,6 @@ webview.focus {
height: 100%;
width: 100%;
position: relative;
flex-grow: 1;
flex-basis: 0px;
}
.hidden {
@@ -442,27 +387,4 @@ webview.focus {
overflow: hidden;
opacity: 1;
}
}
send-feedback {
width: 60%;
height: 85%;
}
#feedback-modal {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(68, 67, 67, 0.81);
align-items: center;
justify-content: center;
z-index: 2;
transition: all 1s ease-out;
}
#feedback-modal.show {
display: flex;
}
}

View File

@@ -13,7 +13,7 @@ body {
kbd {
display: inline-block;
border: 1px solid #ccc;
border: 1px solid #ccc ;
border-radius: 3px;
font-size: 15px;
font-family: Courier New, Courier, monospace;
@@ -95,13 +95,11 @@ td:nth-child(odd) {
}
#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 {
@@ -115,7 +113,7 @@ td:nth-child(odd) {
}
.nav.active {
color: #4ebfac;
color: #464e5a;
cursor: default;
position: relative;
}
@@ -123,19 +121,12 @@ td:nth-child(odd) {
.nav.active::before {
background: #464e5a;
width: 3px;
height: 18px;
height: 16px;
position: absolute;
left: -8px;
content: '';
}
/* We don't want to show this in nav item since we have the + button for adding an Organization */
#nav-AddServer {
display: none;
}
#settings-header {
font-size: 22px;
color: #222c31;
@@ -151,12 +142,12 @@ td:nth-child(odd) {
}
#new-server-container {
padding-left: 42px;
padding-top: 25px;
margin-right: 16px;
opacity: 1;
transition: opacity 0.3s;
}
.title {
padding: 4px 0 6px 0;
font-weight: 500;
color: #222c31;
}
@@ -168,16 +159,6 @@ td:nth-child(odd) {
padding: 4px 0 6px 0;
}
.add-server-info-row {
display: flex;
margin: 8px 0 0 0;
}
.add-server-info-right {
flex-grow: 1;
margin-top: 10px;
}
.sub-title {
padding: 4px 0 6px 0;
font-weight: bold;
@@ -188,40 +169,20 @@ img.server-info-icon {
width: 36px;
height: 36px;
padding: 4px;
cursor: pointer;
vertical-align: middle;
}
.server-info-left {
margin: 4px 20px 0 0;
min-width: 40%;
margin: 10px 20px 0 0;
}
.server-info-right {
margin-top: 4px;
width: 55%;
display: flex;
align-items: baseline;
justify-content: space-between;
flex-grow: 1;
margin-right: 10px;
}
.server-info-row {
display: inline-block;
margin: 5px 0 0 0;
}
.server-info-left .server-info-row {
display: inline-flex;
align-items: inherit;
vertical-align: -2px;
position: relative;
}
.server-url {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: flex;
margin: 8px 0 0 0;
}
.server-info-alias {
@@ -229,10 +190,6 @@ img.server-info-icon {
cursor: pointer;
}
.server-url-info {
margin-right: 10px;
}
.setting-input-key {
font-size: 14px;
height: 27px;
@@ -246,20 +203,22 @@ img.server-info-icon {
.setting-input-value {
flex-grow: 1;
font-size: 14px;
height: 22px;
border-radius: 3px;
padding: 13px;
padding: 7px;
border: #ededed 2px solid;
outline-width: 0;
background: transparent;
max-width: 450px;
max-width: 500px;
}
.setting-input-value:focus {
border: #4EBFAC 2px solid;
border: #7cb980 2px solid;
border-radius: 3px;
}
.manual-proxy-block {
width: 96%;
.setting-block {
width: 100%;
}
.actions-container {
@@ -311,7 +270,9 @@ img.server-info-icon {
}
.settings-card:hover {
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 0px 0px rgba(0, 0, 0, 0.12);
border-left: 8px solid #bcbcbc;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),
0 2px 0px 0px rgba(0,0,0,0.12);
}
.hidden {
@@ -320,15 +281,11 @@ img.server-info-icon {
}
.red {
color: #ef5350;
padding: 8px;
border: rgba(239, 83, 80, 0.5) solid 1px;
}
.red:hover {
color: #e63431;
border: rgba(239, 83, 80, 0.7) solid 1px;
;
color: #ffffff;
background: #ef5350;
padding: 3px;
padding-right: 10px;
padding-left: 10px;
}
.blue {
@@ -358,14 +315,12 @@ img.server-info-icon {
}
i.open-tab-button {
padding-left: 2px;
font-size: 19px;
padding: 0 5px;
font-size: 18px;
cursor: pointer;
}
.reset-data-button,
.custom-css-button,
.download-folder-button {
.reset-data-button {
display: inline-block;
border: none;
padding: 10px;
@@ -376,35 +331,19 @@ i.open-tab-button {
text-decoration: none;
}
.css-delete-action {
margin-bottom: 10px;
}
.reset-data-button:hover {
background-color: #3c9f8d;
color: #fff;
}
.selected-css-path,
.download-folder-path {
background: #eeeeee;
padding: 5px 10px;
margin-right: 10px;
display: flex;
width: 90%;
justify-content: space-between;
}
#remove-custom-css {
align-items: flex-end;
}
#server-info-container {
min-height: calc(100% - 260px);
min-height: calc(100% - 235px);
}
#create-organization-container {
font-size: 1.15em;
position: fixed;
bottom: 15px;
}
#create-organization-container i {
@@ -413,10 +352,17 @@ i.open-tab-button {
}
#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;
@@ -468,199 +414,4 @@ input.toggle-round:checked+label:before {
input.toggle-round:checked+label:after {
margin-left: 25px;
}
/* Add new server modal */
.add-server-modal {
display: block;
position: fixed;
z-index: 1;
padding-top: 15vh;
left: 0;
top: 0;
margin: auto;
width: 100%;
height: 100%;
/* background: rgba(61, 64, 67, 15); */
background: linear-gradient(35deg, #003b52, #45b59b);
overflow: auto;
}
/* Modal Content */
.modal-container {
background-color: #f4f7f8;
margin: auto;
padding: 57px;
border: #dae1e3 1px solid;
width: 550px;
height: 370px;
border-radius: 4px;
}
.add-server-modal .page-title {
text-align: center;
font-size: 1.6rem;
}
.divider {
margin-bottom: 30px;
margin-top: 30px;
color: #7d878a;
}
.divider hr {
margin-left: 8px;
margin-right: 8px;
width: 44%;
}
.left {
float: left;
}
.right {
float: right;
}
.server-center {
width: 100%;
text-align: center;
align-items: center;
padding-top: 13px;
margin-left: -5px;
}
.server-center button {
font-weight: bold;
font-size: 1.1rem;
margin: auto;
align-items: center;
text-align: center;
color: #fff;
background: #4EBFAC;
border-color: none;
border: none;
width: 98%;
height: 46px;
border-radius: 3px;
cursor: pointer;
}
.server-center button:hover {
background: #329588;
}
.server-center button:focus {
background: #329588;
}
.certificates-card {
width:70%
}
.certificate-input {
width:100%;
margin-top: 10px;
display:inline-flex;
}
.certificate-input div {
align-self:center;
}
.certificate-input .setting-input-value {
margin-left:10px;
max-width: 100%;
}
#add-certificate-button {
width:20%;
margin-right:0px;
height: 35px;
}
.tip {
background-color: hsl(46,63%,95%);
border: 1px solid hsl(46,63%,84%);
border-radius: 4px;
}
.md-14 {
font-size: 14px;
vertical-align: middle;
padding-right: 6px;
}
#open-hotkeys-link {
text-decoration: underline;
cursor: pointer;
}
/* responsive grid */
@media (max-width: 650px) {
.selected-css-path,
.download-folder-path {
margin-right: 15px;
}
#css-delete-action {
margin-left: 10px;
}
#css-delete-action span {
display: none;
}
}
@media (max-width: 720px) {
.modal-container {
width: 60vw;
padding: 40px;
min-width: 300px;
}
.server-center button {
margin-right: -12px;
width: 100%;
}
.divider {
margin-right: -8px;
}
.divider hr {
margin-left: 6px;
margin-right: 6px;
width: 43%;
}
#new-server-container {
padding-left: 0px;
}
}
@media (max-width: 600px) {
.divider {
margin-left: 4%;
}
.divider hr {
margin-left: 2px;
margin-right: 2px;
width: 40%;
}
}
@media (max-width: 900px) {
.settings-card {
flex-direction: column;
align-items: center;
}
.server-info-right {
flex-direction: column;
align-items: center;
}
.action {
margin-top: 10px;
}
}
}

View File

@@ -1,78 +0,0 @@
const { ipcRenderer } = require('electron');
const { shell, app } = require('electron').remote;
const LinkUtil = require('../utils/link-util');
const DomainUtil = require('../utils/domain-util');
const ConfigUtil = require('../utils/config-util');
const dingSound = new Audio('../resources/sounds/ding.ogg');
function handleExternalLink(event) {
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
const downloadPath = ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`);
const shouldShowInFolder = ConfigUtil.getConfigItem('showDownloadFolder', false);
// Whitelist URLs which are allowed to be opened in the app
const {
isInternalUrl: isWhiteListURL,
isUploadsUrl: isUploadsURL
} = LinkUtil.isInternal(domainPrefix, url);
if (isWhiteListURL) {
event.preventDefault();
// Show pdf attachments in a new window
if (LinkUtil.isPDF(url) && isUploadsURL) {
ipcRenderer.send('pdf-view', url);
return;
}
// download txt, mp3, mp4 etc.. by using downloadURL in the
// main process which allows the user to save the files to their desktop
// and not trigger webview reload while image in webview will
// do nothing and will not save it
if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
ipcRenderer.send('downloadFile', url, downloadPath);
ipcRenderer.once('downloadFileCompleted', (event, filePath, fileName) => {
const downloadNotification = new Notification('Download Complete', {
body: shouldShowInFolder ? `Click to show ${fileName} in folder` : `Click to open ${fileName}`,
silent: true // We'll play our own sound - ding.ogg
});
// Play sound to indicate download complete
if (!ConfigUtil.getConfigItem('silent')) {
dingSound.play();
}
downloadNotification.onclick = () => {
if (shouldShowInFolder) {
// Reveal file in download folder
shell.showItemInFolder(filePath);
} else {
// Open file in the default native app
shell.openItem(filePath);
}
};
ipcRenderer.removeAllListeners('downloadFileFailed');
});
ipcRenderer.once('downloadFileFailed', () => {
// Automatic download failed, so show save dialog prompt and download
// through webview
this.$el.downloadURL(url);
ipcRenderer.removeAllListeners('downloadFileCompleted');
});
return;
}
// open internal urls inside the current webview.
this.$el.loadURL(url);
} else {
event.preventDefault();
shell.openExternal(url);
}
}
module.exports = handleExternalLink;

View File

@@ -3,12 +3,13 @@
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 { app, dialog } = require('electron').remote;
const LinkUtil = require(__dirname + '/../utils/link-util.js');
const { shell, app } = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js');
const handleExternalLink = require(__dirname + '/../components/handle-external-link.js');
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
class WebView extends BaseComponent {
@@ -18,10 +19,9 @@ class WebView extends BaseComponent {
this.props = props;
this.zoomFactor = 1.0;
this.loading = true;
this.loading = false;
this.badgeCount = 0;
this.customCSS = ConfigUtil.getConfigItem('customCSS');
this.$webviewsContainer = document.querySelector('#webviews-container').classList;
this.loadingIndicator = document.getElementById('content');
}
template() {
@@ -46,7 +46,16 @@ class WebView extends BaseComponent {
registerListeners() {
this.$el.addEventListener('new-window', event => {
handleExternalLink.call(this, event);
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) {
event.preventDefault();
this.$el.loadURL(url);
} else {
event.preventDefault();
shell.openExternal(url);
}
});
if (shouldSilentWebview) {
@@ -75,24 +84,20 @@ class WebView extends BaseComponent {
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('●');
// bounce the dock
if (ConfigUtil.getConfigItem('dockBouncing')) {
app.dock.bounce();
}
}
});
this.$el.addEventListener('dom-ready', () => {
// const loadingIndicator = document.getElementById('content');
this.loadingIndicator.classList.remove('loading');
if (this.props.role === 'server') {
this.$el.classList.add('onload');
}
this.loading = false;
this.show();
});
@@ -126,39 +131,17 @@ class WebView extends BaseComponent {
return;
}
// To show or hide the loading indicator in the the active tab
if (this.loading) {
this.$webviewsContainer.remove('loaded');
} else {
this.$webviewsContainer.add('loaded');
}
this.$el.classList.remove('disabled');
this.$el.classList.add('active');
setTimeout(() => {
if (this.props.role === 'server') {
this.$el.classList.remove('onload');
}
}, 1000);
this.focus();
this.loading = false;
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() {
@@ -172,7 +155,6 @@ class WebView extends BaseComponent {
hide() {
this.$el.classList.add('disabled');
this.$el.classList.remove('active');
}
load() {
@@ -233,9 +215,6 @@ class WebView extends BaseComponent {
reload() {
this.hide();
// Shows the loading indicator till the webview is reloaded
this.$webviewsContainer.remove('loaded');
this.loading = true;
this.$el.reload();
}

View File

@@ -1,39 +0,0 @@
const events = require('events');
const { ipcRenderer } = require('electron');
// we have and will have some non camelcase stuff
// while working with zulip so just turning the rule off
// for the whole file.
/* eslint-disable camelcase */
class ElectronBridge extends events {
send_event(...args) {
this.emit(...args);
}
on_event(...args) {
this.on(...args);
}
}
const electron_bridge = new ElectronBridge();
electron_bridge.on('total_unread_count', (...args) => {
ipcRenderer.send('unread-count', ...args);
});
electron_bridge.on('realm_name', (...args) => {
ipcRenderer.send('realm-name-changed', ...args);
});
electron_bridge.on('realm_icon_url', iconURL => {
const serverURL = location.origin;
iconURL = iconURL.includes('http') ? iconURL : `${serverURL}${iconURL}`;
ipcRenderer.send('realm-icon-changed', serverURL, iconURL);
});
// this follows node's idiomatic implementation of event
// emitters to make event handling more simpler instead of using
// functions zulip side will emit event using ElectronBrigde.send_event
// which is alias of .emit and on this side we can handle the data by adding
// a listener for the event.
module.exports = electron_bridge;

View File

@@ -1,62 +0,0 @@
const { app } = require('electron').remote;
const path = require('path');
const fs = require('fs');
const SendFeedback = require('@electron-elements/send-feedback');
// make the button color match zulip app's theme
SendFeedback.customStyles = `
button:hover, button:focus {
border-color: #4EBFAC;
color: #4EBFAC;
}
button:active {
background-color: #f1f1f1;
color: #4EBFAC;
}
button {
background-color: #4EBFAC;
border-color: #4EBFAC;
}
`;
customElements.define('send-feedback', SendFeedback);
const sendFeedback = document.querySelector('send-feedback');
const feedbackHolder = sendFeedback.parentElement;
// customize the fields of custom elements
sendFeedback.title = 'Report Issue';
sendFeedback.titleLabel = 'Issue title:';
sendFeedback.titlePlaceholder = 'Enter issue title';
sendFeedback.textareaLabel = 'Describe the issue:';
sendFeedback.textareaPlaceholder = 'Succinctly describe your issue and steps to reproduce it...';
sendFeedback.buttonLabel = 'Report Issue';
sendFeedback.loaderSuccessText = '';
sendFeedback.useReporter('emailReporter', {
email: 'akash@zulipchat.com'
});
feedbackHolder.addEventListener('click', e => {
// only remove the class if the grey out faded
// part is clicked and not the feedback element itself
if (e.target === e.currentTarget) {
feedbackHolder.classList.remove('show');
}
});
sendFeedback.addEventListener('feedback-submitted', () => {
setTimeout(() => {
feedbackHolder.classList.remove('show');
}, 1000);
});
const dataDir = app.getPath('userData');
const logsDir = path.join(dataDir, '/Logs');
sendFeedback.logs.push(...fs.readdirSync(logsDir).map(file => path.join(logsDir, file)));
module.exports = {
feedbackHolder,
sendFeedback
};

View File

@@ -3,7 +3,7 @@
const { ipcRenderer, remote } = require('electron');
const isDev = require('electron-is-dev');
const { session, app } = remote;
const { session } = remote;
require(__dirname + '/js/tray.js');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
@@ -11,15 +11,7 @@ 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 DNDUtil = require(__dirname + '/js/utils/dnd-util.js');
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const Logger = require(__dirname + '/js/utils/logger-util.js');
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class ServerManagerView {
constructor() {
@@ -31,14 +23,12 @@ class ServerManagerView {
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$webviewsContainer = document.getElementById('webviews-container');
this.$backButton = $actionsContainer.querySelector('#back-action');
this.$dndButton = $actionsContainer.querySelector('#dnd-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.$dndTooltip = $actionsContainer.querySelector('#dnd-tooltip');
this.$sidebar = document.getElementById('sidebar');
@@ -64,17 +54,7 @@ class ServerManagerView {
loadProxy() {
return new Promise(resolve => {
// To change proxyEnable to useManualProxy in older versions
const proxyEnabledOld = ConfigUtil.isConfigItemExists('useProxy');
if (proxyEnabledOld) {
const proxyEnableOldState = ConfigUtil.getConfigItem('useProxy');
if (proxyEnableOldState) {
ConfigUtil.setConfigItem('useManualProxy', true);
}
ConfigUtil.removeConfigItem('useProxy');
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false);
if (proxyEnabled) {
session.fromPartition('persist:webviewsession').setProxy({
pacScript: ConfigUtil.getConfigItem('proxyPAC', ''),
@@ -98,25 +78,16 @@ class ServerManagerView {
// Default settings which should be respected
const settingOptions = {
trayIcon: true,
useManualProxy: false,
useSystemProxy: false,
useProxy: false,
showSidebar: true,
badgeOption: true,
startAtLogin: false,
startMinimized: false,
enableSpellchecker: true,
showNotification: true,
autoUpdate: true,
betaUpdate: false,
silent: false,
lastActiveTab: 0,
dnd: false,
dndPreviousSettings: {
showNotification: true,
silent: false
},
downloadsPath: `${app.getPath('downloads')}`,
showDownloadFolder: false
lastActiveTab: 0
};
// Platform specific settings
@@ -124,12 +95,6 @@ class ServerManagerView {
if (process.platform === 'win32') {
// Only available on Windows
settingOptions.flashTaskbarOnMessage = true;
settingOptions.dndPreviousSettings.flashTaskbarOnMessage = true;
}
if (process.platform === 'darwin') {
// Only available on macOS
settingOptions.dockBouncing = true;
}
for (const i in settingOptions) {
@@ -157,7 +122,7 @@ class ServerManagerView {
// Remove focus from the settings icon at sidebar bottom
this.$settingsButton.classList.remove('active');
} else {
this.openSettings('AddServer');
this.openSettings('Servers');
}
}
@@ -190,16 +155,11 @@ class ServerManagerView {
}
initActions() {
this.initDNDButton();
this.$dndButton.addEventListener('click', () => {
const dndUtil = DNDUtil.toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
});
this.$reloadButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.reload();
});
this.$addServerButton.addEventListener('click', () => {
this.openSettings('AddServer');
this.openSettings('Servers');
});
this.$settingsButton.addEventListener('click', () => {
this.openSettings('General');
@@ -215,16 +175,10 @@ class ServerManagerView {
});
});
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip, true);
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip);
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
}
initDNDButton() {
const dnd = ConfigUtil.getConfigItem('dnd', false);
this.toggleDNDButton(dnd);
}
getTabIndex() {
@@ -233,17 +187,9 @@ class ServerManagerView {
return currentIndex;
}
sidebarHoverEvent(SidebarButton, SidebarTooltip, addServer = false) {
sidebarHoverEvent(SidebarButton, SidebarTooltip) {
SidebarButton.addEventListener('mouseover', () => {
SidebarTooltip.removeAttribute('style');
// To handle position of add server tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
// This needs to handled only for the add server tooltip and not others.
if (addServer) {
const { top } = SidebarButton.getBoundingClientRect();
SidebarTooltip.style.top = top + 'px';
}
});
SidebarButton.addEventListener('mouseout', () => {
SidebarTooltip.style.display = 'none';
@@ -253,11 +199,6 @@ class ServerManagerView {
onHover(index, serverName) {
this.$serverIconTooltip[index].innerHTML = serverName;
this.$serverIconTooltip[index].removeAttribute('style');
// To handle position of servers' tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
const { top } = this.$serverIconTooltip[index].parentElement.getBoundingClientRect();
this.$serverIconTooltip[index].style.top = top + 'px';
}
onHoverOut(index) {
@@ -297,9 +238,6 @@ class ServerManagerView {
preload: false
})
}));
// To show loading indicator the first time a functional tab is opened, indicator is
// closed when the functional tab DOM is ready, handled in webview.js
this.$webviewsContainer.classList.remove('loaded');
this.activateTab(this.functionalTabs[tabProps.name]);
}
@@ -330,10 +268,10 @@ class ServerManagerView {
}
activateLastTab(index) {
// Open all the tabs in background, also activate the tab based on the index
this.activateTab(index);
// Save last active tab
// Open last active tab
ConfigUtil.setConfigItem('lastActiveTab', index);
// Open all the tabs in background
this.activateTab(index);
}
activateTab(index, hideOldTab = true) {
@@ -356,6 +294,7 @@ class ServerManagerView {
try {
this.tabs[index].webview.canGoBackButton();
} catch (err) {
console.log(err);
}
this.activeTabIndex = index;
@@ -373,7 +312,7 @@ class ServerManagerView {
webContents.send('toggle-sidebar', state);
});
ipcRenderer.on('toggle-silent', (event, state) => {
ipcRenderer.on('toogle-silent', (event, state) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
try {
@@ -386,31 +325,6 @@ class ServerManagerView {
}
});
});
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.toggleDNDButton(state);
ipcRenderer.send('forward-message', 'toggle-silent', newSettings.silent);
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-dnd', state, newSettings);
});
ipcRenderer.on('update-realm-icon', (event, serverURL, iconURL) => {
DomainUtil.getDomains().forEach((domain, index) => {
if (domain.url.includes(serverURL)) {
DomainUtil.saveServerIcon(iconURL).then(localIconUrl => {
const serverImgsSelector = `.tab .server-icons`;
const serverImgs = document.querySelectorAll(serverImgsSelector);
serverImgs[index].src = localIconUrl;
domain.icon = localIconUrl;
DomainUtil.db.push(`/domains[${index}]`, domain, true);
DomainUtil.reloadDB();
});
}
});
});
}
destroyTab(name, index) {
@@ -430,9 +344,6 @@ class ServerManagerView {
}
destroyView() {
// Show loading indicator
this.$webviewsContainer.classList.remove('loaded');
// Clear global variables
this.activeTabIndex = -1;
this.tabs = [];
@@ -480,12 +391,6 @@ class ServerManagerView {
}
}
// Toggles the dnd button icon.
toggleDNDButton(alert) {
this.$dndTooltip.textContent = (alert ? 'Turn Off' : 'Enable') + ' Do Not Disturb';
this.$dndButton.querySelector('i').textContent = alert ? 'notifications_off' : 'notifications';
}
registerIpcs() {
const webviewListeners = {
'webview-reload': 'reload',
@@ -535,7 +440,6 @@ class ServerManagerView {
this.loadProxy().then(() => {
if (showAlert) {
alert('Proxy settings saved!');
ipcRenderer.send('reload-full-app');
}
});
});
@@ -593,10 +497,6 @@ class ServerManagerView {
}
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
});
ipcRenderer.on('open-feedback-modal', () => {
feedbackHolder.classList.add('show');
});
}
}
@@ -610,7 +510,7 @@ window.onload = () => {
window.addEventListener('offline', () => {
reconnectUtil.clearState();
logger.log('No internet connection, you are offline.');
console.log('No internet connection, you are offline.');
});
// only start electron-connect (auto reload on change) when its ran

View File

@@ -1,10 +1,4 @@
const { remote } = require('electron');
const Logger = require('../utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
// Do not change this
const appId = 'org.zulip.zulip-electron';
@@ -34,8 +28,8 @@ function loadBots(sync = false) {
botsListLoaded = true;
})
.fail(error => {
logger.log('Load bots request failed: ', error.responseText);
logger.log('Load bots request status: ', error.statusText);
console.log('Request failed: ', error.responseText);
console.log('Request status: ', error.statusText);
});
if (sync) {
$.ajaxSetup({async: true});

View File

@@ -4,7 +4,6 @@ const {
remote: { app }
} = require('electron');
const params = require('../utils/params-util.js');
const DefaultNotification = require('./default-notification');
const { appId, loadBots } = require('./helpers');
@@ -20,8 +19,9 @@ if (process.platform === 'darwin') {
}
window.addEventListener('load', () => {
// Call this function only when user is logged in
// eslint-disable-next-line no-undef, camelcase
if (params.isPageParams() && page_params.realm_uri) {
if (page_params.realm_uri) {
loadBots();
}
});

View File

@@ -1,91 +0,0 @@
'use-strict';
const { dialog } = require('electron').remote;
const BaseComponent = require(__dirname + '/../../components/base.js');
const CertificateUtil = require(__dirname + '/../../utils/certificate-util.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class AddCertificate extends BaseComponent {
constructor(props) {
super();
this.props = props;
this._certFile = '';
}
template() {
return `
<div class="settings-card server-center certificates-card">
<div class="certificate-input">
<div>Organization URL :</div>
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="certificate-input">
<div>Custom CA's certificate file :</div>
<button id="add-certificate-button">Add</button>
</div>
</div>
`;
}
init() {
this.$addCertificate = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$addCertificate);
this.addCertificateButton = this.$addCertificate.querySelector('#add-certificate-button');
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0];
this.initListeners();
}
validateAndAdd() {
const certificate = this._certFile;
const serverUrl = this.serverUrl.value;
if (certificate !== '' && serverUrl !== '') {
const server = encodeURIComponent(DomainUtil.formatUrl(serverUrl));
const fileName = certificate.substring(certificate.lastIndexOf('/') + 1);
const copy = CertificateUtil.copyCertificate(server, certificate, fileName);
if (!copy) {
return;
}
CertificateUtil.setCertificate(server, fileName);
dialog.showMessageBox({
title: 'Success',
message: `Certificate saved!`
});
this.serverUrl.value = '';
} else {
dialog.showErrorBox('Error', `Please, ${serverUrl === '' ?
'Enter an Organization URL' : 'Choose certificate file'}`);
}
}
addHandler() {
const showDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'crt, pem', extensions: ['crt', 'pem'] }]
};
dialog.showOpenDialog(showDialogOptions, selectedFile => {
if (selectedFile) {
this._certFile = selectedFile[0] || '';
this.validateAndAdd();
}
});
}
initListeners() {
this.addCertificateButton.addEventListener('click', () => {
this.addHandler();
});
this.serverUrl.addEventListener('keypress', event => {
const EnterkeyCode = event.keyCode;
if (EnterkeyCode === 13) {
this.addHandler();
}
});
}
}
module.exports = AddCertificate;

View File

@@ -1,64 +0,0 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js');
const AddCertificate = require(__dirname + '/add-certificate.js');
class ConnectedOrgSection extends BaseSection {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">Connected organizations</div>
<div class="title" id="existing-servers">All the connected orgnizations will appear here.</div>
<div id="server-info-container"></div>
<div class="page-title">Add Custom Certificates</div>
<div id="add-certificate-container"></div>
</div>
`;
}
init() {
this.initServers();
}
initServers() {
this.props.$root.innerHTML = '';
const servers = DomainUtil.getDomains();
this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.getElementById('server-info-container');
this.$existingServers = document.getElementById('existing-servers');
const noServerText = 'All the connected orgnizations will appear here';
// Show noServerText if no servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? noServerText : '';
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.reloadApp
}).init();
}
this.$addCertificateContainer = document.getElementById('add-certificate-container');
this.initAddCertificate();
}
initAddCertificate() {
new AddCertificate({
$root: this.$addCertificateContainer
}).init();
}
}
module.exports = ConnectedOrgSection;

View File

@@ -0,0 +1,37 @@
'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

@@ -24,17 +24,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+Shift+S</span>)</div>
<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+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="dock-bounce-option" style= "display:${process.platform === 'darwin' ? '' : 'none'}">
<div class="setting-description">Bounce dock on new private message</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>
@@ -52,11 +48,7 @@ class GeneralSection extends BaseSection {
</div>
</div>
<div class="title">App Updates</div>
<div class="settings-card">
<div class="setting-row" id="autoupdate-option">
<div class="setting-description">Enable auto updates</div>
<div class="setting-control"></div>
</div>
<div class="settings-card">
<div class="setting-row" id="betaupdate-option">
<div class="setting-description">Get beta updates</div>
<div class="setting-control"></div>
@@ -77,43 +69,6 @@ class GeneralSection extends BaseSection {
<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">Advanced</div>
<div class="settings-card">
<div class="setting-row" id="show-download-folder">
<div class="setting-description">Show downloaded file in the file manager</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="download-folder">
<div class="setting-description">
Default download location
</div>
<button class="download-folder-button blue">Choose</button>
</div>
<div class="setting-row">
<div class="setting-description">
<div class="download-folder-path">${ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`)}</div>
</div>
</div>
</div>
<div class="title">Reset Application Data</div>
<div class="settings-card">
<div class="setting-row" id="resetdata-option">
@@ -131,30 +86,19 @@ class GeneralSection extends BaseSection {
this.updateTrayOption();
this.updateBadgeOption();
this.updateSilentOption();
this.autoUpdateOption();
this.betaUpdateOption();
this.updateUpdateOption();
this.updateSidebarOption();
this.updateStartAtLoginOption();
this.updateResetDataOption();
this.showDesktopNotification();
this.enableSpellchecker();
this.minimizeOnStart();
this.addCustomCSS();
this.showCustomCSSPath();
this.removeCustomCSS();
this.downloadFolder();
this.showDownloadFolder();
// Platform specific settings
// Flashing taskbar on Windows
if (process.platform === 'win32') {
this.updateFlashTaskbar();
}
// Dock bounce on macOS
if (process.platform === 'darwin') {
this.updateDockBouncing();
}
}
updateTrayOption() {
@@ -183,18 +127,6 @@ class GeneralSection extends BaseSection {
});
}
updateDockBouncing() {
this.generateSettingOption({
$element: document.querySelector('#dock-bounce-option .setting-control'),
value: ConfigUtil.getConfigItem('dockBouncing', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('dockBouncing');
ConfigUtil.setConfigItem('dockBouncing', newValue);
this.updateDockBouncing();
}
});
}
updateFlashTaskbar() {
this.generateSettingOption({
$element: document.querySelector('#flash-taskbar-option .setting-control'),
@@ -207,26 +139,14 @@ class GeneralSection extends BaseSection {
});
}
autoUpdateOption() {
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.autoUpdateOption();
}
});
}
betaUpdateOption() {
updateUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#betaupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('betaUpdate', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
ConfigUtil.setConfigItem('betaUpdate', newValue);
this.betaUpdateOption();
this.updateUpdateOption();
}
});
}
@@ -239,7 +159,7 @@ class GeneralSection extends BaseSection {
const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption();
currentBrowserWindow.send('toggle-silent', newValue);
currentBrowserWindow.send('toogle-silent', newValue);
}
});
}
@@ -312,22 +232,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', () => {
@@ -347,62 +251,6 @@ class GeneralSection extends BaseSection {
});
}
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');
});
}
downloadFolderDialog() {
const showDialogOptions = {
title: 'Select Download Location',
defaultId: 1,
properties: ['openDirectory']
};
dialog.showOpenDialog(showDialogOptions, selectedFolder => {
if (selectedFolder) {
ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
const downloadFolderPath = document.querySelector('.download-folder-path');
downloadFolderPath.innerText = selectedFolder[0];
}
});
}
downloadFolder() {
const downloadFolder = document.querySelector('#download-folder .download-folder-button');
downloadFolder.addEventListener('click', () => {
this.downloadFolderDialog();
});
}
showDownloadFolder() {
this.generateSettingOption({
$element: document.querySelector('#show-download-folder .setting-control'),
value: ConfigUtil.getConfigItem('showDownloadFolder', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('showDownloadFolder');
ConfigUtil.setConfigItem('showDownloadFolder', newValue);
this.showDownloadFolder();
}
});
}
}
module.exports = GeneralSection;

View File

@@ -8,7 +8,7 @@ class PreferenceNav extends BaseComponent {
this.props = props;
this.navItems = ['General', 'Network', 'AddServer', 'Organizations', 'Shortcuts'];
this.navItems = ['General', 'Network', 'Servers', 'Shortcuts'];
this.init();
}

View File

@@ -16,15 +16,11 @@ class NetworkSection extends BaseSection {
<div class="settings-pane">
<div class="title">Proxy</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="use-system-settings">
<div class="setting-description">Use system proxy settings (requires restart)</div>
<div class="setting-row" id="use-proxy-option">
<div class="setting-description">Connect servers through a proxy</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="use-manual-settings">
<div class="setting-description">Manual proxy configuration</div>
<div class="setting-control"></div>
</div>
<div class="manual-proxy-block">
<div class="setting-block">
<div class="setting-row" id="proxy-pac-option">
<span class="setting-input-key">PAC script</span>
<input class="setting-input-value" placeholder="e.g. foobar.com/pacfile.js"/>
@@ -55,7 +51,7 @@ class NetworkSection extends BaseSection {
this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
this.$proxySaveAction = document.getElementById('proxy-save-action');
this.$manualProxyBlock = this.props.$root.querySelector('.manual-proxy-block');
this.$settingBlock = this.props.$root.querySelector('.setting-block');
this.initProxyOption();
this.$proxyPAC.value = ConfigUtil.getConfigItem('proxyPAC', '');
@@ -72,54 +68,31 @@ class NetworkSection extends BaseSection {
}
initProxyOption() {
const manualProxyEnabled = ConfigUtil.getConfigItem('useManualProxy', false);
this.toggleManualProxySettings(manualProxyEnabled);
const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false);
this.toggleProxySettings(proxyEnabled);
this.updateProxyOption();
}
toggleManualProxySettings(option) {
toggleProxySettings(option) {
if (option) {
this.$manualProxyBlock.classList.remove('hidden');
this.$settingBlock.classList.remove('hidden');
} else {
this.$manualProxyBlock.classList.add('hidden');
this.$settingBlock.classList.add('hidden');
}
}
updateProxyOption() {
this.generateSettingOption({
$element: document.querySelector('#use-system-settings .setting-control'),
value: ConfigUtil.getConfigItem('useSystemProxy', false),
$element: document.querySelector('#use-proxy-option .setting-control'),
value: ConfigUtil.getConfigItem('useProxy', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('useSystemProxy');
const manualProxyValue = ConfigUtil.getConfigItem('useManualProxy');
if (manualProxyValue && newValue) {
ConfigUtil.setConfigItem('useManualProxy', !manualProxyValue);
this.toggleManualProxySettings(!manualProxyValue);
}
const newValue = !ConfigUtil.getConfigItem('useProxy');
ConfigUtil.setConfigItem('useProxy', newValue);
this.toggleProxySettings(newValue);
if (newValue === false) {
// Remove proxy system proxy settings
ConfigUtil.setConfigItem('proxyRules', '');
ipcRenderer.send('forward-message', 'reload-proxy', true);
// Reload proxy if the proxy is turned off
ipcRenderer.send('forward-message', 'reload-proxy', false);
}
ConfigUtil.setConfigItem('useSystemProxy', newValue);
this.updateProxyOption();
}
});
this.generateSettingOption({
$element: document.querySelector('#use-manual-settings .setting-control'),
value: ConfigUtil.getConfigItem('useManualProxy', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('useManualProxy');
const systemProxyValue = ConfigUtil.getConfigItem('useSystemProxy');
this.toggleManualProxySettings(newValue);
if (systemProxyValue && newValue) {
ConfigUtil.setConfigItem('useSystemProxy', !systemProxyValue);
}
ConfigUtil.setConfigItem('proxyRules', '');
ConfigUtil.setConfigItem('useManualProxy', newValue);
// Reload app only when turning manual proxy off, hence !newValue
ipcRenderer.send('forward-message', 'reload-proxy', !newValue);
this.updateProxyOption();
}
});

View File

@@ -2,7 +2,6 @@
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const shell = require('electron').shell;
class NewServerForm extends BaseComponent {
constructor(props) {
@@ -12,26 +11,19 @@ class NewServerForm extends BaseComponent {
template() {
return `
<div class="server-input-container">
<div class="title">Organization URL</div>
<div class="add-server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="connect">Connect</button>
<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"/>
</div>
<div class="server-info-row">
<div class="action blue server-save-action">
<i class="material-icons">add_box</i>
<span>Add</span>
</div>
</div>
</div>
<div class="server-center">
<div class="divider">
<hr class="left"/>OR<hr class="right" />
</div>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="open-create-org-link">Create a new organization</button>
</div>
</div>
</div>
`;
}
@@ -51,25 +43,17 @@ class NewServerForm extends BaseComponent {
}
submitFormHandler() {
this.$saveServerButton.children[0].innerHTML = 'Connecting...';
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[0].innerHTML = 'Connect';
this.$saveServerButton.children[1].innerHTML = 'Add';
alert(errorMessage);
});
}
openCreateNewOrgExternalLink() {
const link = 'https://zulipchat.com/new/';
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
initActions() {
this.$saveServerButton.addEventListener('click', () => {
this.submitFormHandler();
@@ -81,8 +65,6 @@ class NewServerForm extends BaseComponent {
this.submitFormHandler();
}
});
// open create new org link in default browser
this.openCreateNewOrgExternalLink();
}
}

View File

@@ -7,7 +7,6 @@ const Nav = require(__dirname + '/js/pages/preference/nav.js');
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js');
const NetworkSection = require(__dirname + '/js/pages/preference/network-section.js');
const ConnectedOrgSection = require(__dirname + '/js/pages/preference/connected-org-section.js');
const ShortcutsSection = require(__dirname + '/js/pages/preference/shortcuts-section.js');
class PreferenceView extends BaseComponent {
@@ -40,7 +39,7 @@ class PreferenceView extends BaseComponent {
handleNavigation(navItem) {
this.nav.select(navItem);
switch (navItem) {
case 'AddServer': {
case 'Servers': {
this.section = new ServersSection({
$root: this.$settingsContainer
});
@@ -52,12 +51,6 @@ class PreferenceView extends BaseComponent {
});
break;
}
case 'Organizations': {
this.section = new ConnectedOrgSection({
$root: this.$settingsContainer
});
break;
}
case 'Network': {
this.section = new NetworkSection({
$root: this.$settingsContainer
@@ -97,15 +90,6 @@ class PreferenceView extends BaseComponent {
ipcRenderer.on('toggletray', (event, state) => {
this.handleToggle('tray-option', state);
});
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.handleToggle('show-notification-option', newSettings.showNotification);
this.handleToggle('silent-option', newSettings.silent);
if (process.platform === 'win32') {
this.handleToggle('flash-taskbar-option', newSettings.flashTaskbarOnMessage);
}
});
}
}

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');
@@ -16,18 +16,19 @@ class ServerInfoForm extends BaseComponent {
<div class="settings-card">
<div class="server-info-left">
<img class="server-info-icon" src="${this.props.server.icon}"/>
</div>
<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>
</div>
</div>
<div class="server-info-right">
<div class="server-info-row server-url">
<span class="server-url-info" title="${this.props.server.url}">${this.props.server.url}</span>
<div class="server-info-row">
<input class="setting-input-value" disabled value="${this.props.server.url}"/>
</div>
<div class="server-info-row">
<div class="action red server-delete-action">
<span>Disconnect</span>
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
@@ -43,7 +44,6 @@ class ServerInfoForm extends BaseComponent {
initForm() {
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0];
this.$serverIcon = this.$serverInfoForm.getElementsByClassName('server-info-icon')[0];
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
this.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0];
this.props.$root.appendChild(this.$serverInfoForm);
@@ -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);
@@ -71,12 +71,7 @@ class ServerInfoForm extends BaseComponent {
this.$serverInfoAlias.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
this.$serverIcon.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
}
}
module.exports = ServerInfoForm;

View File

@@ -1,7 +1,10 @@
'use strict';
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) {
@@ -11,14 +14,13 @@ class ServersSection extends BaseSection {
template() {
return `
<div class="add-server-modal">
<div class="modal-container">
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">Add a Zulip organization</div>
<div id="new-server-container"></div>
</div>
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">Register or login to a Zulip organization to get started</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>
</div>
`;
}
@@ -29,10 +31,35 @@ class ServersSection extends BaseSection {
initServers() {
this.props.$root.innerHTML = '';
const servers = DomainUtil.getDomains();
this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.getElementById('server-info-container');
this.$existingServers = document.getElementById('existing-servers');
this.$newServerContainer = document.getElementById('new-server-container');
this.$newServerButton = document.getElementById('new-server-action');
this.$serverInfoContainer.innerHTML = servers.length ? '' : '';
// Show Existing servers if servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing organizations';
this.initNewServerForm();
this.$createOrganizationContainer = document.getElementById('create-organization-container');
this.initCreateNewOrganization();
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.reloadApp
}).init();
}
}
initCreateNewOrganization() {
new CreateOrganziation({
$root: this.$createOrganizationContainer
}).init();
}
initNewServerForm() {

View File

@@ -1,7 +1,6 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const shell = require('electron').shell;
class ShortcutsSection extends BaseSection {
constructor(props) {
@@ -24,10 +23,6 @@ class ShortcutsSection extends BaseSection {
<tr>
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
<td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
@@ -83,6 +78,14 @@ class ShortcutsSection extends BaseSection {
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td>Select All</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
<td>Find</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>G</kbd></td>
<td>Find Next</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<td>Emoji & Symbols</td>
@@ -118,7 +121,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td><kbd>${userOSKey}</kbd><kbd>S</kbd></td>
<td>Toggle Sidebar</td>
</tr>
<tr>
@@ -160,7 +163,6 @@ class ShortcutsSection extends BaseSection {
</table>
<div class="setting-control"></div>
</div>
<div class="settings-card tip"><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div>
`;
}
@@ -180,10 +182,6 @@ class ShortcutsSection extends BaseSection {
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
<td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
@@ -258,7 +256,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td><kbd>${userOSKey}</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td>
</tr>
<tr>
@@ -300,22 +298,13 @@ class ShortcutsSection extends BaseSection {
</table>
<div class="setting-control"></div>
</div>
<div class="tip"><b><i class="material-icons md-14">lightbulb_outline</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div>
`;
}
openHotkeysExternalLink() {
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
const externalCreateNewOrgEl = document.getElementById('open-hotkeys-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
init() {
this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMac() : this.templateWinLin();
this.openHotkeysExternalLink();
}
}

View File

@@ -1,21 +1,13 @@
'use strict';
const { ipcRenderer, shell } = require('electron');
const { ipcRenderer } = require('electron');
const SetupSpellChecker = require('./spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const LinkUtil = require(__dirname + '/utils/link-util.js');
const params = require(__dirname + '/utils/params-util.js');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
// eslint-disable-next-line camelcase
window.electron_bridge = require('./electron-bridge');
const logout = () => {
// Create the menu for the below
document.querySelector('.dropdown-toggle').click();
@@ -28,7 +20,7 @@ const shortcut = () => {
// Create the menu for the below
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
if (node.text.trim().toLowerCase() === 'keyboard shortcuts (?)') {
if (node.text.trim().toLowerCase() === 'keyboard shortcuts') {
node.click();
} else {
// Atleast click the dropdown
@@ -43,42 +35,21 @@ process.once('loaded', () => {
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', () => {
if (params.isPageParams()) {
// 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();
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (e) {
const url = $(this).attr('href');
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
// Open image in the default browser if image preview is unavailable
if (!$img[0]) {
shell.openExternal(window.location.origin + url);
}
// Open image in lightbox
lightbox.open($img);
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
});
@@ -89,10 +60,3 @@ window.addEventListener('beforeunload', () => {
SetupSpellChecker.unsubscribeSpellChecker();
});
// electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
document.addEventListener('keydown', event => {
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
}
});

View File

@@ -1,17 +0,0 @@
'use strict';
// This is a security fix. Following function prevents drag and drop event in the app
// so that attackers can't execute any remote code within the app
// It doesn't affect the compose box so that users can still
// use drag and drop event to share files etc
const preventDragAndDrop = () => {
const preventEvents = ['dragover', 'drop'];
preventEvents.forEach(dragEvents => {
document.addEventListener(dragEvents, event => {
event.preventDefault();
});
});
};
preventDragAndDrop();

View File

@@ -3,12 +3,6 @@
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const Logger = require(__dirname + '/utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class SetupSpellChecker {
init() {
@@ -22,7 +16,7 @@ class SetupSpellChecker {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
logger.error(err);
console.log(err);
}
}

View File

@@ -98,7 +98,7 @@ const renderNativeImage = function (arg) {
return Promise.resolve()
.then(() => renderCanvas(arg))
.then(canvas => {
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPNG();
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPng();
return Promise.resolve(nativeImage.createFromBuffer(pngData, config.pixelRatio));
});
};

View File

@@ -1,86 +0,0 @@
'use strict';
const { app, dialog } = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const { initSetUp } = require('./default-util');
initSetUp();
const logger = new Logger({
file: `certificate-util.log`,
timestamp: true
});
let instance = null;
const certificatesDir = `${app.getPath('userData')}/certificates`;
class CertificateUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
return instance;
}
getCertificate(server, defaultValue = null) {
this.reloadDB();
const value = this.db.getData('/')[server];
if (value === undefined) {
return defaultValue;
} else {
return value;
}
}
// Function to copy the certificate to userData folder
copyCertificate(server, location, fileName) {
let copied = false;
const filePath = `${certificatesDir}/${fileName}`;
try {
fs.copyFileSync(location, filePath);
copied = true;
} catch (err) {
dialog.showErrorBox(
'Error saving certificate',
'We encountered error while saving the certificate.'
);
logger.error('Error while copying the certificate to certificates folder.');
logger.error(err);
}
return copied;
}
setCertificate(server, fileName) {
const filePath = `${certificatesDir}/${fileName}`;
this.db.push(`/${server}`, filePath, true);
this.reloadDB();
}
removeCertificate(server) {
this.db.delete(`/${server}`);
this.reloadDB();
}
reloadDB() {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/certificates.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 the certificate.'
);
logger.error('Error while JSON parsing certificates.json: ');
logger.error(err);
}
}
this.db = new JsonDB(settingsJsonPath, true, true);
}
}
module.exports = new CertificateUtil();

View File

@@ -48,12 +48,6 @@ class ConfigUtil {
return value;
}
}
// This function returns whether a key exists in the configuration file (settings.json)
isConfigItemExists(key) {
this.reloadDB();
const value = this.db.getData('/')[key];
return (value !== undefined);
}
setConfigItem(key, value) {
this.db.push(`/${key}`, value, true);
@@ -66,7 +60,7 @@ class ConfigUtil {
}
reloadDB() {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/settings.json');
const settingsJsonPath = path.join(app.getPath('userData'), '/settings.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
JSON.parse(file);
@@ -75,11 +69,10 @@ class ConfigUtil {
fs.unlinkSync(settingsJsonPath);
dialog.showErrorBox(
'Error saving settings',
'We encountered an error while saving the settings.'
'We encountered error while saving current settings.'
);
logger.error('Error while JSON parsing settings.json: ');
logger.error(err);
logger.reportSentry(err);
}
}
this.db = new JsonDB(settingsJsonPath, true, true);

View File

@@ -10,8 +10,6 @@ if (process.type === 'renderer') {
const zulipDir = app.getPath('userData');
const logDir = `${zulipDir}/Logs/`;
const certificatesDir = `${zulipDir}/certificates/`;
const configDir = `${zulipDir}/config/`;
const initSetUp = () => {
// if it is the first time the app is running
// create zulip dir in userData folder to
@@ -24,51 +22,6 @@ const initSetUp = () => {
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
if (!fs.existsSync(certificatesDir)) {
fs.mkdirSync(certificatesDir);
}
// Migrate config files from app data folder to config folder inside app
// data folder. This will be done once when a user updates to the new version.
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir);
const domainJson = `${zulipDir}/domain.json`;
const certificatesJson = `${zulipDir}/certificates.json`;
const settingsJson = `${zulipDir}/settings.json`;
const updatesJson = `${zulipDir}/updates.json`;
const windowStateJson = `${zulipDir}/window-state.json`;
const configData = [
{
path: domainJson,
fileName: `domain.json`
},
{
path: certificatesJson,
fileName: `certificates.json`
},
{
path: settingsJson,
fileName: `settings.json`
},
{
path: updatesJson,
fileName: `updates.json`
}
];
configData.forEach(data => {
if (fs.existsSync(data.path)) {
fs.copyFileSync(data.path, configDir + data.fileName);
fs.unlinkSync(data.path);
}
});
// window-state.json is only deleted not moved, as the electron-window-state
// package will recreate the file in the config folder.
if (fs.existsSync(windowStateJson)) {
fs.unlinkSync(windowStateJson);
}
}
setupCompleted = true;
}
};

View File

@@ -1,41 +0,0 @@
'use strict';
const ConfigUtil = require(__dirname + '/config-util.js');
function toggle() {
const dnd = !ConfigUtil.getConfigItem('dnd', false);
const dndSettingList = ['showNotification', 'silent'];
if (process.platform === 'win32') {
dndSettingList.push('flashTaskbarOnMessage');
}
let newSettings;
if (dnd) {
const oldSettings = {};
newSettings = {};
// Iterate through the dndSettingList.
for (const settingName of dndSettingList) {
// Store the current value of setting.
oldSettings[settingName] = ConfigUtil.getConfigItem(settingName);
// New value of setting.
newSettings[settingName] = (settingName === 'silent');
}
// Store old value in oldSettings.
ConfigUtil.setConfigItem('dndPreviousSettings', oldSettings);
} else {
newSettings = ConfigUtil.getConfigItem('dndPreviousSettings');
}
for (const settingName of dndSettingList) {
ConfigUtil.setConfigItem(settingName, newSettings[settingName]);
}
ConfigUtil.setConfigItem('dnd', dnd);
return {dnd, newSettings};
}
module.exports = {
toggle
};

View File

@@ -5,14 +5,8 @@ const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
const escape = require('escape-html');
const Logger = require('./logger-util');
const CertificateUtil = require(__dirname + '/certificate-util.js');
const ProxyUtil = require(__dirname + '/proxy-util.js');
const ConfigUtil = require(__dirname + '/config-util.js');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
@@ -110,27 +104,7 @@ class DomainUtil {
domain = this.formatUrl(domain);
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
let certificateLocation = '';
if (certificate) {
// To handle case where certificate has been moved from the location in certificates.json
try {
certificateLocation = fs.readFileSync(certificate);
} catch (err) {
logger.warn('Error while trying to get certificate: ' + err);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
// Add proxy as a parameter if it sbeing used.
const checkDomain = {
url: domain + '/static/audio/zulip.ogg',
ca: (certificateLocation) ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : ''
};
const checkDomain = domain + '/static/audio/zulip.ogg';
const serverConf = {
icon: defaultIconUrl,
@@ -140,16 +114,21 @@ 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'
];
// If the domain contains following strings we just bypass the server
const whitelistDomains = [
'zulipdev.org'
];
// make sure that error is an error or string not undefined
// make sure that error is a error or string not undefined
// so validation does not throw error.
error = error || '';
const certsError = error.toString().includes('certificate');
if (!error && response.statusCode < 400) {
// Correct
this.getServerSettings(domain).then(serverSettings => {
@@ -157,7 +136,7 @@ class DomainUtil {
}, () => {
resolve(serverConf);
});
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError) {
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError.indexOf(error.toString()) >= 0) {
if (silent) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
@@ -165,19 +144,15 @@ class DomainUtil {
resolve(serverConf);
});
} else {
// Report error to sentry to get idea of possible certificate errors
// users get when adding the servers
logger.reportSentry(new Error(error));
const certErrorMessage = `Do you trust certificate from ${domain}? \n ${error}`;
const certErrorDetail = `The organization you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\nIf you have a valid certificate please add it from Settings>Organizations and try to add the organization again.
\nUnless you have a good reason to believe otherwise, you should not proceed.
\nYou can click here if you'd like to proceed with the connection.`;
const certErrorDetail = `The server you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\n Unless you have a good reason to believe otherwise, you should not proceed.
\n You can click here if you'd like to proceed with the connection.`;
dialog.showMessageBox({
type: 'warning',
buttons: ['Yes', 'No'],
defaultId: 1,
defaultId: 0,
message: certErrorMessage,
detail: certErrorDetail
}, response => {
@@ -194,8 +169,7 @@ class DomainUtil {
}
} else {
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \
\n (1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server \
\n (4) the server has a valid certificate, you can add custom certificates in Settings>Organizations`;
\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);
}
});
@@ -203,13 +177,9 @@ class DomainUtil {
}
getServerSettings(domain) {
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverSettingsOptions = {
url: domain + '/api/v1/server_settings',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : ''
};
const serverSettingsUrl = domain + '/api/v1/server_settings';
return new Promise((resolve, reject) => {
request(serverSettingsOptions, (error, response) => {
request(serverSettingsUrl, (error, response) => {
if (!error && response.statusCode === 200) {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
@@ -218,7 +188,7 @@ class DomainUtil {
// Following check handles both the cases
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
url: data.realm_uri,
alias: escape(data.realm_name)
alias: data.realm_name
});
}
} else {
@@ -229,36 +199,25 @@ class DomainUtil {
}
saveServerIcon(url) {
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverIconOptions = {
url,
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : ''
};
// The save will always succeed. If url is invalid, downgrade to default icon.
return new Promise(resolve => {
const filePath = this.generateFilePath(url);
const file = fs.createWriteStream(filePath);
try {
request(serverIconOptions).on('response', response => {
request(url).on('response', response => {
response.on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
});
response.pipe(file).on('finish', () => {
resolve(filePath);
});
}).on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
});
} catch (err) {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
}
});
@@ -276,7 +235,7 @@ class DomainUtil {
}
reloadDB() {
const domainJsonPath = path.join(app.getPath('userData'), 'config/domain.json');
const domainJsonPath = path.join(app.getPath('userData'), '/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
@@ -290,7 +249,6 @@ class DomainUtil {
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
logger.reportSentry(err);
}
}
this.db = new JsonDB(domainJsonPath, true, true);

View File

@@ -19,26 +19,7 @@ class LinkUtil {
const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl);
const sameDomainUrl = (currentDomain === newDomain || newUrl === currentUrl + '/');
const isUploadsUrl = newUrl.includes(currentUrl + '/user_uploads/');
const isInternalUrl = newUrl.includes('/#narrow') || isUploadsUrl;
return {
isInternalUrl: sameDomainUrl && isInternalUrl,
isUploadsUrl
};
}
isImage(url) {
// test for images extension as well as urls like .png?s=100
const isImageUrl = /\.(bmp|gif|jpg|jpeg|png|webp)\?*.*$/i;
return isImageUrl.test(url);
}
isPDF(url) {
// test for pdf extension
const isPDFUrl = /\.(pdf)\?*.*$/i;
return isPDFUrl.test(url);
return (currentDomain === newDomain) && newUrl.includes('/#narrow');
}
}

View File

@@ -52,7 +52,7 @@ class LinuxUpdateUtil {
}
reloadDB() {
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/config/updates.json');
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/updates.json');
try {
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
JSON.parse(file);
@@ -61,7 +61,7 @@ class LinuxUpdateUtil {
fs.unlinkSync(linuxUpdateJsonPath);
dialog.showErrorBox(
'Error saving update notifications.',
'We encountered an error while saving the update notifications.'
'We encountered error while saving update notifications.'
);
logger.error('Error while JSON parsing updates.json: ');
logger.error(err);

View File

@@ -2,10 +2,8 @@ const NodeConsole = require('console').Console;
const fs = require('fs');
const isDev = require('electron-is-dev');
const { initSetUp } = require('./default-util');
const { sentryInit, captureException } = require('./sentry-util');
initSetUp();
sentryInit();
let app = null;
if (process.type === 'renderer') {
app = require('electron').remote.app;
@@ -84,10 +82,6 @@ class Logger {
`${date.getMinutes()}:${date.getSeconds()}`;
return timestamp;
}
reportSentry(err) {
captureException(err);
}
}
module.exports = Logger;

View File

@@ -1,15 +0,0 @@
// This util function returns the page params if they're present else returns null
function isPageParams() {
let webpageParams = null;
try {
// eslint-disable-next-line no-undef, camelcase
webpageParams = page_params;
} catch (err) {
webpageParams = null;
}
return webpageParams;
}
module.exports = {
isPageParams
};

View File

@@ -1,130 +0,0 @@
'use strict';
const url = require('url');
const ConfigUtil = require('./config-util.js');
let instance = null;
class ProxyUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
return instance;
}
// Return proxy to be used for a particular uri, to be used for request
getProxy(uri) {
uri = url.parse(uri);
const proxyRules = ConfigUtil.getConfigItem('proxyRules', '').split(';');
// If SPS is on and system uses no proxy then request should not try to use proxy from
// environment. NO_PROXY = '*' makes request ignore all environment proxy variables.
if (proxyRules[0] === '') {
process.env.NO_PROXY = '*';
return;
}
const proxyRule = {};
if (uri.protocol === 'http:') {
proxyRules.forEach(proxy => {
if (proxy.includes('http=')) {
proxyRule.hostname = proxy.split('http=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('http=')[1].trim().split(':')[1];
}
});
return proxyRule;
}
if (uri.protocol === 'https:') {
proxyRules.forEach(proxy => {
if (proxy.includes('https=')) {
proxyRule.hostname = proxy.split('https=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('https=')[1].trim().split(':')[1];
}
});
return proxyRule;
}
}
resolveSystemProxy(mainWindow) {
const page = mainWindow.webContents;
const ses = page.session;
const resolveProxyUrl = 'www.google.com';
// Check HTTP Proxy
const httpProxy = new Promise(resolve => {
ses.resolveProxy('http://' + resolveProxyUrl, proxy => {
let httpString = '';
if (proxy !== 'DIRECT') {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
// for all other HTTP or direct url:port both uses PROXY
if (proxy.includes('PROXY') || proxy.includes('HTTPS')) {
httpString = 'http=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(httpString);
});
});
// Check HTTPS Proxy
const httpsProxy = new Promise(resolve => {
ses.resolveProxy('https://' + resolveProxyUrl, proxy => {
let httpsString = '';
if (proxy !== 'DIRECT' || proxy.includes('HTTPS')) {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
// for all other HTTP or direct url:port both uses PROXY
if (proxy.includes('PROXY' || proxy.includes('HTTPS'))) {
httpsString += 'https=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(httpsString);
});
});
// Check FTP Proxy
const ftpProxy = new Promise(resolve => {
ses.resolveProxy('ftp://' + resolveProxyUrl, proxy => {
let ftpString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('PROXY')) {
ftpString += 'ftp=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(ftpString);
});
});
// Check SOCKS Proxy
const socksProxy = new Promise(resolve => {
ses.resolveProxy('socks4://' + resolveProxyUrl, proxy => {
let socksString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('SOCKS5')) {
socksString += 'socks=' + proxy.split('SOCKS5')[1] + ';';
} else if (proxy.includes('SOCKS4')) {
socksString += 'socks=' + proxy.split('SOCKS4')[1] + ';';
} else if (proxy.includes('PROXY')) {
socksString += 'socks=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(socksString);
});
});
Promise.all([httpProxy, httpsProxy, ftpProxy, socksProxy]).then(values => {
let proxyString = '';
values.forEach(proxy => {
proxyString += proxy;
});
ConfigUtil.setConfigItem('systemProxyRules', proxyString);
const useSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
if (useSystemProxy) {
ConfigUtil.setConfigItem('proxyRules', proxyString);
}
});
}
}
module.exports = new ProxyUtil();

View File

@@ -1,10 +1,4 @@
const isOnline = require('is-online');
const Logger = require('./logger-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
class ReconnectUtil {
constructor(serverManagerView) {
@@ -19,12 +13,12 @@ class ReconnectUtil {
pollInternetAndReload() {
const pollInterval = setInterval(() => {
this._checkAndReload()
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
}, 1500);
}
@@ -32,24 +26,22 @@ class ReconnectUtil {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
isOnline()
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
logger.log('You\'re back online.');
return resolve(true);
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
console.log('You\'re back online.');
return resolve(true);
}
logger.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = `
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);
});
return resolve(false);
});
} else {
return resolve(true);
}

View File

@@ -1,16 +0,0 @@
const { init, captureException } = require('@sentry/electron');
const isDev = require('electron-is-dev');
const sentryInit = () => {
if (!isDev) {
init({
dsn: 'SENTRY_DSN',
sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
});
}
};
module.exports = {
sentryInit,
captureException
};

View File

@@ -9,48 +9,39 @@
</head>
<body>
<div id="content">
<div id="content" class="loading">
<div class="popup">
<span class="popuptext hidden" id="fullscreen-popup"></span>
</div>
<div id="sidebar" class="toggle-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>
<div id="sidebar" class="toggle-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">
<div class="action-button" id="reload-action">
<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>
</div>
<span id="add-server-tooltip" style="display:none">Add organization</span>
</div>
</div>
<div id="actions-container">
<div class="action-button" id="dnd-action">
<i class="material-icons md-48">notifications</i>
<span id="dnd-tooltip" style="display:none">Do Not Disturb</span>
</div>
<div class="action-button" id="reload-action">
<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>
</div>
<div id="main-container">
<div id="webviews-container"></div>
</div>
</div>
<div id="main-container">
<div id="webviews-container"></div>
</div>
</div>
<div id="feedback-modal">
<send-feedback></send-feedback>
</div>
</body>
<script src="js/main.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

View File

@@ -17,6 +17,5 @@
<div id="reconnect">Try now</div>
</div>
</body>
<script src="js/pages/network.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
<script src="js/pages/network.js"></script>
</html>

View File

@@ -13,5 +13,4 @@
</div>
</body>
<script src="js/pages/preference/preference.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

Binary file not shown.

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

@@ -6,9 +6,9 @@ os: Previous Visual Studio 2015
cache:
- node_modules
install:
- ps: Install-Product node 8 x64
- ps: Install-Product node 6 x64
- git reset --hard HEAD
- npm install npm -g
- node --version
@@ -21,4 +21,4 @@ build: off
test_script:
- npm run test
# - npm run test-e2e
- npm run test-e2e

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -1,722 +0,0 @@
# Version History
All notable changes to the Zulip desktop app are documented in this file.
### v2.3.6 --2018-08-27
**New features**:
* Add proxy details while validating a server. This fixes the server validating issue for users who are using the proxy settings.
**Fixes**:
* Fix youtube video not playing in lightbox.
* Fix realm name not escaped properly.
<hr>
### v2.3.5 --2018-08-03
**New features**:
* Add a setting option to show downloaded file in file manager.
* Added electron bridge to communicate with webapp in real time.
**Fixes**:
* Fix failing attached file downloads.
* Fix page_params error.
* gulpfile: Update syntax and methods for gulp v4.x.
<hr>
### v2.3.4-beta --2018-07-24
**Fixes**:
* Fix downloading functionality of file attachments.
* Fix null of downloadPath when settings.json fails.
<hr>
### v2.3.3 --2018-07-14
**Enhancements**:
* Add dock bounce effect on macOS
* Add a setting option to use the system proxy settings
* Add support for self/custom signed certificate
* Add Sentry support to get the bug reports
* Show a notification when a user clicks on file attachments and open the same in default native app
**Fixes**:
* Fix auto-updates on Windows
* Fix image attachments not opening up in the app
* Security fix - Do proper HTML escaping for server data to avoid XSS attacks
* Other minor fixes
**Updated dependencies**:
electron-builder: v20.20.4
electron-updater: v2.23.3
<hr>
### v2.3.2 --2018-05-28
This is a quick release since we recently updated the certificate for code signing the app and we want to make sure the previous versions of the app get auto-updates without any conflicts.
<hr>
### v2.3.1 --2018-05-23
**Enhancements**:
* Add a new setting to disable auto-updates
* Add a menu item to check for updates manually
* Other minor improvements
**Fixes**:
* Fix app not rendering colors properly
* **Security fix** - Do proper HTML escaping for server data to avoid XSS attacks
* Fix loading indicator when server is loaded
* Other minor fixes
**Updated dependencies**:
electron - `v2.0.1`
electron-builder - `v20.13.4`
electron-updater - `v2.21.10`
<hr>
### v2.2.0-beta --2018-05-08
**Enhancements**:
* Added do not disturb feature
* New DMG installer image
* Minor improvements
**Fixes**:
* Fix download functionality of file attachment links
* Fix tray icon not showing unread counts
**Updated dependencies**:
electron - `v2.0.0`
electron-builder - `v2.21.10`
electron-updater - `v2.21.8`
<hr>
### v2.0.0 -- 2018-04-20
**Enhancements**:
* [Snap](https://snapcraft.io/zulip) support for Linux
* Add an option to download the file attachments instead of opening it in the browser
* Open image link in webapp lightbox
* Add scrollbar for list of organizations on overflow
* Better report issue UX
* Add F5 shortcut for reloading the app
* Responsive UI for connected orgs in smaller window sizes
* Minor improvements
**Fixes**:
* Remove unused shortcuts
* Update toggle sidebar shortcut to CMD/CTRL+SHIFT+S
* Warning dialog box for Reset App Settings
* Fix reinstall script for working across all platforms
* Other minor fixes
**Updated dependencies**:
* Update electron-builder to `v20.8.1`
<hr>
### v1.9.0 --2018-03-23
**Enhancements**:
* Major UI changes in the add new server and setting page
* Some users wanted to change the look of the Zulip. Now you have the power. Feel free to add your own CSS using the all-new setting option **Add Custom CSS**
* Added i18n locale helper script. Internalization is coming in the next release
* Added **What's new** in `help` submenu so that you can see all the latest changes in the app
* Other small improvements
**Fixes**:
* Add Zulip binary link to the bin. It was a regression we missed somehow. You can now start the app using `zulip` from a terminal [Linux]
* Fix app settings overriding issue. This bug was unnoticed from `v1.4.0`. The bug was overriding the app settings which was pretty bad. Don't worry this works as expected now
* Prevent drag and drop events to avoid few security risks
* Do not allow running insecure contents in the app
* The ugly white flickering in the webview is now fixed
* Fix position of left-sidebar on toggle
* Fix actions tooltip not visible on hover
* Other small fixes
**Updated dependencies**:
* electron - `v1.8.4`
* Spectron - `v3.8.0`
<hr>
### v1.8.2 --2018-02-27
**Enhancements**:
* Add bots mention support to reply option
* Show a notification when a new update available [Linux]
* Add back button in left-sidebar
* Add a Loading indication for new server button
* Improve help menu
* Add menu-item to reveal app logs in file manager
* Setting page and left-sidebar UI improvements
* Other minor improvments
**Fixes**:
* Fix broken last active tab
* Fix Zoom In shortcut
* Check whether the internet is working before reloading
* New organization link overlapping existing servers
* Disable electron-connect when it's run from npm start
* Do not auto-reload app when the system comes back from sleep
* Only toggle the setting state if the element is present
* Other minor fixes
**Updated dependencies**:
* electron - `v1.8.2`
<hr>
### v1.8.1 --2018-01-19
**Enhancements**:
* Reply from notifications [macOS]
* Add a setting option to start the app in the background
* 32-bit Debian installer
* 32-bit AppImage installer
* Automatically add apt-repo and GPG public keys on installing Debian file [Linux]
* Remove app data, config files etc on uninstalling Debian installer [Linux]
* Add logger utility for debugging that makes easier to sharing the apps
**Fixes**:
* Handle corrupted config files
* Reload full app on system hibernation
* Load default icon if organization icon is not available
* Focus app when a notification is triggered
* Reflect changes in the preference page (#362)
* Set the default value of flash taskbar setting [Windows]
* Rename Zoom In keyboard shortcut
* Fix checkDomain, so it checks all error codes
* Disable hardware acceleration to decrease the load on GPU
* Minor bug fixes
**Updated dependencies**:
* electron - `v1.7.10`
* electron-builder - `v19.53.6`
* electron-updater - `v2.18.2`
<hr>
### v1.7.0 --2017-11-24
**Enhancements**:
* Updated to Electron `v1.6.15`, electron-builder `v19.46.4`
* Improved setting page for new users
* On the developer side, we have added few tests to make sure app doesn't fail
* Minor improvements
<hr>
### 1.6.0-beta --2017-11-16
**Enhancements**:
* Added setting option to enable/disable spellcheck
* Added setting option to control Windows taskbar flashing [Windows]
* Auto-updates for Linux [AppImage]
* Better tray icons for retina display
* Reset app settings from Menu item
* Show detailed error message on invalid Zulip server
**Fixes**:
* Fixed a bug which was caused by app's shortcuts. From now on our shortcuts won't hijack other apps shortcuts
* Removed [electron-localshortcut](https://github.com/parro-it/electron-localshortcut) completely. Now we only depends on menu accelerators for keyboard shortcuts
* Handle certificate issue properly
* Other minor fixes
<hr>
### v1.5.0 --2017-10-11
**Enhancements**:
* Added an option to clear app data
* Added an option to show/hide desktop notifications
* Redesigned setting page
* Red dot over dock icon for PM [macOS]
* Show server-info on hovering the server icon
**Fixes**:
* Fetch correct organization icon from server_settings API
* Minor improvements
<hr>
### v1.4.0 --2017-09-04
**Enhancements**:
* Added proxy support
* Added setting option for badges
* Start app at login setting option
* Added app category for Linux
* Minor improvements in settings page
* apt-repo on bintray for linux - [here](https://bintray.com/zulip/debian/zulip-elec)
**Fixes**
* Fixed auto-updates
* Better warning message on certificate error
* Don't allow duplicate servers
<hr>
### 1.3.0-beta -- 2017-08-09
**Enhancements**:
* Added back/forward option under `History` submenu
* Added taskbar overlay icon [Windows]
* Added `Window` submenu by which users can switch to other servers
* Added flashing taskbar icon on incoming message [Windows]
* Option to hide the left sidebar
* Staged rollouts
* Minor style improvements
**Fixes**
* Handle focus event properly
* Open external links in default browser
* Allow user to change installation directory
* Fixed server-icon path error [Windows]
* Fixed server validation error
* Fetch server details from Zulip api
* Added tooltip in left sidebar
* Added publisher name in windows installer
**Updated dependencies**:
* electron - `v1.6.11`
* spellchecker - `v1.2.0`
* electron-builder - `v19.9.1`
<hr>
### v1.2.0-beta -- 2017-07-12
**Enhancements**:
* Added new setting page
* Setting for beta-updates. You can opt in for beta updates via selecting it from setting page
* Added Keyboard shortcuts to switch between multiple servers
* Desktop notifications support on Windows
**Fixes**
* Unregister keyboard shortcuts on windows close
* Show warning dialog while deleting server
* Allow server which is signed by root cert
* Other minor bug fixes
<hr>
### v1.1.0-beta --2017-06-23
**Enhancements**:
* New network error page. App will auto-reload once the network comes back
* Quit shortcut - CTRL+Q [Windows]
* Improved preferences
**Fixes**:
* Fixed power-monitor module error
* Fixed code signing error [macOS]
<hr>
### v1.0.0-beta --2017-06-21
**Enhancements**:
* Sign in to multiple teams
* Windows 7 desktop notification support
* Show badge count for each Zulip server
* Toggle window on clicking tray icon [Windows]
* Windows installer is properly code signed using [DigiCert](https://www.digicert.com/) certificate. You may get a warning regarding the certificate, please make it false positive
**Fixes**:
* Close/hide app properly
* Keep app running in background on clicking X
<hr>
### v0.5.10 --2017-05-12
**Enhancements**:
* Added self-signed server support
* Unread message counts in tray icon
<hr>
### v0.5.9 --2017-04-12
**Enhancements**:
* Properly signed app for macOS
* Toggle tray icon
* Better error handling when no internet connenction
**Fixes**:
* Fixed permission issue on windows
* Fixed wrong keyboard shortcuts
<hr>
### v0.5.8 --2017-02-13
**Enhacements**:
Smaller Windows installer size
<hr>
### v0.5.7 --2017-02-08
Minor improvements
<hr>
### v0.5.6 --2017-02-07
**Enhacements**:
- Using NSIS instead of [Squirrel.Windows](https://github.com/Squirrel/Squirrel.Windows) on Windows
- Autoupdates: now using [electron-updater](https://www.npmjs.com/package/electron-updater)
- Removed nuts dependency
**Fixes**:
- Windows installer error
- Compile and build error in native node modules + spellchecker [Windows]
- Missing icon on installing [Windows]
<hr>
### v0.5.4 --2017-01-09
**Fixes**:
- Fixed :
- Auto-updates
- Spellchecker
- Zooming functionality
- Removed unused node modules
- Using stable version for node modules
- Added icon for AppImage
<hr>
### v0.5.3 --2016-12-24
**Enhancements**:
- 🎉 Added automatic update support on Windows
**Fixes**:
- Fixed javascript error when electron has been left running and try to right-click
- Fixed error - Unregister Spellchecker while window is closed
- Fixed - Keyboard shortcuts not getting unregistered
- Minor UI Changes
<hr>
### v0.5.2 --2016-12-13
**Enhancements**:
- ⚡️ Added automatic app updates for macOS
- Added windows installer for 32 bit machine
- Better tray icons
**Fixes**:
- Blurry Unity icon fixed
<hr>
### v0.5.1 --2016-11-23
**Enhacements**:
- Added Spellchecker support with correct spell suggestions
- Added Code-Signing on MAC
- Added Win/linux/Mac installers
- Added Continuous Integration on travis and appveyor
- Open internal links in app only and external in default browser
- Better icons
- Electron version updated to `v1.4.7`
- Using two package.json structure
- Node integration disabled in main window due to jquery error
- Now using electron-builder for packaging instead of electron-packager
- Removed electron-context-menu and simple-spellchecker dependency
**Fixes**:
- Better error handling when user enters wrong zulip server
- Fixed close Zulip window JavaScript error
- Fixed opening image in default browser
- Fixed native notifications issue on linux
<hr>
### v0.0.1-alpha -- 2016-08-31
**Enhacements**:
* Added DMG installer for macOS

View File

@@ -15,7 +15,6 @@ To build and run the app from source, you'll need the following:
* [Python](https://www.python.org/downloads/release/python-2713/)
(v2.7.x recommended)
* A C++ compiler compatible with C++11
* Linux users also need [Snapcraft](https://snapcraft.io/)
* Development headers for the libXext, libXtst, and libxkbfile libraries
### Debian/Ubuntu and friends
@@ -26,7 +25,7 @@ manager (see [here][nodesource-install] for more on the first command):
```sh
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
$ sudo apt install git nodejs python build-essential snapcraft libxext-dev libxtst-dev libxkbfile-dev libgconf-2-4
$ sudo apt install git nodejs python build-essential libxext-dev libxtst-dev libxkbfile-dev
```
[nodesource-install]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
@@ -77,7 +76,7 @@ This command will produce distributable packages or installers for the
operating system you're running on:
* on Windows, a Windows installer file
* on macOS, a `.dmg` file
* on Linux, a plain `.zip` file as well as a `.deb` file, `.snap` file and an
* on Linux, a plain `.zip` file as well as a `.deb` file and an
`AppImage` file.
To generate all three types, you will need all three operating
systems.

View File

@@ -10,11 +10,11 @@ gulp.task('dev', () => {
// Start browser process
electron.start();
// Restart browser process
gulp.watch('app/main/*.js', gulp.series('restart:browser'));
gulp.watch('app/main/*.js', ['restart:browser']);
// Reload renderer process
gulp.watch('app/renderer/css/*.css', gulp.series('reload:renderer'));
gulp.watch('app/renderer/*.html', gulp.series('reload:renderer'));
gulp.watch('app/renderer/js/**/*.js', gulp.series('reload:renderer'));
gulp.watch('app/renderer/css/*.css', ['reload:renderer']);
gulp.watch('app/renderer/*.html', ['reload:renderer']);
gulp.watch('app/renderer/js/**/*.js', ['reload:renderer']);
});
gulp.task('restart:browser', done => {
@@ -36,4 +36,4 @@ gulp.task('test-e2e', () => {
}));
});
gulp.task('default', gulp.parallel('dev', 'test-e2e'));
gulp.task('default', ['dev', 'test-e2e']);

105
help.md Normal file
View File

@@ -0,0 +1,105 @@
# User Guide
> Welcome! This guide will walk you through the basics of using Zulip Desktop.
## Get Zulip Desktop
## Connect to a Server
### Connect through a proxy
It's possible to connect to your server through a proxy.
You can enter the proxy settings in the `Network` section of App Settings.
There are three fields provided:
* `PAC script` - The URL associated with the PAC file.
* `Proxy rules` - Rules indicating which proxies to use.
* `Proxy bypass rules` - Rules indicating which URLs should
bypass the proxy settings.
For a typical setup where internet access is required to use an HTTP proxy,
but URLs on the local network should be accessed directly, configure as follows:
`Proxy rules = proxy.example.com`
Your HTTP proxy server
`Proxy bypass rules = *.example.com;10.0.0.0/8`
Directly connect to your own domain and private IP subnet
for more complex setups, read below to configure complex proxy rules and proxy bypass rules.
### Sets the proxy settings.
When `PAC script` and `Proxy rules` are provided together, the `Proxy rules`
option is ignored and `PAC script` configuration is applied.
The `Proxy rules` has to follow the rules below:
```
proxyRules = schemeProxies[";"<schemeProxies>]
schemeProxies = [<urlScheme>"="]<proxyURIList>
urlScheme = "http" | "https" | "ftp" | "socks"
proxyURIList = <proxyURL>[","<proxyURIList>]
proxyURL = [<proxyScheme>"://"]<proxyHost>[":"<proxyPort>]
```
For example:
* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and
HTTP proxy `foopy2:80` for `ftp://` URLs.
* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs.
* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing
over to `bar` if `foopy:80` is unavailable, and after that using no proxy.
* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs.
* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail
over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable.
* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no
proxy if `foopy` is unavailable.
* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use
`socks4://foopy2` for all other URLs.
The `Proxy bypass rules` is a comma separated list of rules described below:
* `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]`
Match all hostnames that match the pattern HOSTNAME_PATTERN.
Examples:
"foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99",
"https://x.*.y.com:99"
* `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]`
Match a particular domain suffix.
Examples:
".google.com", ".com", "http://.google.com"
* `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]`
Match URLs which are IP address literals.
Examples:
"127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99"
* `IP_LITERAL "/" PREFIX_LENGHT_IN_BITS`
Match any URL that is to an IP literal that falls between the
given range. IP range is specified using CIDR notation.
Examples:
"192.168.1.1/16", "fefe:13::abc/33".
* `<local>`
Match local addresses. The meaning of `<local>` is whether the
host matches one of: "127.0.0.1", "::1", "localhost".
## Change App Preferences
## Reporting an Issue

8426
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.3.6",
"version": "1.8.1",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
@@ -17,12 +17,9 @@
"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": "node ./tools/reinstall-node-modules.js",
"reinstall": "./tools/reinstall-node-modules",
"postinstall": "electron-builder install-app-deps",
"test": "xo",
"test-e2e": "gulp test-e2e",
@@ -30,8 +27,7 @@
"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"
@@ -46,8 +42,7 @@
],
"copyright": "©2017 Kandra Labs, Inc.",
"mac": {
"category": "public.app-category.productivity",
"artifactName": "${productName}-${version}-${arch}.${ext}"
"category": "public.app-category.productivity"
},
"linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
@@ -56,41 +51,32 @@
"target": [
"deb",
"zip",
"AppImage",
"snap"
"AppImage"
],
"maintainer": "Akash Nimare <svnitakash@gmail.com>",
"artifactName": "${productName}-${version}-${arch}.${ext}"
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
},
"deb": {
"synopsis": "Zulip Desktop App",
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
},
"snap": {
"synopsis": "Zulip Desktop App"
},
"dmg": {
"background": "build/appdmg.png",
"icon": "build/icon.icns",
"iconSize": 100,
"iconSize": 128,
"contents": [
{
"x": 380,
"y": 280,
"y": 240,
"type": "link",
"path": "/Applications"
},
{
"x": 110,
"y": 280,
"x": 122,
"y": 240,
"type": "file"
}
],
"window": {
"width": 500,
"height": 500
}
]
},
"win": {
"target": [
@@ -123,17 +109,16 @@
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "2.0.8",
"electron-builder": "20.20.4",
"electron": "1.8.2",
"electron-builder": "19.53.6",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",
"gulp": "^4.0.0",
"gulp": "3.9.1",
"gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"nodemon": "^1.14.11",
"pre-commit": "1.2.2",
"spectron": "3.8.0",
"spectron": "3.7.2",
"tap-colorize": "^1.2.0",
"tape": "^4.8.0",
"xo": "0.18.2"
@@ -173,8 +158,7 @@
}
],
"ignore": [
"tests/*.js",
"tools/locale-helper/*.js"
"tests/*.js"
],
"envs": [
"node",

View File

@@ -2,10 +2,6 @@
# This script runs when user install the debian package
# Link to the binary
ln -sf '/opt/${productFilename}/${executable}' '/usr/local/bin/${executable}';
echo 'Successfully added /opt/${productFilename}/${executable} to /usr/local/bin/${executable}'
# 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

View File

@@ -28,8 +28,4 @@ appDirectory=/home/$getSudoUser/.config/Zulip/;
if [ -d $appDirectory ]; then
sudo rm -rf $appDirectory;
fi
# Delete the link to the binary
echo 'Removing binary link'
sudo rm -f '/usr/local/bin/${executable}';
fi

View File

@@ -15,6 +15,6 @@ fi
npm run test
# if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
# npm run test-e2e
# fi
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
npm run test-e2e
fi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,37 +0,0 @@
name: zulip
version: 2.0.0
summary: Zulip Desktop Client for Linux
description: Zulip combines the immediacy of Slack with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones.
confinement: strict
grade: stable
icon: snap/gui/icon.png
apps:
zulip:
command: env TMPDIR=$XDG_RUNTIME_DIR desktop-launch $SNAP/zulip
plugs:
- desktop
- desktop-legacy
- home
- x11
- unity7
- browser-support
- network
- gsettings
- pulseaudio
- opengl
parts:
app:
plugin: dump
stage-packages:
- libasound2
- libgconf2-4
- libnotify4
- libnspr4
- libnss3
- libpcre3
- libpulse0
- libxss1
- libxtst6
source: dist/linux-unpacked
after:
- desktop-gtk2

View File

@@ -1,185 +0,0 @@
{
"name": "zulip",
"productName": "ZulipTest",
"version": "2.3.3",
"main": "../../app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
"author": {
"name": "Kandra Labs, Inc.",
"email": "support@zulipchat.com"
},
"repository": {
"type": "git",
"url": "https://github.com/zulip/zulip-electron.git"
},
"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": "node ./tools/reinstall-node-modules.js",
"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",
"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"
},
"pre-commit": [
"test"
],
"build": {
"appId": "org.zulip.zulip-electron",
"asar": true,
"files": [
"**/*",
"!docs${/*}",
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
],
"copyright": "©2017 Kandra Labs, Inc.",
"mac": {
"category": "public.app-category.productivity",
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
"packageCategory": "GNOME;GTK;Network;InstantMessaging",
"description": "Zulip Desktop Client for Linux",
"target": [
"deb",
"zip",
"AppImage",
"snap"
],
"maintainer": "Akash Nimare <svnitakash@gmail.com>",
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"deb": {
"synopsis": "Zulip Desktop App",
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
},
"snap": {
"synopsis": "Zulip Desktop App"
},
"dmg": {
"background": "build/appdmg.png",
"icon": "build/icon.icns",
"iconSize": 100,
"contents": [
{
"x": 380,
"y": 280,
"type": "link",
"path": "/Applications"
},
{
"x": 110,
"y": 280,
"type": "file"
}
],
"window": {
"width": 500,
"height": 500
}
},
"win": {
"target": [
{
"target": "nsis-web",
"arch": [
"x64",
"ia32"
]
}
],
"icon": "build/icon.ico",
"publisherName": "Kandra Labs, Inc."
},
"nsis": {
"perMachine": true,
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
},
"keywords": [
"Zulip",
"Group Chat app",
"electron-app",
"electron",
"Desktop app",
"InstantMessaging"
],
"devDependencies": {
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "2.0.1",
"electron-builder": "20.20.4",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",
"gulp": "^4.0.0",
"gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"nodemon": "^1.14.11",
"pre-commit": "1.2.2",
"spectron": "3.8.0",
"tap-colorize": "^1.2.0",
"tape": "^4.8.0",
"xo": "0.18.2"
},
"xo": {
"parserOptions": {
"sourceType": "script",
"ecmaFeatures": {
"globalReturn": true
}
},
"esnext": true,
"overrides": [
{
"files": "app*/**/*.js",
"rules": {
"max-lines": [
"warn",
{
"max": 500,
"skipBlankLines": true,
"skipComments": true
}
],
"no-warning-comments": 0,
"object-curly-spacing": 0,
"capitalized-comments": 0,
"no-else-return": 0,
"no-path-concat": 0,
"no-alert": 0,
"guard-for-in": 0,
"prefer-promise-reject-errors": 0,
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0,
"no-prototype-builtins": 0
}
}
],
"ignore": [
"tests/e2e/*.js",
"tools/locale-helper/*.js"
],
"envs": [
"node",
"browser",
"mocha"
]
}
}

View File

@@ -7,7 +7,7 @@ test('app runs', function (t) {
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.waitForExist('//*[@id="connect"]')) // id of the connect button
.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,17 +0,0 @@
const test = require('tape')
const setup = require('./setup')
// Create new org link should open in the default browser [WIP]
test('new-org-link', 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.click('#open-create-org-link')) // Click on new org link button
.then(() => setup.wait(5000))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

0
tools/fetch-pull-request.cmd Executable file → Normal file
View File

0
tools/fetch-rebase-pull-request.cmd Executable file → Normal file
View File

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,81 +0,0 @@
#!/bin/bash
set -e
usage () {
cat >&2 <<EOF
usage: $0 PULL_REQUEST_ID [REMOTE]
Force-push our HEAD to the given GitHub pull request branch.
Useful for a maintainer to run just before pushing to master,
after tweaking the branch and/or rebasing to latest. This causes
GitHub to see the subsequent push to master as representing a
merge of the PR, rather than requiring the PR to be manually
(and to the casual observer misleadingly) closed instead.
REMOTE defaults to the value of the Git config variable
\`zulip.zulipRemote\` if set, else to \`upstream\`.
See also \`reset-to-pull-request\`.
EOF
exit 1
}
remote_default="$(git config zulip.zulipRemote || echo upstream)"
pr_id="$1"
remote="${2:-"$remote_default"}"
if [ -z "$pr_id" ]; then
usage
fi
remote_url="$(git config remote."$remote".url)"
repo_fq="$(echo "$remote_url" | perl -lne 'print $1 if (
m, ^ git\@github\.com:
([^/]+ / [^/]+?)
(?:\.git)?
$ ,x )')"
if [ -z "$repo_fq" ]; then
# We're pretty specific about what we expect the URL to look like;
# there are probably more cases we could legitimately cover, which
# we can add if/when they come up for someone.
echo "error: couldn't parse remote URL as GitHub repo" >&2
exit 1
fi
# See https://developer.github.com/v3/pulls/#get-a-single-pull-request .
# This is the old REST API; the new GraphQL API does look neat, but it
# seems to require authentication even for simple lookups of public data,
# and that'd be a pain for a simple script like this.
pr_url=https://api.github.com/repos/"${repo_fq}"/pulls/"${pr_id}"
pr_details="$(curl -s "$pr_url")"
pr_jq () {
echo "$pr_details" | jq "$@"
}
if [ "$(pr_jq -r .message)" = "Not Found" ]; then
echo "Invalid PR URL: $pr_url"
exit 1
fi
if [ "$(pr_jq .maintainer_can_modify)" != "true" ]; then
# This happens when the PR has already been merged or closed, or
# if the contributor has turned off the (default) setting to allow
# maintainers of the target repo to push to their PR branch.
#
# The latter seems to be rare (in Greg's experience doing the
# manual equivalent of this script for many different
# contributors, none have ever chosen this setting), but give a
# decent error message if it does happen.
echo "error: PR already closed, or contributor has disallowed pushing to branch" >&2
exit 1
fi
pr_head_repo_fq="$(pr_jq -r .head.repo.full_name)"
pr_head_refname="$(pr_jq -r .head.ref)"
set -x
exec git push git@github.com:"$pr_head_repo_fq" +@:"$pr_head_refname"

0
tools/reinstall-node-modules Executable file → Normal file
View File

0
tools/reinstall-node-modules.cmd Executable file → Normal file
View File

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env node
const {exec} = require('child_process');
const path = require('path');
const isWindows = process.platform === 'win32';
const command = path.join(__dirname, `reinstall-node-modules${isWindows ? '.cmd' : ''}`);
const proc = exec(command, error => {
if (error) {
console.error(error);
}
});
proc.stdout.on('data', data => console.log(data.toString()));
proc.stderr.on('data', data => console.error(data.toString()));
proc.on('exit', code => {
process.exit(code);
});

View File

@@ -1,19 +0,0 @@
#!/bin/bash
set -e
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
remote_default="$(git config zulip.zulipRemote || echo upstream)"
request_id="$1"
remote=${2:-"$remote_default"}
set -x
git fetch "$remote" "pull/$request_id/head"
git reset --hard FETCH_HEAD