Compare commits

...

54 Commits

Author SHA1 Message Date
Anders Kaseorg
2a0f9b30e6 release: New release v5.6.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-02-16 15:55:16 -08:00
Anders Kaseorg
109795ca3e package: Migrate APT repository from Bintray to our domain.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-02-16 15:40:26 -08:00
Anders Kaseorg
e6e5e8a311 Upgrade dependencies, including Electron 11.2.3.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-02-16 15:40:04 -08:00
Anders Kaseorg
bd0869ec07 preload: Move extra keyboard shortcuts to invisible menu items.
Fixes #1060.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-02-16 15:29:25 -08:00
tarun8718
07ae127cc8 shortcuts-section: Deduplicate templateMacHTML and templateWinLinHTML.
Signed-off-by: tarun8718 <tarunkumar8718@gmail.com>
2021-02-02 10:49:35 -08:00
Anders Kaseorg
baa76c3244 Upgrade dependencies, including Electron 11.2.1.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:35:02 -08:00
Anders Kaseorg
7ac31f80ed xo: Remove unused @typescript-eslint/prefer-readonly-parameter-types override.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:35:02 -08:00
Anders Kaseorg
a95ee64f7d xo: Use eslint-import-resolver-typescript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:35:02 -08:00
Anders Kaseorg
7d6c6bc10a xo: Fix unicorn/prefer-number-properties.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:23:48 -08:00
Anders Kaseorg
18b41938de xo: Fix unicorn/empty-brace-spaces.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:23:48 -08:00
Anders Kaseorg
9fe382b27f xo: Fix unicorn/explicit-length-check.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:23:48 -08:00
Anders Kaseorg
f022b338e6 xo: Fix unicorn/no-lonely-if.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:23:48 -08:00
Anders Kaseorg
855d99dfa0 xo: Fix unicorn/prevent-abbreviations.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:23:48 -08:00
Anders Kaseorg
cc2424e0bf xo: Fix @typescript-eslint/no-confusing-void-expression.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-25 11:23:48 -08:00
Anders Kaseorg
fa6d72268f Rename master branch to main.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-01-22 12:13:29 -08:00
Anders Kaseorg
762dd92ec3 Upgrade dependencies, including Electron 11.1.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-17 17:20:32 -08:00
Anders Kaseorg
2e90e24552 Remove fs-extra dependency.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-17 17:20:32 -08:00
Anders Kaseorg
d7adce0ebf appveyor: Use current dependencies.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-17 17:20:32 -08:00
Anders Kaseorg
a1bb6da4fb Switch Travis CI to GitHub Actions.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-16 18:45:20 -08:00
Anders Kaseorg
873fecf548 Revert "performance: Disable hardware acceleration to decrease the load on GPU."
This reverts commit fb74251a2c.

