mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-10-24 08:33:36 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a0f9b30e6 | ||
|
|
109795ca3e | ||
|
|
e6e5e8a311 | ||
|
|
bd0869ec07 | ||
|
|
07ae127cc8 | ||
|
|
baa76c3244 | ||
|
|
7ac31f80ed | ||
|
|
a95ee64f7d | ||
|
|
7d6c6bc10a | ||
|
|
18b41938de | ||
|
|
9fe382b27f | ||
|
|
f022b338e6 | ||
|
|
855d99dfa0 | ||
|
|
cc2424e0bf | ||
|
|
fa6d72268f | ||
|
|
762dd92ec3 | ||
|
|
2e90e24552 | ||
|
|
d7adce0ebf | ||
|
|
a1bb6da4fb | ||
|
|
873fecf548 | ||
|
|
682511bb68 | ||
|
|
02fbe1a6a1 | ||
|
|
0cb82a6f5e | ||
|
|
79808e8ee9 | ||
|
|
2c38df10c8 | ||
|
|
1ca15d44a0 | ||
|
|
82450a91a9 | ||
|
|
62edfa6f8b | ||
|
|
fe86315ece | ||
|
|
df3f719e89 | ||
|
|
0632d8199f | ||
|
|
047bf0ca45 | ||
|
|
356c879668 | ||
|
|
ba432d32b3 | ||
|
|
c8ada3f47d | ||
|
|
cd77fc6448 | ||
|
|
a2f926c611 | ||
|
|
6c5eb85a16 | ||
|
|
cadb1c6eaa | ||
|
|
73710319e6 | ||
|
|
da91dc5595 | ||
|
|
31d5e5a092 | ||
|
|
13ee1d0990 | ||
|
|
d5a9063378 | ||
|
|
918064f35d | ||
|
|
193b8326bc | ||
|
|
9abb7f376e | ||
|
|
ac338fa438 | ||
|
|
f5b78ee845 | ||
|
|
126bb26a6e | ||
|
|
23e86abb5b | ||
|
|
3a3714787f | ||
|
|
bc57aabc97 | ||
|
|
08df02a1ea |
15
.github/workflows/node.js.yml
vendored
Normal file
15
.github/workflows/node.js.yml
vendored
Normal 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
|
||||
23
.travis.yml
23
.travis.yml
@@ -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
|
||||
@@ -1,13 +1,13 @@
|
||||
# Zulip Desktop Client
|
||||
[](https://travis-ci.org/zulip/zulip-desktop)
|
||||
[](https://ci.appveyor.com/project/zulip/zulip-desktop/branch/master)
|
||||
[](https://travis-ci.com/github/zulip/zulip-desktop)
|
||||
[](https://ci.appveyor.com/project/zulip/zulip-desktop/branch/main)
|
||||
[](https://github.com/sindresorhus/xo)
|
||||
[](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"/>
|
||||

|
||||

|
||||
|
||||
# Download
|
||||
Please see the [installation guide](https://zulip.com/help/desktop-app-install-guide).
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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')));
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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`;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
))();
|
||||
|
||||
@@ -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 {};
|
||||
@@ -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, {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -61,5 +61,4 @@
|
||||
// it messes up require module path resolution
|
||||
require('./js/main');
|
||||
</script>
|
||||
<script>require('./js/shared/preventdrag.js')</script>
|
||||
</html>
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
BIN
app/resources/tray/trayunread.ico
Normal file
BIN
app/resources/tray/trayunread.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
@@ -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
|
||||
|
||||
39
changelog.md
39
changelog.md
@@ -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.
|
||||
|
||||
|
||||
@@ -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
3491
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
72
package.json
72
package.json
@@ -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"
|
||||
|
||||
13
packaging/deb-after-install.sh
Normal file
13
packaging/deb-after-install.sh
Normal 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
30
packaging/deb-apt.asc
Normal 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
1
packaging/deb-apt.list
Normal file
@@ -0,0 +1 @@
|
||||
deb https://download.zulip.com/desktop/apt stable main
|
||||
2
packaging/deb-release-upgrades.cfg
Normal file
2
packaging/deb-release-upgrades.cfg
Normal file
@@ -0,0 +1,2 @@
|
||||
[ThirdPartyMirrors]
|
||||
zulip-desktop=https://download.zulip.com/desktop/apt
|
||||
@@ -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
|
||||
@@ -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}'
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -1,7 +0,0 @@
|
||||
@echo off
|
||||
|
||||
echo "Removing node_modules"
|
||||
rmdir /s /q node_modules
|
||||
|
||||
echo "node_modules removed reinstalling npm packages"
|
||||
npm i
|
||||
@@ -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
4
typings.d.ts
vendored
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user