The actual problem in #213 was the infinite bouncing question mark
hotspot animation (https://github.com/zulip/zulip/issues/13760).

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-16 14:14:12 -08:00
Anders Kaseorg
682511bb68 injected: Remove unused page_params type declaration.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-08 16:40:49 -08:00
Anders Kaseorg
02fbe1a6a1 Revert to upstream undo and redo roles.
This reverts part of commit 01f6e77237
(#866).  The Electron bug was fixed upstream in Electron 9.0.0-beta.23.

Closes #899.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-02 19:54:22 -08:00
Anders Kaseorg
0cb82a6f5e release: New release v5.5.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 19:53:20 -08:00
Anders Kaseorg
79808e8ee9 preload: Provide hooks for server to robustly replace logout et al.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 18:11:45 -08:00
Anders Kaseorg
2c38df10c8 electron-bridge: Expose boolean return from emit.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 17:59:59 -08:00
Anders Kaseorg
1ca15d44a0 electron-bridge: Move mutable state out of electron_bridge.
Only the initial value of a mutable field is exposed via
exposeInMainWorld, which is why we have a bunch of setter and getter
functions.  It’s better to avoid the possibility for this confusion.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 17:54:21 -08:00
Anders Kaseorg
82450a91a9 preload: Remove retry button redirection hack.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 17:18:09 -08:00
Anders Kaseorg
62edfa6f8b Remove macOS notification inline replies feature.
node-mac-notifier no longer builds on macOS with Electron 11 (error:
no template named 'remove_cv_t' in namespace 'std').  It was
previously implicated in crashes on macOS (#1016).  And we no longer
have any macOS developers that seem to be maintaining this
feature (e.g. #1022 is stalled).

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 17:06:11 -08:00
Anders Kaseorg
fe86315ece main: Be explicit about disabling contextIsolation for the main window.
We have been relying on the default here, but the default will be
changing in Electron 12.  (We already enable contextIsolation in the
webviews that load remote content.)

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 16:34:41 -08:00
Anders Kaseorg
df3f719e89 Upgrade dependencies, including Electron 11.0.3.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 16:02:41 -08:00
Anders Kaseorg
0632d8199f injected: Condition narrow-by-topic handler on page_params.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-12-01 16:02:32 -08:00
Anders Kaseorg
047bf0ca45 webview: Pass webPreferences values as explicit booleans
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-11-30 12:39:35 -08:00
Anders Kaseorg
356c879668 Remove Devtron.
Devtron is unmaintained and no longer works.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-11-18 15:25:33 -08:00
Anders Kaseorg
ba432d32b3 Remove preventdrag script.
This was not a security feature; security is enforced using context
isolation and the same-origin policy.

Furthermore, navigation on drag-and-drop was already disabled by
default in Electron 3.0.

https://www.electronjs.org/blog/electron-3-0#breaking-api-changes

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-11-17 16:10:47 -08:00
Anders Kaseorg
c8ada3f47d Rewrite reinstall script to avoid auxilliary script files.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-11-17 15:41:46 -08:00
aryanshridhar
cd77fc6448 new-server-form: Strip whitespace from added organization URL.
Fixes #1037.
2020-11-15 19:56:53 -08:00
Anders Kaseorg
a2f926c611 README: Migrate Travis badge to travis-ci.com.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-27 15:49:22 -07:00
Anders Kaseorg
6c5eb85a16 README: Use Markdown for screenshot display.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-27 15:45:55 -07:00
Anders Kaseorg
cadb1c6eaa Upgrade dependencies, including Electron 10.1.5.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:47:13 -07:00
Anders Kaseorg
73710319e6 xo: Fix unicorn/prevent-abbreviations.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:47:13 -07:00
Anders Kaseorg
da91dc5595 xo: Fix @typescript-eslint/consistent-indexed-object-style.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:47:13 -07:00
Anders Kaseorg
31d5e5a092 xo: Fix unicorn/prefer-ternary, I guess.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:47:13 -07:00
Anders Kaseorg
13ee1d0990 logger-util: Add missing space.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:47:13 -07:00
Anders Kaseorg
d5a9063378 typescript: Fix implicit any in catch clauses.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:47:13 -07:00
Anders Kaseorg
918064f35d checkDomain: Remove special handling for “certificate” error strings.
The fragile check has been broken by changing strings, and the default
invalidZulipServerError message is fine.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:47:01 -07:00
Anders Kaseorg
193b8326bc injected: Check if narrow is defined.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:32:05 -07:00
Anders Kaseorg
9abb7f376e injected: Remove unused default_language from zulipWindow type.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-24 15:32:05 -07:00
Anders Kaseorg
ac338fa438 Upgrade dependencies, including Electron 10.1.3.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-01 15:54:59 -07:00
Anders Kaseorg
f5b78ee845 Set enableRemoteModule.
We would like to disable the remote module for improved sandboxing
(#915), but until then this is required for Electron 10, which
disables the remote module by default.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-01 15:54:59 -07:00
Aryan Shridhar
126bb26a6e Tray Icon : Changed Unread tray icon in Windows.
Replaced unread messages icon in the lower tray bar in windows with a new icon.
Fixed #506.
2020-09-17 16:07:23 +05:30
Anders Kaseorg
23e86abb5b Remove support for custom certificate exceptions.
Version 5.4.0 and later uses electron.net for all network
requests (#993), so custom certificates can now be configured in the
same system certificate store that Chrome uses.

https://zulip.com/help/custom-certificates#desktop

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-09-11 22:25:28 -07:00
Anders Kaseorg
3a3714787f main: Fix mainWindowState scope.
Fixes a regression with the factory reset function introduced by
commit cf9d0c8aa2.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-09-10 21:11:41 -07:00
Anders Kaseorg
bc57aabc97 Disable unused Chromium plugins; delete old commented PDF code.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-09-10 18:28:35 -07:00
Anders Kaseorg
08df02a1ea changelog: Update for 5.4.3 release.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-09-09 23:18:59 -07:00
61 changed files with 2478 additions and 2703 deletions

15
.github/workflows/node.js.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Node.js CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci
- run: npm test

View File

@@ -1,23 +0,0 @@
os:
- osx
- linux
addons:
apt:
packages:
- build-essential
- libxext-dev
- libxtst-dev
- libxkbfile-dev
language: node_js
node_js:
- '10'
- '12'
cache:
directories:
- node_modules
script:
- npm run test

View File

@@ -1,13 +1,13 @@
# Zulip Desktop Client
[![Build Status](https://travis-ci.org/zulip/zulip-desktop.svg?branch=master)](https://travis-ci.org/zulip/zulip-desktop)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/zulip/zulip-desktop?branch=master&svg=true)](https://ci.appveyor.com/project/zulip/zulip-desktop/branch/master)
[![Build Status](https://travis-ci.com/zulip/zulip-desktop.svg?branch=main)](https://travis-ci.com/github/zulip/zulip-desktop)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/zulip/zulip-desktop?branch=main&svg=true)](https://ci.appveyor.com/project/zulip/zulip-desktop/branch/main)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://chat.zulip.org)
Desktop client for Zulip. Available for Mac, Linux, and Windows.
<img src="https://i.imgur.com/s1o6TRA.png"/>
<img src="https://i.imgur.com/vekKnW4.png"/>
![screenshot](https://i.imgur.com/s1o6TRA.png)
![screenshot](https://i.imgur.com/vekKnW4.png)
# Download
Please see the [installation guide](https://zulip.com/help/desktop-app-install-guide).

View File

@@ -6,7 +6,6 @@ import path from 'path';
import windowStateKeeper from 'electron-window-state';
import * as BadgeSettings from '../renderer/js/pages/preference/badge-settings';
import * as CertificateUtil from '../renderer/js/utils/certificate-util';
import * as ConfigUtil from '../renderer/js/utils/config-util';
import * as ProxyUtil from '../renderer/js/utils/proxy-util';
import {sentryInit} from '../renderer/js/utils/sentry-util';
@@ -70,7 +69,7 @@ const toggleApp = (): void => {
function createMainWindow(): Electron.BrowserWindow {
// Load the previous state with fallback to defaults
const mainWindowState: windowStateKeeper.State = windowStateKeeper({
mainWindowState = windowStateKeeper({
defaultWidth: 1100,
defaultHeight: 720,
path: `${app.getPath('userData')}/config`
@@ -87,7 +86,8 @@ function createMainWindow(): Electron.BrowserWindow {
minWidth: 500,
minHeight: 400,
webPreferences: {
plugins: true,
contextIsolation: false,
enableRemoteModule: true,
nodeIntegration: true,
partition: 'persist:webviewsession',
webviewTag: true
@@ -143,9 +143,6 @@ function createMainWindow(): Electron.BrowserWindow {
return win;
}
// 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');
@@ -222,36 +219,9 @@ app.on('ready', () => {
event: Event,
webContents: Electron.WebContents,
urlString: string,
error: string,
certificate: Electron.Certificate,
callback: (isTrusted: boolean) => void
) /* eslint-disable-line max-params */ => {
// TODO: The entire concept of selectively ignoring certificate errors
// is ill-conceived, and this handler needs to be deleted.
error: string
) => {
const url = new URL(urlString);
if (url.protocol === 'wss:') {
url.protocol = 'https:';
}
const filename = CertificateUtil.getCertificate(encodeURIComponent(url.origin));
if (filename !== undefined) {
try {
const savedCertificate = fs.readFileSync(
path.join(`${app.getPath('userData')}/certificates`, filename),
'utf8'
);
if (certificate.data.replace(/[\r\n]/g, '') ===
savedCertificate.replace(/[\r\n]/g, '')) {
event.preventDefault();
callback(true);
return;
}
} catch (error) {
console.error(`Error reading certificate file ${filename}:`, error);
}
}
dialog.showErrorBox(
'Certificate error',
`The server presented an invalid certificate for ${url.origin}:
@@ -285,32 +255,6 @@ ${error}`
app.quit();
});
// Code to show pdf in a new BrowserWindow (currently commented out due to bug-upstream)
// 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);
// // We don't want to have the menu bar in pdf window
// pdfWindow.setMenu(null);
// });
// Reload full app not just webview, useful in debugging
ipcMain.on('reload-full-app', () => {
mainWindow.reload();
@@ -467,7 +411,7 @@ app.on('before-quit', () => {
});
// Send crash reports
process.on('uncaughtException', err => {
console.error(err);
console.error(err.stack);
process.on('uncaughtException', error => {
console.error(error);
console.error(error.stack);
});

View File

@@ -1,7 +1,7 @@
import {app, Notification, net} from 'electron';
import getStream from 'get-stream';
import semver from 'semver';
import * as semver from 'semver';
import * as ConfigUtil from '../renderer/js/utils/config-util';
import * as LinuxUpdateUtil from '../renderer/js/utils/linux-update-util';
@@ -38,7 +38,7 @@ export async function linuxUpdateNotification(session: Electron.session): Promis
LinuxUpdateUtil.setUpdateItem(latestVersion, true);
}
}
} catch (error) {
} catch (error: unknown) {
logger.error('Linux update error.');
logger.error(error);
}

View File

@@ -115,6 +115,15 @@ function getViewSubmenu(): Electron.MenuItemConstructorOptions[] {
sendAction('hard-reload');
}
}
}, {
label: t.__('Hard Reload'),
visible: false,
accelerator: 'F5',
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('hard-reload');
}
}
}, {
type: 'separator'
}, {
@@ -128,6 +137,24 @@ function getViewSubmenu(): Electron.MenuItemConstructorOptions[] {
sendAction('zoomIn');
}
}
}, {
label: t.__('Zoom In'),
visible: false,
accelerator: 'CommandOrControl+Plus',
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomIn');
}
}
}, {
label: t.__('Zoom In'),
visible: false,
accelerator: 'CommandOrControl+numadd',
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomIn');
}
}
}, {
label: t.__('Zoom Out'),
accelerator: 'CommandOrControl+-',
@@ -136,6 +163,15 @@ function getViewSubmenu(): Electron.MenuItemConstructorOptions[] {
sendAction('zoomOut');
}
}
}, {
label: t.__('Zoom Out'),
visible: false,
accelerator: 'CommandOrControl+numsub',
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomOut');
}
}
}, {
label: t.__('Actual Size'),
accelerator: 'CommandOrControl+0',
@@ -144,6 +180,15 @@ function getViewSubmenu(): Electron.MenuItemConstructorOptions[] {
sendAction('zoomActualSize');
}
}
}, {
label: t.__('Actual Size'),
visible: false,
accelerator: 'CommandOrControl+num0',
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomActualSize');
}
}
}, {
type: 'separator'
}, {
@@ -308,7 +353,7 @@ function getDarwinTpl(props: MenuProps): Electron.MenuItemConstructorOptions[] {
enabled: enableMenu,
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('shortcut');
sendAction('show-keyboard-shortcuts');
}
}
}, {
@@ -364,20 +409,10 @@ function getDarwinTpl(props: MenuProps): Electron.MenuItemConstructorOptions[] {
label: t.__('Edit'),
submenu: [{
label: t.__('Undo'),
accelerator: 'Cmd+Z',
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('undo');
}
}
role: 'undo'
}, {
label: t.__('Redo'),
accelerator: 'Cmd+Shift+Z',
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('redo');
}
}
role: 'redo'
}, {
type: 'separator'
}, {
@@ -450,7 +485,7 @@ function getOtherTpl(props: MenuProps): Electron.MenuItemConstructorOptions[] {
enabled: enableMenu,
click(_item, focusedWindow) {
if (focusedWindow) {
sendAction('shortcut');
sendAction('show-keyboard-shortcuts');
}
}
}, {

View File

@@ -13,7 +13,9 @@ import * as Messages from '../resources/messages';
export async function fetchResponse(request: ClientRequest): Promise<IncomingMessage> {
return new Promise((resolve, reject) => {
request.on('response', resolve);
request.on('abort', () => reject(new Error('Request aborted')));
request.on('abort', () => {
reject(new Error('Request aborted'));
});
request.on('error', reject);
request.end();
});
@@ -87,7 +89,7 @@ export const _saveServerIcon = async (url: string, session: Electron.session): P
const filePath = generateFilePath(url);
await pipeline(response, fs.createWriteStream(filePath));
return filePath;
} catch (error) {
} catch (error: unknown) {
logger.log('Could not get server icon.');
logger.log(error);
logger.reportSentry(error);
@@ -105,7 +107,7 @@ export const _isOnline = async (url: string, session: Electron.session): Promise
}));
const isValidResponse = response.statusCode >= 200 && response.statusCode < 400;
return isValidResponse;
} catch (error) {
} catch (error: unknown) {
logger.log(error);
return false;
}

View File

@@ -19,11 +19,7 @@ export const setAutoLaunch = async (AutoLaunchValue: boolean): Promise<void> =>
name: 'Zulip',
isHidden: false
});
if (autoLaunchOption) {
await ZulipAutoLauncher.enable();
} else {
await ZulipAutoLauncher.disable();
}
await (autoLaunchOption ? ZulipAutoLauncher.enable() : ZulipAutoLauncher.disable());
} else {
app.setLoginItemSettings({
openAtLogin: autoLaunchOption,

View File

@@ -18,7 +18,7 @@
</p>
<p class="detail license">
Available under the
<a href="https://github.com/zulip/zulip-desktop/blob/master/LICENSE" target="_blank" rel="noopener noreferrer">Apache 2.0 License</a>
<a href="https://github.com/zulip/zulip-desktop/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">Apache 2.0 License</a>
</p>
</div>
</div>
@@ -28,6 +28,5 @@
const version_tag = document.querySelector('#version');
version_tag.textContent = 'v' + app.getVersion();
</script>
<script>require('./js/shared/preventdrag.js')</script>
</body>
</html>

View File

@@ -622,10 +622,6 @@ input.toggle-round:checked + label::after {
max-width: 100%;
}
#add-certificate-button {
margin: 10px 10px 10px 37px;
}
.tip {
background: none;
padding: 0;

View File

@@ -20,13 +20,13 @@ export const contextMenu = (webContents: Electron.WebContents, event: Event, pro
let menuTemplate: Electron.MenuItemConstructorOptions[] = [{
label: t.__('Add to Dictionary'),
visible: props.isEditable && isText && props.misspelledWord.length !== 0,
visible: props.isEditable && isText && props.misspelledWord.length > 0,
click(_item) {
webContents.session.addWordToSpellCheckerDictionary(props.misspelledWord);
}
}, {
type: 'separator',
visible: props.isEditable && isText && props.misspelledWord.length !== 0
visible: props.isEditable && isText && props.misspelledWord.length > 0
}, {
label: `${t.__('Look Up')} "${props.selectionText}"`,
visible: process.platform === 'darwin' && isText,

View File

@@ -54,11 +54,7 @@ export default class ServerTab extends Tab {
let shortcutText = '';
if (SystemUtil.getOS() === 'Mac') {
shortcutText = `${shownIndex}`;
} else {
shortcutText = `Ctrl+${shownIndex}`;
}
shortcutText = SystemUtil.getOS() === 'Mac' ? `${shownIndex}` : `Ctrl+${shownIndex}`;
// Array index == Shown index - 1
ipcRenderer.send('switch-server-tab', shownIndex - 1);

View File

@@ -63,9 +63,8 @@ export default class WebView extends BaseComponent {
partition="persist:webviewsession"
name="${this.props.name}"
webpreferences="
${this.props.nodeIntegration ? '' : 'contextIsolation,'}
${ConfigUtil.getConfigItem('enableSpellchecker') ? 'spellcheck,' : ''}
javascript
contextIsolation=${!this.props.nodeIntegration},
spellcheck=${Boolean(ConfigUtil.getConfigItem('enableSpellchecker'))}
">
</webview>
`;
@@ -74,7 +73,9 @@ export default class WebView extends BaseComponent {
init(): void {
this.$el = this.generateNodeFromHTML(this.templateHTML()) as Electron.WebviewTag;
this.domReady = new Promise(resolve => {
this.$el.addEventListener('dom-ready', () => resolve(), true);
this.$el.addEventListener('dom-ready', () => {
resolve();
}, true);
});
this.props.$root.append(this.$el);
@@ -148,8 +149,8 @@ export default class WebView extends BaseComponent {
this.$el.addEventListener('did-fail-load', event => {
const {errorDescription} = event;
const hasConnectivityErr = SystemUtil.connectivityERR.includes(errorDescription);
if (hasConnectivityErr) {
const hasConnectivityError = SystemUtil.connectivityERR.includes(errorDescription);
if (hasConnectivityError) {
console.error('error', errorDescription);
if (!this.props.url.includes('network.html')) {
this.props.onNetworkError(this.props.index);
@@ -266,8 +267,8 @@ export default class WebView extends BaseComponent {
ipcRenderer.sendTo(this.$el.getWebContentsId(), 'logout');
}
showShortcut(): void {
ipcRenderer.sendTo(this.$el.getWebContentsId(), 'shortcut');
showKeyboardShortcuts(): void {
ipcRenderer.sendTo(this.$el.getWebContentsId(), 'show-keyboard-shortcuts');
}
openDevTools(): void {

View File

@@ -1,68 +1,61 @@
import {ipcRenderer} from 'electron';
import {EventEmitter} from 'events';
import isDev from 'electron-is-dev';
import {ClipboardDecrypterImpl} from './clipboard-decrypter';
import {NotificationData, newNotification} from './notification';
type ListenerType = ((...args: any[]) => void);
class ElectronBridgeImpl extends EventEmitter implements ElectronBridge {
send_notification_reply_message_supported: boolean;
idle_on_system: boolean;
last_active_on_system: number;
let notificationReplySupported = false;
// Indicates if the user is idle or not
let idle = false;
// Indicates the time at which user was last active
let lastActive = Date.now();
constructor() {
super();
this.send_notification_reply_message_supported = false;
// Indicates if the user is idle or not
this.idle_on_system = false;
export const bridgeEvents = new EventEmitter();
// Indicates the time at which user was last active
this.last_active_on_system = Date.now();
}
const electron_bridge: ElectronBridge = {
send_event: (eventName: string | symbol, ...args: unknown[]): boolean =>
bridgeEvents.emit(eventName, ...args),
send_event = (eventName: string | symbol, ...args: unknown[]): void => {
this.emit(eventName, ...args);
};
on_event: (eventName: string, listener: ListenerType): void => {
bridgeEvents.on(eventName, listener);
},
on_event = (eventName: string, listener: ListenerType): void => {
this.on(eventName, listener);
};
new_notification = (
new_notification: (
title: string,
options: NotificationOptions | undefined,
dispatch: (type: string, eventInit: EventInit) => boolean
): NotificationData =>
newNotification(title, options, dispatch);
newNotification(title, options, dispatch),
get_idle_on_system = (): boolean => this.idle_on_system;
get_idle_on_system: (): boolean => idle,
get_last_active_on_system = (): number => this.last_active_on_system;
get_last_active_on_system: (): number => lastActive,
get_send_notification_reply_message_supported = (): boolean =>
this.send_notification_reply_message_supported;
get_send_notification_reply_message_supported: (): boolean =>
notificationReplySupported,
set_send_notification_reply_message_supported = (value: boolean): void => {
this.send_notification_reply_message_supported = value;
};
set_send_notification_reply_message_supported: (value: boolean): void => {
notificationReplySupported = value;
},
decrypt_clipboard = (version: number): ClipboardDecrypterImpl =>
new ClipboardDecrypterImpl(version);
}
decrypt_clipboard: (version: number): ClipboardDecrypterImpl =>
new ClipboardDecrypterImpl(version)
};
const electron_bridge = new ElectronBridgeImpl();
electron_bridge.on('total_unread_count', (...args) => {
bridgeEvents.on('total_unread_count', (...args) => {
ipcRenderer.send('unread-count', ...args);
});
electron_bridge.on('realm_name', realmName => {
bridgeEvents.on('realm_name', realmName => {
const serverURL = location.origin;
ipcRenderer.send('realm-name-changed', serverURL, realmName);
});
electron_bridge.on('realm_icon_url', (iconURL: unknown) => {
bridgeEvents.on('realm_icon_url', (iconURL: unknown) => {
if (typeof iconURL !== 'string') {
throw new TypeError('Expected string for iconURL');
}
@@ -72,6 +65,25 @@ electron_bridge.on('realm_icon_url', (iconURL: unknown) => {
ipcRenderer.send('realm-icon-changed', serverURL, iconURL);
});
// Set user as active and update the time of last activity
ipcRenderer.on('set-active', () => {
if (isDev) {
console.log('active');
}
idle = false;
lastActive = Date.now();
});
// Set user as idle and time of last activity is left unchanged
ipcRenderer.on('set-idle', () => {
if (isDev) {
console.log('idle');
}
idle = true;
});
// 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

View File

@@ -9,13 +9,6 @@ interface CompatElectronBridge extends ElectronBridge {
(() => {
const zulipWindow = window as typeof window & {
electron_bridge: CompatElectronBridge;
narrow: {
by_subject?: (target_id: number, opts: {trigger?: string}) => void;
by_topic?: (target_id: number, opts: {trigger?: string}) => void;
};
page_params?: {
default_language?: string;
};
raw_electron_bridge: ElectronBridge;
};
@@ -41,27 +34,6 @@ interface CompatElectronBridge extends ElectronBridge {
zulipWindow.electron_bridge = electron_bridge;
(async () => {
if (document.readyState === 'loading') {
await new Promise(resolve => {
document.addEventListener('DOMContentLoaded', () => {
resolve();
});
});
}
const {page_params} = zulipWindow;
if (page_params) {
electron_bridge.send_event('zulip-loaded');
}
})();
electron_bridge.on_event('narrow-by-topic', (id: number) => {
const {narrow} = zulipWindow;
const narrowByTopic = narrow.by_topic || narrow.by_subject;
narrowByTopic(id, {trigger: 'notification'});
});
function attributeListener<T extends EventTarget>(type: string): PropertyDescriptor {
const symbol = Symbol('on' + type);

View File

@@ -161,19 +161,15 @@ class ServerManagerView {
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
if (proxyEnabled) {
await session.fromPartition('persist:webviewsession').setProxy({
pacScript: ConfigUtil.getConfigItem('proxyPAC', ''),
proxyRules: ConfigUtil.getConfigItem('proxyRules', ''),
proxyBypassRules: ConfigUtil.getConfigItem('proxyBypass', '')
});
} else {
await session.fromPartition('persist:webviewsession').setProxy({
pacScript: '',
proxyRules: '',
proxyBypassRules: ''
});
}
await session.fromPartition('persist:webviewsession').setProxy(proxyEnabled ? {
pacScript: ConfigUtil.getConfigItem('proxyPAC', ''),
proxyRules: ConfigUtil.getConfigItem('proxyRules', ''),
proxyBypassRules: ConfigUtil.getConfigItem('proxyBypass', '')
} : {
pacScript: '',
proxyRules: '',
proxyBypassRules: ''
});
}
// Settings are initialized only when user clicks on General/Server/Network section settings
@@ -255,7 +251,7 @@ class ServerManagerView {
const serverConf = await DomainUtil.checkDomain(domain);
await DomainUtil.addDomain(serverConf);
return true;
} catch (error) {
} catch (error: unknown) {
logger.error(error);
logger.error(`Could not add ${domain}. Please contact your system administrator.`);
return false;
@@ -382,7 +378,9 @@ class ServerManagerView {
this.showLoading(this.loading.has(this.tabs[this.activeTabIndex].webview.props.url));
},
onNetworkError: (index: number) => this.openNetworkTroubleshooting(index),
onNetworkError: (index: number) => {
this.openNetworkTroubleshooting(index);
},
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: false,
preload: true
@@ -547,7 +545,9 @@ class ServerManagerView {
this.showLoading(this.loading.has(this.tabs[this.activeTabIndex].webview.props.url));
},
onNetworkError: (index: number) => this.openNetworkTroubleshooting(index),
onNetworkError: (index: number) => {
this.openNetworkTroubleshooting(index);
},
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: true,
preload: false
@@ -628,8 +628,7 @@ class ServerManagerView {
try {
this.tabs[index].webview.canGoBackButton();
} catch {
}
} catch {}
this.activeTabIndex = index;
this.tabs[index].activate();
@@ -794,16 +793,36 @@ class ServerManagerView {
registerIpcs(): void {
const webviewListeners: Array<[string, (webview: WebView) => void]> = [
['webview-reload', webview => webview.reload()],
['back', webview => webview.back()],
['focus', webview => webview.focus()],
['forward', webview => webview.forward()],
['zoomIn', webview => webview.zoomIn()],
['zoomOut', webview => webview.zoomOut()],
['zoomActualSize', webview => webview.zoomActualSize()],
['log-out', webview => webview.logOut()],
['shortcut', webview => webview.showShortcut()],
['tab-devtools', webview => webview.openDevTools()]
['webview-reload', webview => {
webview.reload();
}],
['back', webview => {
webview.back();
}],
['focus', webview => {
webview.focus();
}],
['forward', webview => {
webview.forward();
}],
['zoomIn', webview => {
webview.zoomIn();
}],
['zoomOut', webview => {
webview.zoomOut();
}],
['zoomActualSize', webview => {
webview.zoomActualSize();
}],
['log-out', webview => {
webview.logOut();
}],
['show-keyboard-shortcuts', webview => {
webview.showKeyboardShortcuts();
}],
['tab-devtools', webview => {
webview.openDevTools();
}]
];
for (const [channel, listener] of webviewListeners) {
@@ -1022,11 +1041,6 @@ class ServerManagerView {
await this.openSettings('AddServer');
});
// Redo and undo functionality since the default API doesn't work on macOS
ipcRenderer.on('undo', () => this.getActiveWebview().undo());
ipcRenderer.on('redo', () => this.getActiveWebview().redo());
ipcRenderer.on('set-active', async () => {
const webviews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webview');
await Promise.all([...webviews].map(async webview => webview.send('set-active')));

View File

@@ -1,113 +0,0 @@
import {ipcRenderer} from 'electron';
import MacNotifier from 'node-mac-notifier';
import electron_bridge from '../electron-bridge';
import * as ConfigUtil from '../utils/config-util';
import {
appId, customReply, focusCurrentServer, parseReply
} from './helpers';
type ReplyHandler = (response: string) => void;
type ClickHandler = () => void;
let replyHandler: ReplyHandler;
let clickHandler: ClickHandler;
interface NotificationHandlerArgs {
response: string;
}
class DarwinNotification {
tag: number;
constructor(title: string, options: NotificationOptions) {
const silent: boolean = ConfigUtil.getConfigItem('silent') || false;
const {icon} = options;
const profilePic = new URL(icon, location.href).href;
this.tag = Number.parseInt(options.tag, 10);
const notification = new MacNotifier(title, Object.assign(options, {
bundleId: appId,
canReply: true,
silent,
icon: profilePic
}));
notification.addEventListener('click', () => {
// Focus to the server who sent the
// notification if not focused already
if (clickHandler) {
clickHandler();
}
focusCurrentServer();
ipcRenderer.send('focus-app');
});
notification.addEventListener('reply', this.notificationHandler);
}
static requestPermission(): void {
// Do nothing
}
// Override default Notification permission
static get permission(): NotificationPermission {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
get onreply(): ReplyHandler {
return replyHandler;
}
set onreply(handler: ReplyHandler) {
replyHandler = handler;
}
get onclick(): ClickHandler {
return clickHandler;
}
set onclick(handler: ClickHandler) {
clickHandler = handler;
}
// Not something that is common or
// used by zulip server but added to be
// future proff.
addEventListener(event: string, handler: ClickHandler | ReplyHandler): void {
if (event === 'click') {
clickHandler = handler as ClickHandler;
}
if (event === 'reply') {
replyHandler = handler as ReplyHandler;
}
}
async notificationHandler({response}: NotificationHandlerArgs): Promise<void> {
response = await parseReply(response);
focusCurrentServer();
if (electron_bridge.send_notification_reply_message_supported) {
electron_bridge.send_event('send_notification_reply_message', this.tag, response);
return;
}
electron_bridge.emit('narrow-by-topic', this.tag);
if (replyHandler) {
replyHandler(response);
return;
}
customReply(response);
}
// Method specific to notification api
// used by zulip
close(): void {
// Do nothing
}
}
module.exports = DarwinNotification;

View File

@@ -1,69 +1,8 @@
import {remote} from 'electron';
import Logger from '../utils/logger-util';
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
// Do not change this
export const appId = 'org.zulip.zulip-electron';
export type BotListItem = [string, string];
const botsList: BotListItem[] = [];
let botsListLoaded = false;
// This function load list of bots from the server
// in case botsList isn't already completely loaded when required in parseRely
export async function loadBots(): Promise<void> {
botsList.length = 0;
const response = await fetch('/json/users');
if (response.ok) {
const {members} = await response.json();
members.forEach(({is_bot, full_name}: {[key: string]: unknown}) => {
if (is_bot && typeof full_name === 'string') {
const bot = `@${full_name}`;
const mention = `@**${bot.replace(/^@/, '')}**`;
botsList.push([bot, mention]);
}
});
botsListLoaded = true;
} else {
logger.log('Load bots request failed: ', await response.text());
logger.log('Load bots request status: ', response.status);
}
}
export function checkElements(...elements: unknown[]): boolean {
let status = true;
elements.forEach(element => {
if (element === null || element === undefined) {
status = false;
}
});
return status;
}
export function customReply(reply: string): void {
// Server does not support notification reply yet.
const buttonSelector = '.messagebox #send_controls button[type=submit]';
const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content';
const textarea: HTMLInputElement = document.querySelector('#compose-textarea');
const messagebox: HTMLButtonElement = document.querySelector(messageboxSelector);
const sendButton: HTMLButtonElement = document.querySelector(buttonSelector);
// Sanity check for old server versions
const elementsExists = checkElements(textarea, messagebox, sendButton);
if (!elementsExists) {
return;
}
textarea.value = reply;
messagebox.click();
sendButton.click();
}
const currentWindow = remote.getCurrentWindow();
const webContents = remote.getCurrentWebContents();
const webContentsId = webContents.id;
@@ -73,65 +12,3 @@ const webContentsId = webContents.id;
export function focusCurrentServer(): void {
currentWindow.webContents.send('focus-webview-with-id', webContentsId);
}
// This function parses the reply from to notification
// making it easier to reply from notification eg
// @username in reply will be converted to @**username**
// #stream in reply will be converted to #**stream**
// bot mentions are not yet supported
export async function parseReply(reply: string): Promise<string> {
const usersDiv = document.querySelectorAll('#user_presences li');
const streamHolder = document.querySelectorAll('#stream_filters li');
type UsersItem = BotListItem;
type StreamsItem = BotListItem;
const users: UsersItem[] = [];
const streams: StreamsItem[] = [];
usersDiv.forEach(userRow => {
const anchor = userRow.querySelector('span a');
if (anchor !== null) {
const user = `@${anchor.textContent.trim()}`;
const mention = `@**${user.replace(/^@/, '')}**`;
users.push([user, mention]);
}
});
streamHolder.forEach(stream => {
const streamAnchor = stream.querySelector('div a');
if (streamAnchor !== null) {
const streamName = `#${streamAnchor.textContent.trim()}`;
const streamMention = `#**${streamName.replace(/^#/, '')}**`;
streams.push([streamName, streamMention]);
}
});
users.forEach(([user, mention]) => {
if (reply.includes(user)) {
const regex = new RegExp(user, 'g');
reply = reply.replace(regex, mention);
}
});
streams.forEach(([stream, streamMention]) => {
const regex = new RegExp(stream, 'g');
reply = reply.replace(regex, streamMention);
});
// If botsList isn't completely loaded yet, make a request for list
if (!botsListLoaded) {
await loadBots();
}
// Iterate for every bot name and replace in reply
// @botname with @**botname**
botsList.forEach(([bot, mention]) => {
if (reply.includes(bot)) {
const regex = new RegExp(bot, 'g');
reply = reply.replace(regex, mention);
}
});
reply = reply.replace(/\\n/, '\n');
return reply;
}

View File

@@ -9,12 +9,6 @@ const {app} = remote;
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
app.setAppUserModelId(appId);
let Notification = DefaultNotification;
if (process.platform === 'darwin') {
Notification = require('./darwin-notifications');
}
export interface NotificationData {
close: () => void;
title: string;
@@ -39,7 +33,7 @@ export function newNotification(
options: NotificationOptions | undefined,
dispatch: (type: string, eventInit: EventInit) => boolean
): NotificationData {
const notification = new Notification(title, options);
const notification = new DefaultNotification(title, options);
for (const type of ['click', 'close', 'error', 'show']) {
notification.addEventListener(type, (ev: Event) => {
if (!dispatch(type, ev)) {
@@ -49,7 +43,9 @@ export function newNotification(
}
return {
close: () => notification.close(),
close: () => {
notification.close();
},
title: notification.title,
dir: notification.dir,
lang: notification.lang,

View File

@@ -1,99 +0,0 @@
import {remote, OpenDialogOptions} from 'electron';
import path from 'path';
import {htmlEscape} from 'escape-goat';
import BaseComponent from '../../components/base';
import * as CertificateUtil from '../../utils/certificate-util';
import * as DomainUtil from '../../utils/domain-util';
import * as t from '../../utils/translation-util';
interface AddCertificateProps {
$root: Element;
}
const {dialog} = remote;
export default class AddCertificate extends BaseComponent {
props: AddCertificateProps;
_certFile: string;
$addCertificate: Element | null;
addCertificateButton: Element | null;
serverUrl: HTMLInputElement | null;
constructor(props: AddCertificateProps) {
super();
this.props = props;
this._certFile = '';
}
templateHTML(): string {
return htmlEscape`
<div class="settings-card certificates-card">
<div class="certificate-input">
<div>${t.__('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>${t.__('Certificate file')}</div>
<button class="green" id="add-certificate-button">${t.__('Upload')}</button>
</div>
</div>
`;
}
init(): void {
this.$addCertificate = this.generateNodeFromHTML(this.templateHTML());
this.props.$root.append(this.$addCertificate);
this.addCertificateButton = this.$addCertificate.querySelector('#add-certificate-button');
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
this.initListeners();
}
async validateAndAdd(): Promise<void> {
const certificate = this._certFile;
const serverUrl = this.serverUrl.value;
if (certificate !== '' && serverUrl !== '') {
const server = encodeURIComponent(DomainUtil.formatUrl(serverUrl));
const fileName = path.basename(certificate);
const copy = CertificateUtil.copyCertificate(server, certificate, fileName);
if (!copy) {
return;
}
CertificateUtil.setCertificate(server, fileName);
this.serverUrl.value = '';
await dialog.showMessageBox({
title: 'Success',
message: 'Certificate saved!'
});
} else {
dialog.showErrorBox('Error', `Please, ${serverUrl === '' ?
'Enter an Organization URL' : 'Choose certificate file'}`);
}
}
async addHandler(): Promise<void> {
const showDialogOptions: OpenDialogOptions = {
title: 'Select file',
properties: ['openFile'],
filters: [{name: 'crt, pem', extensions: ['crt', 'pem']}]
};
const {filePaths, canceled} = await dialog.showOpenDialog(showDialogOptions);
if (!canceled) {
this._certFile = filePaths[0] || '';
await this.validateAndAdd();
}
}
initListeners(): void {
this.addCertificateButton.addEventListener('click', async () => {
await this.addHandler();
});
this.serverUrl.addEventListener('keypress', async event => {
if (event.key === 'Enter') {
await this.addHandler();
}
});
}
}

View File

@@ -51,7 +51,7 @@ export default class BaseSection extends BaseComponent {
/* A method that in future can be used to create dropdown menus using <select> <option> tags.
it needs an object which has ``key: value`` pairs and will return a string that can be appended to HTML
*/
generateSelectHTML(options: {[key: string]: string}, className?: string, idName?: string): string {
generateSelectHTML(options: Record<string, string>, className?: string, idName?: string): string {
let html = htmlEscape`<select class="${className}" id="${idName}">\n`;
Object.keys(options).forEach(key => {
html += htmlEscape`<option name="${key}" value="${key}">${options[key]}</option>\n`;

View File

@@ -5,7 +5,6 @@ import {htmlEscape} from 'escape-goat';
import * as DomainUtil from '../../utils/domain-util';
import * as t from '../../utils/translation-util';
import AddCertificate from './add-certificate';
import BaseSection from './base-section';
import FindAccounts from './find-accounts';
import ServerInfoForm from './server-info-form';
@@ -19,7 +18,6 @@ export default class ConnectedOrgSection extends BaseSection {
$serverInfoContainer: Element | null;
$existingServers: Element | null;
$newOrgButton: HTMLButtonElement | null;
$addCertificateContainer: Element | null;
$findAccountsContainer: Element | null;
constructor(props: ConnectedOrgSectionProps) {
super();
@@ -33,8 +31,6 @@ export default class ConnectedOrgSection extends BaseSection {
<div class="title" id="existing-servers">${t.__('All the connected orgnizations will appear here.')}</div>
<div id="server-info-container"></div>
<div id="new-org-button"><button class="green sea w-250">${t.__('Connect to another organization')}</button></div>
<div class="page-title">${t.__('Add Custom Certificates')}</div>
<div id="add-certificate-container"></div>
<div class="page-title">${t.__('Find accounts by email')}</div>
<div id="find-accounts-container"></div>
</div>
@@ -54,7 +50,6 @@ export default class ConnectedOrgSection extends BaseSection {
this.$serverInfoContainer = document.querySelector('#server-info-container');
this.$existingServers = document.querySelector('#existing-servers');
this.$newOrgButton = document.querySelector('#new-org-button');
this.$addCertificateContainer = document.querySelector('#add-certificate-container');
this.$findAccountsContainer = document.querySelector('#find-accounts-container');
const noServerText = t.__('All the connected orgnizations will appear here');
@@ -74,16 +69,9 @@ export default class ConnectedOrgSection extends BaseSection {
ipcRenderer.send('forward-message', 'open-org-tab');
});
this.initAddCertificate();
this.initFindAccounts();
}
initAddCertificate(): void {
new AddCertificate({
$root: this.$addCertificateContainer
}).init();
}
initFindAccounts(): void {
new FindAccounts({
$root: this.$findAccountsContainer

View File

@@ -1,9 +1,9 @@
import {ipcRenderer, remote, OpenDialogOptions} from 'electron';
import fs from 'fs';
import path from 'path';
import Tagify from '@yaireo/tagify';
import {htmlEscape} from 'escape-goat';
import fs from 'fs-extra';
import ISO6391 from 'iso-639-1';
import supportedLocales from '../../../../translations/supported-locales.json';
@@ -148,7 +148,7 @@ export default class GeneralSection extends BaseSection {
<div class="title">${t.__('Factory Reset Data')}</div>
<div class="settings-card">
<div class="setting-row" id="factory-reset-option">
<div class="setting-description">${t.__('Reset the application, thus deleting all the connected organizations, accounts, and certificates.')}
<div class="setting-description">${t.__('Reset the application, thus deleting all the connected organizations and accounts.')}
</div>
<button class="factory-reset-button red w-150">${t.__('Factory Reset')}</button>
</div>
@@ -494,8 +494,10 @@ export default class GeneralSection extends BaseSection {
detail: clearAppDataMessage
});
if (response === 0) {
await fs.remove(getAppPath);
setTimeout(() => ipcRenderer.send('clear-app-settings'), 1000);
await fs.promises.rmdir(getAppPath, {recursive: true});
setTimeout(() => {
ipcRenderer.send('clear-app-settings');
}, 1000);
}
}
@@ -551,7 +553,7 @@ export default class GeneralSection extends BaseSection {
maxTags: 3,
dropdown: {
enabled: 0,
maxItems: Infinity,
maxItems: Number.POSITIVE_INFINITY,
closeOnSelect: false,
highlightFirst: true
}

View File

@@ -69,12 +69,13 @@ export default class NewServerForm extends BaseComponent {
this.$saveServerButton.textContent = 'Connecting...';
let serverConf;
try {
serverConf = await DomainUtil.checkDomain(this.$newServerUrl.value);
} catch (error) {
serverConf = await DomainUtil.checkDomain(this.$newServerUrl.value.trim());
} catch (error: unknown) {
this.$saveServerButton.textContent = 'Connect';
await dialog.showMessageBox({
type: 'error',
message: error.toString(),
message: error instanceof Error ?
`${error.name}: ${error.message}` : 'Unknown error',
buttons: ['OK']
});
return;
@@ -94,7 +95,9 @@ export default class NewServerForm extends BaseComponent {
networkSettingsLink(): void {
const networkSettingsId = document.querySelectorAll('.server-network-option')[0];
networkSettingsId.addEventListener('click', () => ipcRenderer.send('forward-message', 'open-network-settings'));
networkSettingsId.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'open-network-settings');
});
}
initActions(): void {

View File

@@ -16,12 +16,9 @@ export default class ShortcutsSection extends BaseSection {
this.props = props;
}
// TODO - Deduplicate templateMacHTML and templateWinLinHTML functions. In theory
// they both should be the same the only thing different should be the userOSKey
// variable but there seems to be inconsistences between both function, one has more
// lines though one may just be using more new lines and other thing is the use of +.
templateMacHTML(): string {
const userOSKey = '⌘';
// eslint-disable-next-line complexity
templateHTML(): string {
const cmdOrCtrl = process.platform === 'darwin' ? '⌘' : 'Ctrl';
return htmlEscape`
<div class="settings-pane">
@@ -30,35 +27,39 @@ export default class ShortcutsSection extends BaseSection {
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>,</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>,</kbd></td>
<td>${t.__('Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>K</kbd></td>
<td>${t.__('Keyboard Shortcuts')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>${t.__('Toggle Do Not Disturb')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Shift</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>M</kbd></td>
<td>${t.__('Toggle Do Not Disturb')}</td>
</tr>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Shift</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>D</kbd></td>
<td>${t.__('Reset App Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>L</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>L</kbd></td>
<td>${t.__('Log Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>H</kbd></td>
<td>${t.__('Hide Zulip')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Option</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>H</kbd></td>
<td>${t.__('Hide Others')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Q</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Q</kbd></td>
<td>${t.__('Quit Zulip')}</td>
</tr>
</table>
@@ -68,35 +69,43 @@ export default class ShortcutsSection extends BaseSection {
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Z</kbd></td>
<td>${t.__('Undo')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Shift</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>Z</kbd></td>
<td>${t.__('Redo')}</td>
</tr>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Y</kbd></td>
<td>${t.__('Redo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>X</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>X</kbd></td>
<td>${t.__('Cut')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>C</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>C</kbd></td>
<td>${t.__('Copy')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste and Match Style')}</td>
</tr>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Shift</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste and Match Style')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>A</kbd></td>
<td>${t.__('Select All')}</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Control</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>Space</kbd></td>
<td>${t.__('Emoji & Symbols')}</td>
</tr>
</table>
@@ -106,39 +115,63 @@ export default class ShortcutsSection extends BaseSection {
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>R</kbd></td>
<td>${t.__('Reload')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd></td>
<td>${t.__('Hard Reload')}</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Shift</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>R</kbd></td>
<td>${t.__('Hard Reload')}</td>
</tr>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>F11</kbd></td>
<td>${t.__('Toggle Full Screen')}</td>
</tr>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Control</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>F</kbd></td>
<td>${t.__('Enter Full Screen')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>+</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>+</kbd></td>
<td>${t.__('Zoom In')}</td>
</tr>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>=</kbd></td>
<td>${t.__('Zoom In')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>-</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>-</kbd></td>
<td>${t.__('Zoom Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>0</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>0</kbd></td>
<td>${t.__('Actual Size')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>${t.__('Toggle Sidebar')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>I</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Shift</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>S</kbd></td>
<td>${t.__('Toggle Sidebar')}</td>
</tr>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Option</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>I</kbd></td>
<td>${t.__('Toggle DevTools for Zulip App')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>U</kbd></td>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td>
<td>${t.__('Toggle DevTools for Zulip App')}</td>
</tr>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>Option</kbd> + <kbd>${cmdOrCtrl}</kbd> + <kbd>U</kbd></td>
<td>${t.__('Toggle DevTools for Active Tab')}</td>
</tr>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td>
<td>${t.__('Toggle DevTools for Active Tab')}</td>
</tr>
<tr>
@@ -155,159 +188,19 @@ export default class ShortcutsSection extends BaseSection {
<div class="title">${t.__('History Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>←</kbd></td>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>←</kbd></td>
<td>${t.__('Back')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>→</kbd></td>
<td>${t.__('Forward')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Window Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>M</kbd></td>
<td>${t.__('Minimize')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>W</kbd></td>
<td>${t.__('Close')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
</div>
`;
}
templateWinLinHTML(): string {
const userOSKey = 'Ctrl';
return htmlEscape`
<div class="settings-pane">
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>${t.__('Tip')}: </b>${t.__('These desktop app shortcuts extend the Zulip webapp\'s')} <span id="open-hotkeys-link"> ${t.__('keyboard shortcuts')}</span>.</p></div>
<div class="title">${t.__('Application Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>,</kbd></td>
<td>${t.__('Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
<td>${t.__('Keyboard Shortcuts')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>${t.__('Toggle Do Not Disturb')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
<td>${t.__('Log Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Q</kbd></td>
<td>${t.__('Quit Zulip')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('Edit Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Z</kbd></td>
<td>${t.__('Undo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Y</kbd></td>
<td>${t.__('Redo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>X</kbd></td>
<td>${t.__('Cut')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>C</kbd></td>
<td>${t.__('Copy')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste and Match Style')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>A</kbd></td>
<td>${t.__('Select All')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('View Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>R</kbd></td>
<td>${t.__('Reload')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd></td>
<td>${t.__('Hard Reload')}</td>
</tr>
<tr>
<td><kbd>F11</kbd></td>
<td>${t.__('Toggle Full Screen')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>=</kbd></td>
<td>${t.__('Zoom In')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>-</kbd></td>
<td>${t.__('Zoom Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>0</kbd></td>
<td>${t.__('Actual Size')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>${t.__('Toggle Sidebar')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td>
<td>${t.__('Toggle DevTools for Zulip App')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td>
<td>${t.__('Toggle DevTools for Active Tab')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Next Organization')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Previous Organization')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('History Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>Alt</kbd> + <kbd>←</kbd></td>
<td>${t.__('Back')}</td>
</tr>
<tr>
<tr ${process.platform === 'darwin' ? '' : 'hidden'}>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>→</kbd></td>
<td>${t.__('Forward')}</td>
</tr>
<tr ${process.platform === 'darwin' ? 'hidden' : ''}>
<td><kbd>Alt</kbd> + <kbd>→</kbd></td>
<td>${t.__('Forward')}</td>
</tr>
@@ -318,11 +211,11 @@ export default class ShortcutsSection extends BaseSection {
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>M</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>M</kbd></td>
<td>${t.__('Minimize')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>W</kbd></td>
<td><kbd>${cmdOrCtrl}</kbd> + <kbd>W</kbd></td>
<td>${t.__('Close')}</td>
</tr>
</table>
@@ -341,8 +234,7 @@ export default class ShortcutsSection extends BaseSection {
}
init(): void {
this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMacHTML() : this.templateWinLinHTML();
this.props.$root.innerHTML = this.templateHTML();
this.openHotkeysExternalLink();
}
}

View File

@@ -1,23 +1,16 @@
import {contextBridge, ipcRenderer, webFrame} from 'electron';
import fs from 'fs';
import isDev from 'electron-is-dev';
import electron_bridge from './electron-bridge';
import {loadBots} from './notification/helpers';
import electron_bridge, {bridgeEvents} from './electron-bridge';
import * as NetworkError from './pages/network';
// Prevent drag and drop event in main process which prevents remote code executaion
// eslint-disable-next-line import/no-unassigned-import
import './shared/preventdrag';
contextBridge.exposeInMainWorld('raw_electron_bridge', electron_bridge);
electron_bridge.once('zulip-loaded', async () => {
await loadBots();
});
ipcRenderer.on('logout', () => {
if (bridgeEvents.emit('logout')) {
return;
}
// Create the menu for the below
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
dropdown.click();
@@ -26,7 +19,11 @@ ipcRenderer.on('logout', () => {
nodes[nodes.length - 1].click();
});
ipcRenderer.on('shortcut', () => {
ipcRenderer.on('show-keyboard-shortcuts', () => {
if (bridgeEvents.emit('show-keyboard-shortcuts')) {
return;
}
// Create the menu for the below
const node: HTMLElement = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
@@ -40,6 +37,10 @@ ipcRenderer.on('shortcut', () => {
});
ipcRenderer.on('show-notification-settings', () => {
if (bridgeEvents.emit('show-notification-settings')) {
return;
}
// Create the menu for the below
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
dropdown.click();
@@ -55,16 +56,6 @@ ipcRenderer.on('show-notification-settings', () => {
}, 100);
});
electron_bridge.once('zulip-loaded', () => {
// 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');
});
}
});
window.addEventListener('load', (event: any): void => {
if (!event.target.URL.includes('app/renderer/network.html')) {
return;
@@ -75,41 +66,6 @@ window.addEventListener('load', (event: any): void => {
NetworkError.init($reconnectButton, $settingsButton);
});
// Electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
// Zoom from numpad keys is not supported by electron, so adding it through listeners.
document.addEventListener('keydown', event => {
const cmdOrCtrl = event.ctrlKey || event.metaKey;
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
} else if (cmdOrCtrl && (event.code === 'NumpadAdd' || event.code === 'Equal')) {
ipcRenderer.send('forward-message', 'zoomIn');
} else if (cmdOrCtrl && event.code === 'NumpadSubtract') {
ipcRenderer.send('forward-message', 'zoomOut');
} else if (cmdOrCtrl && event.code === 'Numpad0') {
ipcRenderer.send('forward-message', 'zoomActualSize');
}
});
// Set user as active and update the time of last activity
ipcRenderer.on('set-active', () => {
if (isDev) {
console.log('active');
}
electron_bridge.idle_on_system = false;
electron_bridge.last_active_on_system = Date.now();
});
// Set user as idle and time of last activity is left unchanged
ipcRenderer.on('set-idle', () => {
if (isDev) {
console.log('idle');
}
electron_bridge.idle_on_system = true;
});
(async () => webFrame.executeJavaScript(
fs.readFileSync(require.resolve('./injected'), 'utf8')
))();

View File

@@ -1,17 +0,0 @@
// 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 = (): void => {
const preventEvents = ['dragover', 'drop'];
preventEvents.forEach(dragEvents => {
document.addEventListener(dragEvents, event => {
event.preventDefault();
});
});
};
preventDragAndDrop();
export {};

View File

@@ -21,6 +21,8 @@ const iconPath = (): string => {
return APP_ICON + (process.platform === 'win32' ? 'win.ico' : 'macOSTemplate.png');
};
const winUnreadTrayIconPath = (): string => APP_ICON + 'unread.ico';
let unread = 0;
const trayIconSize = (): number => {
@@ -97,6 +99,10 @@ const renderCanvas = function (arg: number): HTMLCanvasElement {
* @return the native image
*/
const renderNativeImage = function (arg: number): NativeImage {
if (process.platform === 'win32') {
return nativeImage.createFromPath(winUnreadTrayIconPath());
}
const canvas = renderCanvas(arg);
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPNG();
return nativeImage.createFromBuffer(pngData, {

View File

@@ -1,79 +0,0 @@
import electron from 'electron';
import fs from 'fs';
import path from 'path';
import {JsonDB} from 'node-json-db';
import {initSetUp} from './default-util';
import Logger from './logger-util';
const {app, dialog} =
process.type === 'renderer' ? electron.remote : electron;
initSetUp();
const logger = new Logger({
file: 'certificate-util.log',
timestamp: true
});
const certificatesDir = `${app.getPath('userData')}/certificates`;
let db: JsonDB;
reloadDB();
export function getCertificate(server: string): string | undefined {
reloadDB();
return db.getData('/')[server];
}
// Function to copy the certificate to userData folder
export function copyCertificate(_server: string, location: string, fileName: string): boolean {
let copied = false;
const filePath = `${certificatesDir}/${fileName}`;
try {
fs.copyFileSync(location, filePath);
copied = true;
} catch (error) {
dialog.showErrorBox(
'Error saving certificate',
'We encountered error while saving the certificate.'
);
logger.error('Error while copying the certificate to certificates folder.');
logger.error(error);
}
return copied;
}
export function setCertificate(server: string, fileName: string): void {
const filePath = `${fileName}`;
db.push(`/${server}`, filePath, true);
reloadDB();
}
export function removeCertificate(server: string): void {
db.delete(`/${server}`);
reloadDB();
}
function reloadDB(): void {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/certificates.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
JSON.parse(file);
} catch (error) {
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(error);
}
}
db = new JsonDB(settingsJsonPath, true, true);
}

View File

@@ -32,7 +32,7 @@ reloadDB();
export function getConfigItem(key: string, defaultValue: unknown = null): any {
try {
db.reload();
} catch (error) {
} catch (error: unknown) {
logger.error('Error while reloading settings.json: ');
logger.error(error);
}
@@ -60,7 +60,7 @@ export function getConfigString(key: string, defaultValue: string): string {
export function isConfigItemExists(key: string): boolean {
try {
db.reload();
} catch (error) {
} catch (error: unknown) {
logger.error('Error while reloading settings.json: ');
logger.error(error);
}
@@ -89,7 +89,7 @@ function reloadDB(): void {
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
JSON.parse(file);
} catch (error) {
} catch (error: unknown) {
if (fs.existsSync(settingsJsonPath)) {
fs.unlinkSync(settingsJsonPath);
dialog.showErrorBox(

View File

@@ -1,17 +1,11 @@
import electron from 'electron';
import fs from 'fs';
let app: Electron.App = null;
const app = process.type === 'renderer' ? electron.remote.app : electron.app;
let setupCompleted = false;
if (process.type === 'renderer') {
app = electron.remote.app;
} else {
app = electron.app;
}
const zulipDir = app.getPath('userData');
const logDir = `${zulipDir}/Logs/`;
const certificatesDir = `${zulipDir}/certificates/`;
const configDir = `${zulipDir}/config/`;
export const initSetUp = (): void => {
// If it is the first time the app is running
@@ -26,16 +20,11 @@ export const initSetUp = (): void => {
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`;
@@ -44,10 +33,6 @@ export const initSetUp = (): void => {
path: domainJson,
fileName: 'domain.json'
},
{
path: certificatesJson,
fileName: 'certificates.json'
},
{
path: settingsJson,
fileName: 'settings.json'

View File

@@ -91,27 +91,6 @@ export function duplicateDomain(domain: string): boolean {
return getDomains().some(server => server.url === domain);
}
async function checkCertError(domain: string, serverConf: ServerConf, error: any, silent: boolean): Promise<ServerConf> {
if (silent) {
// Since getting server settings has already failed
return serverConf;
}
// Report error to sentry to get idea of possible certificate errors
// users get when adding the servers
logger.reportSentry(error);
const certErrorMessage = Messages.certErrorMessage(domain, error);
const certErrorDetail = Messages.certErrorDetail();
await dialog.showMessageBox({
type: 'error',
buttons: ['OK'],
message: certErrorMessage,
detail: certErrorDetail
});
throw new Error('Untrusted certificate.');
}
export async function checkDomain(domain: string, silent = false): Promise<ServerConf> {
if (!silent && duplicateDomain(domain)) {
// Do not check duplicate in silent mode
@@ -120,25 +99,9 @@ export async function checkDomain(domain: string, silent = false): Promise<Serve
domain = formatUrl(domain);
const serverConf = {
icon: defaultIconUrl,
url: domain,
alias: domain
};
try {
return await getServerSettings(domain);
} catch (error_) {
// Make sure that error is an error or string not undefined
// so validation does not throw error.
const error = error_ || '';
const certsError = error.toString().includes('certificate');
if (certsError) {
const result = await checkCertError(domain, serverConf, error, silent);
return result;
}
} catch {
throw new Error(Messages.invalidZulipServerError(domain));
}
}
@@ -162,7 +125,7 @@ export async function updateSavedServer(url: string, index: number): Promise<voi
updateDomain(index, newServerConf);
reloadDB();
}
} catch (error) {
} catch (error: unknown) {
logger.log('Could not update server icon.');
logger.log(error);
logger.reportSentry(error);
@@ -174,7 +137,7 @@ function reloadDB(): void {
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
} catch (error) {
} catch (error: unknown) {
if (fs.existsSync(domainJsonPath)) {
fs.unlinkSync(domainJsonPath);
dialog.showErrorBox(

View File

@@ -9,7 +9,7 @@ const logger = new Logger({
});
// TODO: replace enterpriseSettings type with an interface once settings are final
let enterpriseSettings: {[key: string]: unknown};
let enterpriseSettings: Record<string, unknown>;
let configFile: boolean;
reloadDB();
@@ -26,7 +26,7 @@ function reloadDB(): void {
try {
const file = fs.readFileSync(enterpriseFile, 'utf8');
enterpriseSettings = JSON.parse(file);
} catch (error) {
} catch (error: unknown) {
logger.log('Error while JSON parsing global_config.json: ');
logger.log(error);
}

View File

@@ -47,7 +47,7 @@ function reloadDB(): void {
try {
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
JSON.parse(file);
} catch (error) {
} catch (error: unknown) {
if (fs.existsSync(linuxUpdateJsonPath)) {
fs.unlinkSync(linuxUpdateJsonPath);
dialog.showErrorBox(

View File

@@ -70,9 +70,9 @@ export default class Logger {
// Trim log according to type of process
if (process.type === 'renderer') {
requestIdleCallback(() => this.trimLog(file));
requestIdleCallback(async () => this.trimLog(file));
} else {
process.nextTick(() => this.trimLog(file));
process.nextTick(async () => this.trimLog(file));
}
const fileStream = fs.createWriteStream(file, {flags: 'a'});
@@ -116,7 +116,9 @@ export default class Logger {
}
setupConsoleMethod(type: Level): void {
this[type] = (...args: unknown[]) => this._log(type, ...args);
this[type] = (...args: unknown[]) => {
this._log(type, ...args);
};
}
getTimestamp(): string {
@@ -127,28 +129,24 @@ export default class Logger {
return timestamp;
}
reportSentry(err: unknown): void {
reportSentry(error: unknown): void {
if (reportErrors) {
captureException(err);
captureException(error);
}
}
trimLog(file: string): void{
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
throw err;
}
async trimLog(file: string): Promise<void> {
const data = await fs.promises.readFile(file, 'utf8');
const MAX_LOG_FILE_LINES = 500;
const logs = data.split(os.EOL);
const logLength = logs.length - 1;
const MAX_LOG_FILE_LINES = 500;
const logs = data.split(os.EOL);
const logLength = logs.length - 1;
// Keep bottom MAX_LOG_FILE_LINES of each log instance
if (logLength > MAX_LOG_FILE_LINES) {
const trimmedLogs = logs.slice(logLength - MAX_LOG_FILE_LINES);
const toWrite = trimmedLogs.join(os.EOL);
fs.writeFileSync(file, toWrite);
}
});
// Keep bottom MAX_LOG_FILE_LINES of each log instance
if (logLength > MAX_LOG_FILE_LINES) {
const trimmedLogs = logs.slice(logLength - MAX_LOG_FILE_LINES);
const toWrite = trimmedLogs.join(os.EOL);
await fs.promises.writeFile(file, toWrite);
}
}
}

View File

@@ -15,12 +15,10 @@ export async function resolveSystemProxy(mainWindow: Electron.BrowserWindow): Pr
const httpProxy = (async () => {
const proxy = await ses.resolveProxy('http://' + resolveProxyUrl);
let httpString = '';
if (proxy !== 'DIRECT') {
if (proxy !== 'DIRECT' && (proxy.includes('PROXY') || 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')) {
httpString = 'http=' + proxy.split('PROXY')[1] + ';';
}
httpString = 'http=' + proxy.split('PROXY')[1] + ';';
}
return httpString;
@@ -29,12 +27,10 @@ export async function resolveSystemProxy(mainWindow: Electron.BrowserWindow): Pr
const httpsProxy = (async () => {
const proxy = await ses.resolveProxy('https://' + resolveProxyUrl);
let httpsString = '';
if (proxy !== 'DIRECT' || proxy.includes('HTTPS')) {
if ((proxy !== 'DIRECT' || proxy.includes('HTTPS')) && (proxy.includes('PROXY') || 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] + ';';
}
httpsString += 'https=' + proxy.split('PROXY')[1] + ';';
}
return httpsString;
@@ -44,10 +40,8 @@ export async function resolveSystemProxy(mainWindow: Electron.BrowserWindow): Pr
const ftpProxy = (async () => {
const proxy = await ses.resolveProxy('ftp://' + resolveProxyUrl);
let ftpString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('PROXY')) {
ftpString += 'ftp=' + proxy.split('PROXY')[1] + ';';
}
if (proxy !== 'DIRECT' && proxy.includes('PROXY')) {
ftpString += 'ftp=' + proxy.split('PROXY')[1] + ';';
}
return ftpString;

View File

@@ -1,6 +1,6 @@
import {ipcRenderer} from 'electron';
import backoff from 'backoff';
import * as backoff from 'backoff';
import {htmlEscape} from 'escape-goat';
import type WebView from '../components/webview';

View File

@@ -61,5 +61,4 @@
// it messes up require module path resolution
require('./js/main');
</script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

View File

@@ -16,6 +16,5 @@
<script>
document.querySelector('#tagify-css').href = require.resolve('@yaireo/tagify/dist/tagify.css');
require('./js/pages/preference/preference.js');
require('./js/shared/preventdrag.js')
</script>
</html>

View File

@@ -8,7 +8,7 @@ export function invalidZulipServerError(domain: string): string {
\n • You can connect to that URL in a web browser.\
\n • If you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings.\
\n • It's a Zulip server. (The oldest supported version is 1.6).\
\n • The server has a valid certificate. (You can add custom certificates in Settings > Organizations). \
\n • The server has a valid certificate. \
\n • The SSL is correctly configured for the certificate. Check out the SSL troubleshooting guide -
\n https://zulip.readthedocs.io/en/stable/production/ssl-certificates.html`;
}
@@ -18,15 +18,6 @@ export function noOrgsError(domain: string): string {
\nPlease contact your server administrator.`;
}
export function certErrorMessage(domain: string, error: string): string {
return `Certificate error for ${domain}\n${error}`;
}
export function certErrorDetail(): string {
return `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.`;
}
export function enterpriseOrgError(length: number, domains: string[]): DialogBoxError {
let domainList = '';
for (const domain of domains) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -2,15 +2,14 @@ version: build{build}
platform:
- x64
os: Previous Visual Studio 2015
os: Visual Studio 2015
cache:
- node_modules -> appveyor.yml
install:
- ps: Install-Product node 10 x64
- git reset --hard HEAD
- npm install npm -g
- ps: Install-Product node LTS $env:platform
- node --version
- npm --version
- python --version

View File

@@ -2,10 +2,40 @@
All notable changes to the Zulip desktop app are documented in this file.
### v5.4.2 --2020-08-12
### v5.6.0 --2021-02-16
**Potential Fixes**:
* macOS: Electron 9 upgrade is a potential fix for the ['grey screen issue'](https://chat.zulip.org/#narrow/stream/9-issues/topic/Grey.20Window.20on.20macOS) reported.
**Fixes**:
* Fixed zoom in keyboard shortcut not to zoom twice.
**Enhancements**:
* Restored hardware acceleration to the Electron default.
**Dependencies**:
* Upgraded all dependencies, including Electron 11.2.3.
### v5.5.0 --2020-12-01
**Removed features**:
* Removed legacy handling of custom certificates. Custom certificates can be configured in the same system certificate store that Chrome uses ([instructions](https://zulip.com/help/custom-certificates#desktop)).
* Removed the unmaintained notification inline replies feature on macOS. We believe the `node-mac-notifier` library used by this feature had been responsible for the grey screen crash issue.
**Fixes**:
* Fixed a regression with the factory reset function.
* Fixed the grey screen crash issue on macOS ([#1016](https://github.com/zulip/zulip-desktop/issues/1016)).
* Whitespace is now stripped from the organization URL when adding a new organization.
**Dependencies**:
* Upgraded all dependencies, including Electron 11.0.3.
### v5.4.3 --2020-09-10
**Security fixes**:
* CVE-2020-24582: Escape all strings interpolated into HTML to close cross-site scripting vulnerabilities that a malicious server could exploit.
**Dependencies**:
* Upgrade dependencies, including Electron 9.3.0.
### v5.4.2 --2020-08-12
**Dependencies**:
* Upgrade all dependencies, including Electron 9.2.0.
@@ -15,9 +45,6 @@ All notable changes to the Zulip desktop app are documented in this file.
**Fixes**:
* Resized the large application icon on macOS dock to be coherent with other icons.
**Potential Fixes**:
* macOS: Electron 9 upgrade is a potential fix for the ['grey screen issue'](https://chat.zulip.org/#narrow/stream/9-issues/topic/Grey.20Window.20on.20macOS) reported.
**Dependencies**:
* Upgrade all dependencies, including Electron 9.1.1.

View File

@@ -1,3 +1,3 @@
### Want to contribute to this Wiki?
[Edit `/docs` files and send a pull request.](https://github.com/zulip/zulip-desktop/tree/master/docs)
[Edit `/docs` files and send a pull request.](https://github.com/zulip/zulip-desktop/tree/main/docs)

3491
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": "5.4.3",
"version": "5.6.0",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
@@ -18,13 +18,13 @@
"url": "https://github.com/zulip/zulip-desktop/issues"
},
"engines": {
"node": ">=10.0.0"
"node": ">=12.10.0"
},
"scripts": {
"start": "tsc && electron .",
"clean-ts-files": "git clean app/*.js -e node_modules -xf",
"watch-ts": "tsc -w",
"reinstall": "node ./tools/reinstall-node-modules.js",
"reinstall": "rimraf node_modules && npm install",
"postinstall": "electron-builder install-app-deps",
"lint-css": "stylelint app/renderer/css/*.css",
"lint-html": "./node_modules/.bin/htmlhint \"app/renderer/*.html\" ",
@@ -68,7 +68,6 @@
"linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
"icon": "build/icon.icns",
"packageCategory": "GNOME;GTK;Network;InstantMessaging",
"description": "Zulip Desktop Client for Linux",
"target": [
"deb",
@@ -80,9 +79,14 @@
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"deb": {
"packageCategory": "net",
"synopsis": "Zulip Desktop App",
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
"afterInstall": "./packaging/deb-after-install.sh",
"fpm": [
"./packaging/deb-apt.list=/etc/apt/sources.list.d/zulip-desktop.list",
"./packaging/deb-apt.asc=/etc/apt/trusted.gpg.d/zulip-desktop.asc",
"./packaging/deb-release-upgrades.cfg=/etc/update-manager/release-upgrades.d/zulip-desktop.cfg"
]
},
"snap": {
"synopsis": "Zulip Desktop App"
@@ -146,60 +150,53 @@
],
"dependencies": {
"@electron-elements/send-feedback": "^2.0.3",
"@sentry/electron": "^2.0.0",
"@yaireo/tagify": "^3.17.10",
"adm-zip": "^0.4.16",
"@sentry/electron": "^2.3.0",
"@yaireo/tagify": "^3.22.3",
"adm-zip": "^0.5.2",
"auto-launch": "^5.0.5",
"backoff": "^2.5.0",
"electron-is-dev": "^1.2.0",
"electron-log": "^4.2.4",
"electron-updater": "^4.3.4",
"electron-log": "^4.3.1",
"electron-updater": "^4.3.5",
"electron-window-state": "^5.0.3",
"escape-goat": "^3.0.0",
"fs-extra": "^9.0.1",
"get-stream": "^6.0.0",
"i18n": "^0.13.2",
"iso-639-1": "^2.1.4",
"nan": "^2.14.0",
"iso-639-1": "^2.1.8",
"node-json-db": "^1.1.0",
"semver": "^7.3.2"
},
"optionalDependencies": {
"node-mac-notifier": "^1.1.0"
"semver": "^7.3.4"
},
"devDependencies": {
"@types/adm-zip": "^0.4.33",
"@types/auto-launch": "^5.0.1",
"@types/backoff": "^2.5.1",
"@types/fs-extra": "^9.0.1",
"@types/i18n": "^0.8.7",
"@types/node": "^14.6.4",
"@types/i18n": "^0.12.0",
"@types/node": "^14.14.28",
"@types/requestidlecallback": "^0.3.1",
"devtron": "^1.4.0",
"dotenv": "^8.2.0",
"electron": "^9.3.0",
"electron-builder": "^22.8.0",
"electron": "^11.2.3",
"electron-builder": "^22.9.1",
"electron-connect": "^0.6.3",
"electron-notarize": "^1.0.0",
"eslint-import-resolver-typescript": "^2.4.0",
"glob": "^7.1.6",
"gulp": "^4.0.2",
"gulp-tape": "^1.0.0",
"gulp-typescript": "^6.0.0-alpha.1",
"htmlhint": "^0.14.1",
"nodemon": "^2.0.4",
"htmlhint": "^0.14.2",
"nodemon": "^2.0.7",
"pre-commit": "^1.2.2",
"rimraf": "^3.0.2",
"spectron": "^11.1.0",
"stylelint": "^13.7.0",
"spectron": "^13.0.0",
"stylelint": "^13.10.0",
"tap-colorize": "^1.2.0",
"tape": "^5.0.1",
"typescript": "^4.0.2",
"xo": "^0.33.1"
"tape": "^5.1.1",
"typescript": "^4.1.5",
"xo": "^0.37.1"
},
"xo": {
"rules": {
"@typescript-eslint/no-dynamic-delete": "off",
"@typescript-eslint/prefer-readonly-parameter-types": "off",
"arrow-body-style": "error",
"import/first": "error",
"import/newline-after-import": "error",
@@ -229,13 +226,20 @@
"browser"
],
"overrides": [
{
"files": [
"**/*.ts"
],
"settings": {
"import/resolver": "typescript"
}
},
{
"files": [
"app/renderer/js/injected.ts",
"gulpfile.js",
"scripts/notarize.js",
"tests/**/*.js",
"tools/reinstall-node-modules.js"
"tests/**/*.js"
],
"parserOptions": {
"sourceType": "script"

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# Link to the binary
ln -sf '/opt/${productFilename}/${executable}' '/usr/bin/${executable}'
# SUID chrome-sandbox for Electron 5+
chmod 4755 '/opt/${productFilename}/chrome-sandbox' || true
update-mime-database /usr/share/mime || true
update-desktop-database /usr/share/applications || true
# Clean up configuration for old Bintray repository
rm -f /etc/apt/zulip.list

30
packaging/deb-apt.asc Normal file
View File

@@ -0,0 +1,30 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFmdzvQBCADJ4BFlK+4ymIWa3jrNL0WfGPV3dVkZ1Ghy5MsgRIs81CpVS83m
kyBLULY551GNwuHZaeXbkaA+cTDyhEPBFr0MTF0gO514escnjwcL7U1UCLA4I0WP
0yETXLHp7HFh4g+MZpObkgmLP55aV3jqgNK/p05umrhECBl1HJo+8T+0VNi2x1Pm
LoJVvA7uJHcsNaQVWQF4RP0MaI4TLyjHZAJlpthQfbmq0AbZMEjDu8Th5G9KTsqE
WRyFoAj/SWwKQK2U4xpnA6jEraMcvsYYQMrCXlG+MOV7zVknLrH5tfk7JlmWB4DV
cs+QP5Z/UrVu+YpTpaoJoZV6LlEU1kNGjtq9ABEBAAG0TVp1bGlwIEFQVCBSZXBv
c2l0b3J5IFNpZ25pbmcgS2V5IEJpbnRyYXkgKFByb2R1Y3Rpb24pIDxzdXBwb3J0
QHp1bGlwY2hhdC5jb20+iQE4BBMBAgAiBQJZnc70AhsDBgsJCAcDAgYVCAIJCgsE
FgIDAQIeAQIXgAAKCRAkJL5a6b0Q2Vg1CADJzrH0mbwKi5GiHo5+iX5/WuUkSA8S
lI7FWzkbnPD0sfxJBwBNhZnAALQUvCybHxoU8VZ5ZbU1vbU+EG7pUMzENZLgEhoC
MDl1j8uCSahjjO+bk8qHhgM1FUKpoGec2wKfPKpcz1P+/bLTRKe7aqilkPSYOjeV
u8JI713zRL0nHd9vYZDoN2HR30J5sqgjRHtK5okNhiFG+pF3HFATG7nbNOa/tv+q
ZvhbI/5S8P5VKPSK/1lmMh0UFyNIbPg6MvWiqnfy7DAvOZGJpawkiN2B0XhNZKZR
KKXvFk3qvFpNTCUrH77MlPgjn+oRbE9SYm0phj0o2jQi/s1s2r75tk/ZuQENBFmd
zvQBCACv7VNQ6x3hfaRl8YF8bbrWXN2ZWxEa353p4QryHODsa7wHtsoNR3P30TIL
yafjjcV8P6dzyDw6TpfRqqQDKLY6FtznT2HdceQSffGTXB4CRV7KURBqh81PX/Jo
dz0NwkNrd0NWqkk6BnLX6U5tGuYiqC3vLpjOHmVQezJ41xpf85ElJ2nBW0rEcmfk
fwQthJU7BbqWKd6nbt2G+xWkCVoN6q+CWLXtK0laHMKBGQnoiQpldotsKM8UnDeQ
XPqrEi28ksjVW8tBStCkLwV2hCxk49zdTvRjrhBTQ1Ff/kenuEwqbSERiKfA7I8o
mlqulSiJ6rYdDnGjNcoRgnHb50hTABEBAAGJAR8EGAECAAkFAlmdzvQCGwwACgkQ
JCS+Wum9ENnsOQgApQ2+4azOXprYQXj1ImamD30pmvvKD06Z7oDzappFpEXzRSJK
tMfNaowG7YrXujydrpqaOgv4kFzaAJizWGbmOKXTwQJnavGC1JC4Lijx0s3CLtms
OY3EC2GMNTp2rACuxZQ+26lBuPG8Nd+rNnP8DSzdQROQD2EITplqR1Rc0FLHGspu
rL0JsVTuWS3qSpR3nlmwuLjVgIs5KEaOVEa4pkH9QwyAFDsprF0uZP8xAAs8WrVr
Isg3zs7YUcAtu/i6C2jPuMsHjGfKStkYW/4+wONIynhoFjqeYrR0CiZ9lvVa3tJk
BCeqaQFskx1HhgWBT9Qqc73+i45udWUsa3issg==
=YJGK
-----END PGP PUBLIC KEY BLOCK-----

1
packaging/deb-apt.list Normal file
View File

@@ -0,0 +1 @@
deb https://download.zulip.com/desktop/apt stable main

View File

@@ -0,0 +1,2 @@
[ThirdPartyMirrors]
zulip-desktop=https://download.zulip.com/desktop/apt

View File

@@ -1,19 +0,0 @@
#!/bin/bash
# This script runs when user install the debian package
# Link to the binary
ln -sf '/opt/${productFilename}/${executable}' '/usr/bin/${executable}'
# SUID chrome-sandbox for Electron 5+
chmod 4755 '/opt/${productFilename}/chrome-sandbox' || true
update-mime-database /usr/share/mime || true
update-desktop-database /usr/share/applications || true
# Install apt repository source list if it does not exist
if ! grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then
sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9
echo "deb https://dl.bintray.com/zulip/debian/ stable main" | \
sudo tee -a /etc/apt/sources.list.d/zulip.list;
fi

View File

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

View File

@@ -66,10 +66,10 @@ async function wait(ms) {
}
// Quit the app, end the test, either in success (!err) or failure (err)
async function endTest(app, t, err) {
async function endTest(app, t, error) {
await app.client.windowByIndex(0);
await app.stop();
t.end(err);
t.end(error);
}
function getAppDataDir() {

View File

@@ -12,6 +12,6 @@ fi
request_id="$1"
remote=${2:-"upstream"}
git fetch "$remote" "pull/$request_id/head"
git checkout -B "review-${request_id}" $remote/master
git checkout -B "review-${request_id}" $remote/main
git reset --hard FETCH_HEAD
git pull --rebase

View File

@@ -11,7 +11,7 @@ if "%~1"=="" (
echo "Error you must specify the PR number"
)
if "%~2"=="" (
if "%~2"=="" (
set remote="upstream"
) else (
set remote=%2
@@ -19,6 +19,6 @@ if "%~2"=="" (
set request_id="%1"
git fetch "%remote%" "pull/%request_id%/head"
git checkout -B "review-%request_id%" %remote%/master
git checkout -B "review-%request_id%" %remote%/main
git reset --hard FETCH_HEAD
git pull --rebase

View File

@@ -7,9 +7,9 @@ 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,
Useful for a maintainer to run just before pushing to main,
after tweaking the branch and/or rebasing to latest. This causes
GitHub to see the subsequent push to master as representing a
GitHub to see the subsequent push to main as representing a
merge of the PR, rather than requiring the PR to be manually
(and to the casual observer misleadingly) closed instead.

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env node
'use strict';
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);
});

4
typings.d.ts vendored
View File

@@ -21,8 +21,6 @@ declare module '@electron-elements/send-feedback' {
declare module 'electron-connect';
declare module 'node-mac-notifier';
declare module '@yaireo/tagify';
interface ClipboardDecrypter {
@@ -32,7 +30,7 @@ interface ClipboardDecrypter {
}
interface ElectronBridge {
send_event: (eventName: string | symbol, ...args: unknown[]) => void;
send_event: (eventName: string | symbol, ...args: unknown[]) => boolean;
on_event: (eventName: string, listener: ListenerType) => void;
new_notification: (
title: string,

View File

@@ -3,7 +3,7 @@
# Zulip Beta Client Launcher
# This script ensures that you have the latest version of the specified branch
# (defaults to master if none specified) and then updates or installs all your
# (defaults to main if none specified) and then updates or installs all your
# required npm modules.
# I recommend symlinking this script into your PATH.
@@ -22,7 +22,7 @@ showUsage()
envSetup()
{
defaultBranch="master"
defaultBranch="main"
startingDir=`pwd`
requirePop=0
@@ -64,12 +64,12 @@ gitCheckout()
{
git fetch $upstreamRemote
git checkout $myBranch
git rebase $upstreamRemote/master
git rebase $upstreamRemote/main
if [ $? -gt 0 ]
then
echo "Stashing uncommitted changes and doing a new git pull"
git stash && requirePop=1
git rebase $upstreamRemote/master
git rebase $upstreamRemote/main
fi
}