mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-11-04 14:03:27 +00:00
Compare commits
3 Commits
v3.1.0-bet
...
windows-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3768ec6055 | ||
|
|
c029e70046 | ||
|
|
4773ab0d15 |
@@ -2,7 +2,6 @@ root = true
|
|||||||
|
|
||||||
[*]
|
[*]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 4
|
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
<!-- Please Include: -->
|
<!-- Please Include: -->
|
||||||
- **Operating System**:
|
- **Operating System**:
|
||||||
- [ ] Windows
|
- [ ] Windows
|
||||||
- [ ] Linux/Ubuntu
|
- [ ] Linux/Ubutnu
|
||||||
- [ ] macOS
|
- [ ] macOS
|
||||||
- **Clear steps to reproduce the issue**:
|
- **Clear steps to reproduce the issue**:
|
||||||
- **Relevant error messages and/or screenshots**:
|
- **Relevant error messages and/or screenshots**:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -36,6 +36,3 @@ config.gypi
|
|||||||
# tests/package.json
|
# tests/package.json
|
||||||
|
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
# Ignore all the typescript compiled files
|
|
||||||
app/**/*.js
|
|
||||||
|
|||||||
25
.htmlhintrc
25
.htmlhintrc
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"tagname-lowercase": true,
|
|
||||||
"attr-lowercase": true,
|
|
||||||
"attr-value-double-quotes": true,
|
|
||||||
"attr-value-not-empty": false,
|
|
||||||
"attr-no-duplication": true,
|
|
||||||
"doctype-first": true,
|
|
||||||
"tag-pair": true,
|
|
||||||
"empty-tag-not-self-closed": true,
|
|
||||||
"spec-char-escape": true,
|
|
||||||
"id-unique": true,
|
|
||||||
"src-not-empty": true,
|
|
||||||
"title-require": true,
|
|
||||||
"alt-require": false,
|
|
||||||
"doctype-html5": true,
|
|
||||||
"id-class-value": "dash",
|
|
||||||
"style-disabled": false,
|
|
||||||
"inline-style-disabled": false,
|
|
||||||
"inline-script-disabled": false,
|
|
||||||
"space-tab-mixed-disabled": "space4",
|
|
||||||
"id-class-ad-disabled": false,
|
|
||||||
"href-abs-or-rel": false,
|
|
||||||
"attr-unsafe-chars": true,
|
|
||||||
"head-script-disabled": true
|
|
||||||
}
|
|
||||||
67
.stylelintrc
67
.stylelintrc
@@ -1,67 +0,0 @@
|
|||||||
{
|
|
||||||
"rules": {
|
|
||||||
# Stylistic rules for CSS.
|
|
||||||
"function-comma-space-after": "always",
|
|
||||||
"function-comma-space-before": "never",
|
|
||||||
"function-max-empty-lines": 0,
|
|
||||||
"function-whitespace-after": "always",
|
|
||||||
|
|
||||||
"value-keyword-case": "lower",
|
|
||||||
"value-list-comma-newline-after": "always-multi-line",
|
|
||||||
"value-list-comma-space-after": "always-single-line",
|
|
||||||
"value-list-comma-space-before": "never",
|
|
||||||
"value-list-max-empty-lines": 0,
|
|
||||||
|
|
||||||
"unit-case": "lower",
|
|
||||||
"property-case": "lower",
|
|
||||||
"color-hex-case": "lower",
|
|
||||||
|
|
||||||
"declaration-bang-space-before": "always",
|
|
||||||
"declaration-colon-newline-after": "always-multi-line",
|
|
||||||
"declaration-colon-space-after": "always-single-line",
|
|
||||||
"declaration-colon-space-before": "never",
|
|
||||||
"declaration-block-semicolon-newline-after": "always",
|
|
||||||
"declaration-block-semicolon-space-before": "never",
|
|
||||||
"declaration-block-trailing-semicolon": "always",
|
|
||||||
|
|
||||||
"block-closing-brace-empty-line-before": "never",
|
|
||||||
"block-closing-brace-newline-after": "always",
|
|
||||||
"block-closing-brace-newline-before": "always",
|
|
||||||
"block-opening-brace-newline-after": "always",
|
|
||||||
"block-opening-brace-space-before": "always",
|
|
||||||
|
|
||||||
"selector-attribute-brackets-space-inside": "never",
|
|
||||||
"selector-attribute-operator-space-after": "never",
|
|
||||||
"selector-attribute-operator-space-before": "never",
|
|
||||||
"selector-combinator-space-after": "always",
|
|
||||||
"selector-combinator-space-before": "always",
|
|
||||||
"selector-descendant-combinator-no-non-space": true,
|
|
||||||
"selector-pseudo-class-parentheses-space-inside": "never",
|
|
||||||
"selector-pseudo-element-case": "lower",
|
|
||||||
"selector-pseudo-element-colon-notation": "double",
|
|
||||||
"selector-type-case": "lower",
|
|
||||||
"selector-list-comma-newline-after": "always",
|
|
||||||
"selector-list-comma-space-before": "never",
|
|
||||||
|
|
||||||
"media-feature-colon-space-after": "always",
|
|
||||||
"media-feature-colon-space-before": "never",
|
|
||||||
"media-feature-name-case": "lower",
|
|
||||||
"media-feature-parentheses-space-inside": "never",
|
|
||||||
"media-feature-range-operator-space-after": "always",
|
|
||||||
"media-feature-range-operator-space-before": "always",
|
|
||||||
"media-query-list-comma-newline-after": "always",
|
|
||||||
"media-query-list-comma-space-before": "never",
|
|
||||||
|
|
||||||
"at-rule-name-case": "lower",
|
|
||||||
"at-rule-name-space-after": "always",
|
|
||||||
"at-rule-semicolon-newline-after": "always",
|
|
||||||
"at-rule-semicolon-space-before": "never",
|
|
||||||
|
|
||||||
"comment-whitespace-inside": "always",
|
|
||||||
"indentation": 4,
|
|
||||||
|
|
||||||
# Limit language features
|
|
||||||
"color-no-hex": true,
|
|
||||||
"color-named": "never",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,20 +2,20 @@
|
|||||||
|
|
||||||
Thanks for taking the time to contribute!
|
Thanks for taking the time to contribute!
|
||||||
|
|
||||||
The following is a set of guidelines for contributing to Zulip's desktop Client. These are just guidelines, not rules, so use your best judgement and feel free to propose changes to this document in a pull request.
|
The following is a set of guidelines for contributing to Zulip Electron Desktop Client. These are just guidelines, not rules, so use your best judgement and feel free to propose changes to this document in a pull request.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](https://jlord.dev/blog/essential-electron) great article.
|
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](http://jlord.us/essential-electron/) great article.
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
|
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
|
||||||
|
|
||||||
* If you have any questions regarding zulip-desktop, open an [issue](https://github.com/zulip/zulip-desktop/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/16-desktop).
|
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/16-desktop).
|
||||||
|
|
||||||
## Issue
|
## Issue
|
||||||
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-desktop/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-desktop/issues/new).
|
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-electron/issues/new).
|
||||||
|
|
||||||
The [zulipbot](https://github.com/zulip/zulipbot) helps to claim an issue by commenting the following in the comment section: "**@zulipbot** claim". **@zulipbot** will assign you to the issue and label the issue as **in progress**. For more details, check out [**@zulipbot**](https://github.com/zulip/zulipbot).
|
The [zulipbot](https://github.com/zulip/zulipbot) helps to claim an issue by commenting the following in the comment section: "**@zulipbot** claim". **@zulipbot** will assign you to the issue and label the issue as **in progress**. For more details, check out [**@zulipbot**](https://github.com/zulip/zulipbot).
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Zulip Desktop Client
|
# Zulip Desktop Client
|
||||||
[](https://travis-ci.org/zulip/zulip-desktop)
|
[](https://travis-ci.org/zulip/zulip-electron)
|
||||||
[](https://ci.appveyor.com/project/zulip/zulip-desktop/branch/master)
|
[](https://ci.appveyor.com/project/akashnimare/zulip-electron/branch/master)
|
||||||
[](https://github.com/sindresorhus/xo)
|
[](https://github.com/sindresorhus/xo)
|
||||||
[](https://chat.zulip.org)
|
[](https://chat.zulip.org)
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { app, dialog, shell } from 'electron';
|
const { app, dialog, shell } = require('electron');
|
||||||
import { autoUpdater } from 'electron-updater';
|
const { autoUpdater } = require('electron-updater');
|
||||||
import { linuxUpdateNotification } from './linuxupdater'; // Required only in case of linux
|
const isDev = require('electron-is-dev');
|
||||||
|
|
||||||
import log = require('electron-log');
|
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
|
||||||
import isDev = require('electron-is-dev');
|
|
||||||
import ConfigUtil = require('../renderer/js/utils/config-util');
|
|
||||||
|
|
||||||
export function appUpdater(updateFromMenu = false): void {
|
function appUpdater(updateFromMenu = false) {
|
||||||
// Don't initiate auto-updates in development
|
// Don't initiate auto-updates in development
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'linux' && !process.env.APPIMAGE) {
|
if (process.platform === 'linux' && !process.env.APPIMAGE) {
|
||||||
|
const { linuxUpdateNotification } = require('./linuxupdater');
|
||||||
linuxUpdateNotification();
|
linuxUpdateNotification();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -24,6 +23,8 @@ export function appUpdater(updateFromMenu = false): void {
|
|||||||
const LogsDir = `${app.getPath('userData')}/Logs`;
|
const LogsDir = `${app.getPath('userData')}/Logs`;
|
||||||
|
|
||||||
// Log whats happening
|
// Log whats happening
|
||||||
|
const log = require('electron-log');
|
||||||
|
|
||||||
log.transports.file.file = `${LogsDir}/updates.log`;
|
log.transports.file.file = `${LogsDir}/updates.log`;
|
||||||
log.transports.file.level = 'info';
|
log.transports.file.level = 'info';
|
||||||
autoUpdater.logger = log;
|
autoUpdater.logger = log;
|
||||||
@@ -83,6 +84,7 @@ Current Version: ${app.getVersion()}`
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Ask the user if update is available
|
// Ask the user if update is available
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
autoUpdater.on('update-downloaded', event => {
|
autoUpdater.on('update-downloaded', event => {
|
||||||
// Ask user to update the app
|
// Ask user to update the app
|
||||||
dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
@@ -104,3 +106,7 @@ Current Version: ${app.getVersion()}`
|
|||||||
// Init for updates
|
// Init for updates
|
||||||
autoUpdater.checkForUpdates();
|
autoUpdater.checkForUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
appUpdater
|
||||||
|
};
|
||||||
@@ -1,25 +1,21 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { sentryInit } from '../renderer/js/utils/sentry-util';
|
const path = require('path');
|
||||||
import { appUpdater } from './autoupdater';
|
const fs = require('fs');
|
||||||
import { setAutoLaunch } from './startup';
|
|
||||||
|
const electron = require('electron');
|
||||||
|
const windowStateKeeper = require('electron-window-state');
|
||||||
|
const isDev = require('electron-is-dev');
|
||||||
|
const appMenu = require('./menu');
|
||||||
|
const { appUpdater } = require('./autoupdater');
|
||||||
|
|
||||||
|
const { setAutoLaunch } = require('./startup');
|
||||||
|
|
||||||
import windowStateKeeper = require('electron-window-state');
|
|
||||||
import path = require('path');
|
|
||||||
import fs = require('fs');
|
|
||||||
import isDev = require('electron-is-dev');
|
|
||||||
import electron = require('electron');
|
|
||||||
const { app, ipcMain } = electron;
|
const { app, ipcMain } = electron;
|
||||||
|
|
||||||
import AppMenu = require('./menu');
|
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
|
||||||
import BadgeSettings = require('../renderer/js/pages/preference/badge-settings');
|
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
|
||||||
import ConfigUtil = require('../renderer/js/utils/config-util');
|
const ProxyUtil = require('./../renderer/js/utils/proxy-util.js');
|
||||||
import ProxyUtil = require('../renderer/js/utils/proxy-util');
|
const { sentryInit } = require('./../renderer/js/utils/sentry-util.js');
|
||||||
|
|
||||||
interface PatchedGlobal extends NodeJS.Global {
|
|
||||||
mainWindowState: windowStateKeeper.State;
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalPatched = global as PatchedGlobal;
|
|
||||||
|
|
||||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||||
// in development mode
|
// in development mode
|
||||||
@@ -28,8 +24,8 @@ if (isDev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prevent window being garbage collected
|
// Prevent window being garbage collected
|
||||||
let mainWindow: Electron.BrowserWindow;
|
let mainWindow;
|
||||||
let badgeCount: number;
|
let badgeCount;
|
||||||
|
|
||||||
let isQuitting = false;
|
let isQuitting = false;
|
||||||
|
|
||||||
@@ -53,20 +49,20 @@ if (singleInstanceLock) {
|
|||||||
|
|
||||||
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
|
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
|
||||||
|
|
||||||
const iconPath = (): string => {
|
const iconPath = () => {
|
||||||
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
|
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
|
||||||
};
|
};
|
||||||
|
|
||||||
function createMainWindow(): Electron.BrowserWindow {
|
function createMainWindow() {
|
||||||
// Load the previous state with fallback to defaults
|
// Load the previous state with fallback to defaults
|
||||||
const mainWindowState: windowStateKeeper.State = windowStateKeeper({
|
const mainWindowState = windowStateKeeper({
|
||||||
defaultWidth: 1100,
|
defaultWidth: 1100,
|
||||||
defaultHeight: 720,
|
defaultHeight: 720,
|
||||||
path: `${app.getPath('userData')}/config`
|
path: `${app.getPath('userData')}/config`
|
||||||
});
|
});
|
||||||
|
|
||||||
// Let's keep the window position global so that we can access it in other process
|
// Let's keep the window position global so that we can access it in other process
|
||||||
globalPatched.mainWindowState = mainWindowState;
|
global.mainWindowState = mainWindowState;
|
||||||
|
|
||||||
const win = new electron.BrowserWindow({
|
const win = new electron.BrowserWindow({
|
||||||
// This settings needs to be saved in config
|
// This settings needs to be saved in config
|
||||||
@@ -138,7 +134,7 @@ app.disableHardwareAcceleration();
|
|||||||
app.commandLine.appendSwitch('force-color-profile', 'srgb');
|
app.commandLine.appendSwitch('force-color-profile', 'srgb');
|
||||||
|
|
||||||
// eslint-disable-next-line max-params
|
// eslint-disable-next-line max-params
|
||||||
app.on('certificate-error', (event: Event, _webContents: Electron.WebContents, _url: string, _error: string, _certificate: any, callback) => {
|
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
callback(true);
|
callback(true);
|
||||||
});
|
});
|
||||||
@@ -150,7 +146,7 @@ app.on('activate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('ready', () => {
|
app.on('ready', () => {
|
||||||
AppMenu.setMenu({
|
appMenu.setMenu({
|
||||||
tabs: []
|
tabs: []
|
||||||
});
|
});
|
||||||
mainWindow = createMainWindow();
|
mainWindow = createMainWindow();
|
||||||
@@ -163,10 +159,7 @@ app.on('ready', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize sentry for main process
|
// Initialize sentry for main process
|
||||||
const errorReporting = ConfigUtil.getConfigItem('errorReporting');
|
|
||||||
if (errorReporting) {
|
|
||||||
sentryInit();
|
sentryInit();
|
||||||
}
|
|
||||||
|
|
||||||
const isSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
|
const isSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
|
||||||
|
|
||||||
@@ -238,7 +231,7 @@ app.on('ready', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('clear-app-settings', () => {
|
ipcMain.on('clear-app-settings', () => {
|
||||||
globalPatched.mainWindowState.unmanage();
|
global.mainWindowState.unmanage(mainWindow);
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
app.exit();
|
app.exit();
|
||||||
});
|
});
|
||||||
@@ -255,49 +248,48 @@ app.on('ready', () => {
|
|||||||
BadgeSettings.updateBadge(badgeCount, mainWindow);
|
BadgeSettings.updateBadge(badgeCount, mainWindow);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('toggle-menubar', (_event: Electron.IpcMessageEvent, showMenubar: boolean) => {
|
ipcMain.on('toggle-menubar', (event, showMenubar) => {
|
||||||
mainWindow.setAutoHideMenuBar(showMenubar);
|
mainWindow.setAutoHideMenuBar(showMenubar);
|
||||||
mainWindow.setMenuBarVisibility(!showMenubar);
|
mainWindow.setMenuBarVisibility(!showMenubar);
|
||||||
page.send('toggle-autohide-menubar', showMenubar, true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-badge', (_event: Electron.IpcMessageEvent, messageCount: number) => {
|
ipcMain.on('update-badge', (event, messageCount) => {
|
||||||
badgeCount = messageCount;
|
badgeCount = messageCount;
|
||||||
BadgeSettings.updateBadge(badgeCount, mainWindow);
|
BadgeSettings.updateBadge(badgeCount, mainWindow);
|
||||||
page.send('tray', messageCount);
|
page.send('tray', messageCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-taskbar-icon', (_event: Electron.IpcMessageEvent, data: any, text: string) => {
|
ipcMain.on('update-taskbar-icon', (event, data, text) => {
|
||||||
BadgeSettings.updateTaskbarIcon(data, text, mainWindow);
|
BadgeSettings.updateTaskbarIcon(data, text, mainWindow);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('forward-message', (_event: Electron.IpcMessageEvent, listener: any, ...params: any[]) => {
|
ipcMain.on('forward-message', (event, listener, ...params) => {
|
||||||
page.send(listener, ...params);
|
page.send(listener, ...params);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-menu', (_event: Electron.IpcMessageEvent, props: any) => {
|
ipcMain.on('update-menu', (event, props) => {
|
||||||
AppMenu.setMenu(props);
|
appMenu.setMenu(props);
|
||||||
const activeTab = props.tabs[props.activeTabIndex];
|
const activeTab = props.tabs[props.activeTabIndex];
|
||||||
if (activeTab) {
|
if (activeTab) {
|
||||||
mainWindow.setTitle(`Zulip - ${activeTab.webview.props.name}`);
|
mainWindow.setTitle(`Zulip - ${activeTab.webview.props.name}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('toggleAutoLauncher', (_event: Electron.IpcMessageEvent, AutoLaunchValue: boolean) => {
|
ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => {
|
||||||
setAutoLaunch(AutoLaunchValue);
|
setAutoLaunch(AutoLaunchValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('downloadFile', (_event: Electron.IpcMessageEvent, url: string, downloadPath: string) => {
|
ipcMain.on('downloadFile', (event, url, downloadPath) => {
|
||||||
page.downloadURL(url);
|
page.downloadURL(url);
|
||||||
page.session.once('will-download', (_event: Event, item) => {
|
page.session.once('will-download', (event, item) => {
|
||||||
const filePath = path.join(downloadPath, item.getFilename());
|
const filePath = path.join(downloadPath, item.getFilename());
|
||||||
|
|
||||||
const getTimeStamp = (): any => {
|
const getTimeStamp = () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return date.getTime();
|
return date.getTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatFile = (filePath: string): string => {
|
const formatFile = filePath => {
|
||||||
const fileExtension = path.extname(filePath);
|
const fileExtension = path.extname(filePath);
|
||||||
const baseName = path.basename(filePath, fileExtension);
|
const baseName = path.basename(filePath, fileExtension);
|
||||||
return `${baseName}-${getTimeStamp()}${fileExtension}`;
|
return `${baseName}-${getTimeStamp()}${fileExtension}`;
|
||||||
@@ -311,7 +303,7 @@ app.on('ready', () => {
|
|||||||
|
|
||||||
item.setSavePath(setFilePath);
|
item.setSavePath(setFilePath);
|
||||||
|
|
||||||
item.on('updated', (_event: Event, state) => {
|
item.on('updated', (event, state) => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'interrupted': {
|
case 'interrupted': {
|
||||||
// Can interrupted to due to network error, cancel download then
|
// Can interrupted to due to network error, cancel download then
|
||||||
@@ -331,7 +323,7 @@ app.on('ready', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
item.once('done', (_event: Event, state) => {
|
item.once('done', (event, state) => {
|
||||||
const getFileName = fs.existsSync(filePath) ? formatFile(filePath) : item.getFilename();
|
const getFileName = fs.existsSync(filePath) ? formatFile(filePath) : item.getFilename();
|
||||||
if (state === 'completed') {
|
if (state === 'completed') {
|
||||||
page.send('downloadFileCompleted', item.getSavePath(), getFileName);
|
page.send('downloadFileCompleted', item.getSavePath(), getFileName);
|
||||||
@@ -345,23 +337,13 @@ app.on('ready', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('realm-name-changed', (_event: Electron.IpcMessageEvent, serverURL: string, realmName: string) => {
|
ipcMain.on('realm-name-changed', (event, serverURL, realmName) => {
|
||||||
page.send('update-realm-name', serverURL, realmName);
|
page.send('update-realm-name', serverURL, realmName);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('realm-icon-changed', (_event: Electron.IpcMessageEvent, serverURL: string, iconURL: string) => {
|
ipcMain.on('realm-icon-changed', (event, serverURL, iconURL) => {
|
||||||
page.send('update-realm-icon', serverURL, iconURL);
|
page.send('update-realm-icon', serverURL, iconURL);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Using event.sender.send instead of page.send here to
|
|
||||||
// make sure the value of errorReporting is sent only once on load.
|
|
||||||
ipcMain.on('error-reporting', (event: Electron.IpcMessageEvent) => {
|
|
||||||
event.sender.send('error-reporting-val', errorReporting);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('save-last-tab', (_event: Electron.IpcMessageEvent, index: number) => {
|
|
||||||
ConfigUtil.setConfigItem('lastActiveTab', index);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('before-quit', () => {
|
app.on('before-quit', () => {
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
import { app, Notification } from 'electron';
|
const { app } = require('electron');
|
||||||
|
const { Notification } = require('electron');
|
||||||
|
|
||||||
import request = require('request');
|
const request = require('request');
|
||||||
import semver = require('semver');
|
const semver = require('semver');
|
||||||
import ConfigUtil = require('../renderer/js/utils/config-util');
|
const ConfigUtil = require('../renderer/js/utils/config-util');
|
||||||
import ProxyUtil = require('../renderer/js/utils/proxy-util');
|
const ProxyUtil = require('../renderer/js/utils/proxy-util');
|
||||||
import LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
|
const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
|
||||||
import Logger = require('../renderer/js/utils/logger-util');
|
const Logger = require('../renderer/js/utils/logger-util');
|
||||||
|
|
||||||
const logger = new Logger({
|
const logger = new Logger({
|
||||||
file: 'linux-update-util.log',
|
file: 'linux-update-util.log',
|
||||||
timestamp: true
|
timestamp: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export function linuxUpdateNotification(): void {
|
function linuxUpdateNotification() {
|
||||||
let url = 'https://api.github.com/repos/zulip/zulip-desktop/releases';
|
let url = 'https://api.github.com/repos/zulip/zulip-electron/releases';
|
||||||
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
|
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
|
||||||
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
|
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ export function linuxUpdateNotification(): void {
|
|||||||
ecdhCurve: 'auto'
|
ecdhCurve: 'auto'
|
||||||
};
|
};
|
||||||
|
|
||||||
request(options, (error: any, response: any, body: any) => {
|
request(options, (error, response, body) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error('Linux update error.');
|
logger.error('Linux update error.');
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
@@ -46,3 +47,7 @@ export function linuxUpdateNotification(): void {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
linuxUpdateNotification
|
||||||
|
};
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { app, shell, BrowserWindow, Menu, dialog } from 'electron';
|
const path = require('path');
|
||||||
import { appUpdater } from './autoupdater';
|
|
||||||
|
|
||||||
import AdmZip = require('adm-zip');
|
const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
|
||||||
import fs = require('fs-extra');
|
|
||||||
import path = require('path');
|
const fs = require('fs-extra');
|
||||||
import DNDUtil = require('../renderer/js/utils/dnd-util');
|
const AdmZip = require('adm-zip');
|
||||||
import Logger = require('../renderer/js/utils/logger-util');
|
const { appUpdater } = require('./autoupdater');
|
||||||
import ConfigUtil = require('../renderer/js/utils/config-util');
|
|
||||||
|
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
|
||||||
|
const DNDUtil = require(__dirname + '/../renderer/js/utils/dnd-util.js');
|
||||||
|
const Logger = require(__dirname + '/../renderer/js/utils/logger-util.js');
|
||||||
|
|
||||||
const appName = app.getName();
|
const appName = app.getName();
|
||||||
|
|
||||||
@@ -17,12 +19,11 @@ const logger = new Logger({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class AppMenu {
|
class AppMenu {
|
||||||
getHistorySubmenu(enableMenu: boolean): Electron.MenuItemConstructorOptions[] {
|
getHistorySubmenu() {
|
||||||
return [{
|
return [{
|
||||||
label: 'Back',
|
label: 'Back',
|
||||||
accelerator: process.platform === 'darwin' ? 'Command+Left' : 'Alt+Left',
|
accelerator: process.platform === 'darwin' ? 'Command+Left' : 'Alt+Left',
|
||||||
enabled: enableMenu,
|
click(item, focusedWindow) {
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('back');
|
AppMenu.sendAction('back');
|
||||||
}
|
}
|
||||||
@@ -30,8 +31,7 @@ class AppMenu {
|
|||||||
}, {
|
}, {
|
||||||
label: 'Forward',
|
label: 'Forward',
|
||||||
accelerator: process.platform === 'darwin' ? 'Command+Right' : 'Alt+Right',
|
accelerator: process.platform === 'darwin' ? 'Command+Right' : 'Alt+Right',
|
||||||
enabled: enableMenu,
|
click(item, focusedWindow) {
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('forward');
|
AppMenu.sendAction('forward');
|
||||||
}
|
}
|
||||||
@@ -39,64 +39,58 @@ class AppMenu {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolsSubmenu(): Electron.MenuItemConstructorOptions[] {
|
getToolsSubmenu() {
|
||||||
return [{
|
return [{
|
||||||
label: 'Check for Updates',
|
label: `Check for Updates`,
|
||||||
click() {
|
click() {
|
||||||
AppMenu.checkForUpdate();
|
AppMenu.checkForUpdate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Release Notes',
|
label: `Release Notes`,
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal(`https://github.com/zulip/zulip-desktop/releases/tag/v${app.getVersion()}`);
|
shell.openExternal(`https://github.com/zulip/zulip-electron/releases/tag/v${app.getVersion()}`);
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Factory Reset',
|
label: 'Factory Reset',
|
||||||
accelerator: process.platform === 'darwin' ? 'Command+Shift+D' : 'Ctrl+Shift+D',
|
accelerator: process.platform === 'darwin' ? 'Command+Shift+D' : 'Ctrl+Shift+D',
|
||||||
click() {
|
click() {
|
||||||
AppMenu.resetAppSettings();
|
AppMenu.resetAppSettings();
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Download App Logs',
|
label: 'Download App Logs',
|
||||||
click() {
|
click() {
|
||||||
const zip = new AdmZip();
|
const zip = new AdmZip();
|
||||||
const date = new Date();
|
let date = new Date();
|
||||||
const dateString = date.toLocaleDateString().replace(/\//g, '-');
|
date = date.toLocaleDateString().replace(/\//g, '-');
|
||||||
|
|
||||||
// Create a zip file of all the logs and config data
|
// Create a zip file of all the logs and config data
|
||||||
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/Logs`);
|
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/Logs`);
|
||||||
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/config`);
|
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/config`);
|
||||||
|
|
||||||
// Put the log file in downloads folder
|
// Put the log file in downloads folder
|
||||||
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${dateString}.zip`;
|
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${date}.zip`;
|
||||||
zip.writeZip(logFilePath);
|
zip.writeZip(logFilePath);
|
||||||
|
|
||||||
// Open and select the log file
|
// Open and select the log file
|
||||||
shell.showItemInFolder(logFilePath);
|
shell.showItemInFolder(logFilePath);
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Toggle DevTools for Zulip App',
|
label: 'Toggle DevTools for Zulip App',
|
||||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
focusedWindow.webContents.toggleDevTools();
|
focusedWindow.webContents.toggleDevTools();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: 'Toggle DevTools for Active Tab',
|
label: 'Toggle DevTools for Active Tab',
|
||||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('tab-devtools');
|
AppMenu.sendAction('tab-devtools');
|
||||||
}
|
}
|
||||||
@@ -104,11 +98,11 @@ class AppMenu {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewSubmenu(): Electron.MenuItemConstructorOptions[] {
|
getViewSubmenu() {
|
||||||
return [{
|
return [{
|
||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
accelerator: 'CommandOrControl+R',
|
accelerator: 'CommandOrControl+R',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('reload-current-viewer');
|
AppMenu.sendAction('reload-current-viewer');
|
||||||
}
|
}
|
||||||
@@ -116,7 +110,7 @@ class AppMenu {
|
|||||||
}, {
|
}, {
|
||||||
label: 'Hard Reload',
|
label: 'Hard Reload',
|
||||||
accelerator: 'CommandOrControl+Shift+R',
|
accelerator: 'CommandOrControl+Shift+R',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('hard-reload');
|
AppMenu.sendAction('hard-reload');
|
||||||
}
|
}
|
||||||
@@ -128,7 +122,7 @@ class AppMenu {
|
|||||||
}, {
|
}, {
|
||||||
label: 'Zoom In',
|
label: 'Zoom In',
|
||||||
accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=',
|
accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('zoomIn');
|
AppMenu.sendAction('zoomIn');
|
||||||
}
|
}
|
||||||
@@ -136,7 +130,7 @@ class AppMenu {
|
|||||||
}, {
|
}, {
|
||||||
label: 'Zoom Out',
|
label: 'Zoom Out',
|
||||||
accelerator: 'CommandOrControl+-',
|
accelerator: 'CommandOrControl+-',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('zoomOut');
|
AppMenu.sendAction('zoomOut');
|
||||||
}
|
}
|
||||||
@@ -144,7 +138,7 @@ class AppMenu {
|
|||||||
}, {
|
}, {
|
||||||
label: 'Actual Size',
|
label: 'Actual Size',
|
||||||
accelerator: 'CommandOrControl+0',
|
accelerator: 'CommandOrControl+0',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('zoomActualSize');
|
AppMenu.sendAction('zoomActualSize');
|
||||||
}
|
}
|
||||||
@@ -153,7 +147,7 @@ class AppMenu {
|
|||||||
type: 'separator'
|
type: 'separator'
|
||||||
}, {
|
}, {
|
||||||
label: 'Toggle Tray Icon',
|
label: 'Toggle Tray Icon',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
focusedWindow.webContents.send('toggletray');
|
focusedWindow.webContents.send('toggletray');
|
||||||
}
|
}
|
||||||
@@ -161,31 +155,17 @@ class AppMenu {
|
|||||||
}, {
|
}, {
|
||||||
label: 'Toggle Sidebar',
|
label: 'Toggle Sidebar',
|
||||||
accelerator: 'CommandOrControl+Shift+S',
|
accelerator: 'CommandOrControl+Shift+S',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
const newValue = !ConfigUtil.getConfigItem('showSidebar');
|
const newValue = !ConfigUtil.getConfigItem('showSidebar');
|
||||||
focusedWindow.webContents.send('toggle-sidebar', newValue);
|
focusedWindow.webContents.send('toggle-sidebar', newValue);
|
||||||
ConfigUtil.setConfigItem('showSidebar', newValue);
|
ConfigUtil.setConfigItem('showSidebar', newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
label: 'Auto hide Menu bar',
|
|
||||||
checked: ConfigUtil.getConfigItem('autoHideMenubar', false),
|
|
||||||
visible: process.platform !== 'darwin',
|
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
const newValue = !ConfigUtil.getConfigItem('autoHideMenubar');
|
|
||||||
focusedWindow.setAutoHideMenuBar(newValue);
|
|
||||||
focusedWindow.setMenuBarVisibility(!newValue);
|
|
||||||
focusedWindow.webContents.send('toggle-autohide-menubar', newValue);
|
|
||||||
ConfigUtil.setConfigItem('autoHideMenubar', newValue);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
type: 'checkbox'
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
getHelpSubmenu(): Electron.MenuItemConstructorOptions[] {
|
getHelpSubmenu() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: `${appName + ' Desktop'} v${app.getVersion()}`,
|
label: `${appName + ' Desktop'} v${app.getVersion()}`,
|
||||||
@@ -193,7 +173,7 @@ class AppMenu {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'About Zulip',
|
label: 'About Zulip',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('open-about');
|
AppMenu.sendAction('open-about');
|
||||||
}
|
}
|
||||||
@@ -201,13 +181,10 @@ class AppMenu {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `Help Center`,
|
label: `Help Center`,
|
||||||
click(focusedWindow) {
|
click() {
|
||||||
if (focusedWindow) {
|
shell.openExternal('https://zulipchat.com/help/');
|
||||||
AppMenu.sendAction('open-help');
|
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Report an Issue',
|
label: 'Report an Issue',
|
||||||
click() {
|
click() {
|
||||||
// the goal is to notify the main.html BrowserWindow
|
// the goal is to notify the main.html BrowserWindow
|
||||||
@@ -216,12 +193,11 @@ class AppMenu {
|
|||||||
window.webContents.send('open-feedback-modal');
|
window.webContents.send('open-feedback-modal');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}];
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getWindowSubmenu(tabs: any[], activeTabIndex: number, enableMenu: boolean): Electron.MenuItemConstructorOptions[] {
|
getWindowSubmenu(tabs, activeTabIndex) {
|
||||||
const initialSubmenu: any[] = [{
|
const initialSubmenu = [{
|
||||||
role: 'minimize'
|
role: 'minimize'
|
||||||
}, {
|
}, {
|
||||||
role: 'close'
|
role: 'close'
|
||||||
@@ -232,75 +208,38 @@ class AppMenu {
|
|||||||
initialSubmenu.push({
|
initialSubmenu.push({
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
});
|
});
|
||||||
tabs.forEach(tab => {
|
for (let i = 0; i < tabs.length; i++) {
|
||||||
// Do not add functional tab settings to list of windows in menu bar
|
// Do not add functional tab settings to list of windows in menu bar
|
||||||
if (tab.props.role === 'function' && tab.webview.props.name === 'Settings') {
|
if (tabs[i].props.role === 'function' && tabs[i].webview.props.name === 'Settings') {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialSubmenu.push({
|
initialSubmenu.push({
|
||||||
label: tab.webview.props.name,
|
label: tabs[i].webview.props.name,
|
||||||
accelerator: tab.props.role === 'function' ? '' : `${ShortcutKey} + ${tab.props.index + 1}`,
|
accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`,
|
||||||
checked: tab.props.index === activeTabIndex,
|
checked: tabs[i].props.index === activeTabIndex,
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('switch-server-tab', tab.props.index);
|
AppMenu.sendAction('switch-server-tab', tabs[i].props.index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
type: 'checkbox'
|
type: 'checkbox'
|
||||||
});
|
});
|
||||||
});
|
|
||||||
initialSubmenu.push({
|
|
||||||
type: 'separator'
|
|
||||||
});
|
|
||||||
initialSubmenu.push({
|
|
||||||
label: 'Switch to Next Organization',
|
|
||||||
accelerator: `Ctrl+Tab`,
|
|
||||||
enabled: enableMenu,
|
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
AppMenu.sendAction('switch-server-tab', AppMenu.getNextServer(tabs, activeTabIndex));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
label: 'Switch to Previous Organization',
|
|
||||||
accelerator: `Ctrl+Shift+Tab`,
|
|
||||||
enabled: enableMenu,
|
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
AppMenu.sendAction('switch-server-tab', AppMenu.getPreviousServer(tabs, activeTabIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return initialSubmenu;
|
return initialSubmenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDarwinTpl(props: any): Electron.MenuItemConstructorOptions[] {
|
getDarwinTpl(props) {
|
||||||
const { tabs, activeTabIndex, enableMenu } = props;
|
const { tabs, activeTabIndex, enableMenu } = props;
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
label: `${app.getName()}`,
|
label: `${app.getName()}`,
|
||||||
submenu: [{
|
submenu: [{
|
||||||
label: 'Add Organization',
|
|
||||||
accelerator: 'Cmd+Shift+N',
|
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
AppMenu.sendAction('new-server');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
label: 'Toggle Do Not Disturb',
|
|
||||||
accelerator: 'Cmd+Shift+M',
|
|
||||||
click() {
|
|
||||||
const dndUtil = DNDUtil.toggle();
|
|
||||||
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
label: 'Desktop Settings',
|
label: 'Desktop Settings',
|
||||||
accelerator: 'Cmd+,',
|
accelerator: 'Cmd+,',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('open-settings');
|
AppMenu.sendAction('open-settings');
|
||||||
}
|
}
|
||||||
@@ -309,7 +248,7 @@ class AppMenu {
|
|||||||
label: 'Keyboard Shortcuts',
|
label: 'Keyboard Shortcuts',
|
||||||
accelerator: 'Cmd+Shift+K',
|
accelerator: 'Cmd+Shift+K',
|
||||||
enabled: enableMenu,
|
enabled: enableMenu,
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('shortcut');
|
AppMenu.sendAction('shortcut');
|
||||||
}
|
}
|
||||||
@@ -317,19 +256,17 @@ class AppMenu {
|
|||||||
}, {
|
}, {
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
}, {
|
}, {
|
||||||
label: 'Copy Zulip URL',
|
label: 'Toggle Do Not Disturb',
|
||||||
accelerator: 'Cmd+Shift+C',
|
accelerator: 'Command+Shift+M',
|
||||||
enabled: enableMenu,
|
click() {
|
||||||
click(_item: any, focusedWindow: any) {
|
const dndUtil = DNDUtil.toggle();
|
||||||
if (focusedWindow) {
|
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
|
||||||
AppMenu.sendAction('copy-zulip-url');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Log Out of Organization',
|
label: 'Log Out',
|
||||||
accelerator: 'Cmd+L',
|
accelerator: 'Cmd+L',
|
||||||
enabled: enableMenu,
|
enabled: enableMenu,
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('log-out');
|
AppMenu.sendAction('log-out');
|
||||||
}
|
}
|
||||||
@@ -376,10 +313,10 @@ class AppMenu {
|
|||||||
submenu: this.getViewSubmenu()
|
submenu: this.getViewSubmenu()
|
||||||
}, {
|
}, {
|
||||||
label: 'History',
|
label: 'History',
|
||||||
submenu: this.getHistorySubmenu(enableMenu)
|
submenu: this.getHistorySubmenu()
|
||||||
}, {
|
}, {
|
||||||
label: 'Window',
|
label: 'Window',
|
||||||
submenu: this.getWindowSubmenu(tabs, activeTabIndex, enableMenu)
|
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
|
||||||
}, {
|
}, {
|
||||||
label: 'Tools',
|
label: 'Tools',
|
||||||
submenu: this.getToolsSubmenu()
|
submenu: this.getToolsSubmenu()
|
||||||
@@ -389,17 +326,28 @@ class AppMenu {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
getOtherTpl(props: any): Electron.MenuItemConstructorOptions[] {
|
getOtherTpl(props) {
|
||||||
const { tabs, activeTabIndex, enableMenu } = props;
|
const { tabs, activeTabIndex, enableMenu } = props;
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
label: '&File',
|
label: '&File',
|
||||||
submenu: [{
|
submenu: [{
|
||||||
label: 'Add Organization',
|
label: 'Desktop Settings',
|
||||||
accelerator: 'Ctrl+Shift+N',
|
accelerator: 'Ctrl+,',
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('new-server');
|
AppMenu.sendAction('open-settings');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: 'separator'
|
||||||
|
}, {
|
||||||
|
label: 'Keyboard Shortcuts',
|
||||||
|
accelerator: 'Ctrl+Shift+K',
|
||||||
|
enabled: enableMenu,
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
AppMenu.sendAction('shortcut');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@@ -412,38 +360,10 @@ class AppMenu {
|
|||||||
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
|
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Desktop Settings',
|
label: 'Log Out',
|
||||||
accelerator: 'Ctrl+,',
|
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
AppMenu.sendAction('open-settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
label: 'Keyboard Shortcuts',
|
|
||||||
accelerator: 'Ctrl+Shift+K',
|
|
||||||
enabled: enableMenu,
|
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
AppMenu.sendAction('shortcut');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
type: 'separator'
|
|
||||||
}, {
|
|
||||||
label: 'Copy Zulip URL',
|
|
||||||
accelerator: 'Ctrl+Shift+C',
|
|
||||||
enabled: enableMenu,
|
|
||||||
click(_item: any, focusedWindow: any) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
AppMenu.sendAction('copy-zulip-url');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
label: 'Log Out of Organization',
|
|
||||||
accelerator: 'Ctrl+L',
|
accelerator: 'Ctrl+L',
|
||||||
enabled: enableMenu,
|
enabled: enableMenu,
|
||||||
click(_item: any, focusedWindow: any) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
AppMenu.sendAction('log-out');
|
AppMenu.sendAction('log-out');
|
||||||
}
|
}
|
||||||
@@ -480,10 +400,10 @@ class AppMenu {
|
|||||||
submenu: this.getViewSubmenu()
|
submenu: this.getViewSubmenu()
|
||||||
}, {
|
}, {
|
||||||
label: '&History',
|
label: '&History',
|
||||||
submenu: this.getHistorySubmenu(enableMenu)
|
submenu: this.getHistorySubmenu()
|
||||||
}, {
|
}, {
|
||||||
label: '&Window',
|
label: '&Window',
|
||||||
submenu: this.getWindowSubmenu(tabs, activeTabIndex, enableMenu)
|
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
|
||||||
}, {
|
}, {
|
||||||
label: '&Tools',
|
label: '&Tools',
|
||||||
submenu: this.getToolsSubmenu()
|
submenu: this.getToolsSubmenu()
|
||||||
@@ -494,7 +414,7 @@ class AppMenu {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
static sendAction(action: any, ...params: any[]): void {
|
static sendAction(action, ...params) {
|
||||||
const win = BrowserWindow.getAllWindows()[0];
|
const win = BrowserWindow.getAllWindows()[0];
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
@@ -504,27 +424,10 @@ class AppMenu {
|
|||||||
win.webContents.send(action, ...params);
|
win.webContents.send(action, ...params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkForUpdate(): void {
|
static checkForUpdate() {
|
||||||
appUpdater(true);
|
appUpdater(true);
|
||||||
}
|
}
|
||||||
|
static resetAppSettings() {
|
||||||
static getNextServer(tabs: any[], activeTabIndex: number): number {
|
|
||||||
do {
|
|
||||||
activeTabIndex = (activeTabIndex + 1) % tabs.length;
|
|
||||||
}
|
|
||||||
while (tabs[activeTabIndex].props.role !== 'server');
|
|
||||||
return activeTabIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
static getPreviousServer(tabs: any[], activeTabIndex: number): number {
|
|
||||||
do {
|
|
||||||
activeTabIndex = (activeTabIndex - 1 + tabs.length) % tabs.length;
|
|
||||||
}
|
|
||||||
while (tabs[activeTabIndex].props.role !== 'server');
|
|
||||||
return activeTabIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
static resetAppSettings(): void {
|
|
||||||
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
|
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
|
||||||
|
|
||||||
// We save App's settings/configurations in following files
|
// We save App's settings/configurations in following files
|
||||||
@@ -540,7 +443,7 @@ class AppMenu {
|
|||||||
if (response === 0) {
|
if (response === 0) {
|
||||||
settingFiles.forEach(settingFileName => {
|
settingFiles.forEach(settingFileName => {
|
||||||
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
|
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
|
||||||
fs.access(getSettingFilesPath, (error: any) => {
|
fs.access(getSettingFilesPath, error => {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error('Error while resetting app settings.');
|
logger.error('Error while resetting app settings.');
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
@@ -555,11 +458,11 @@ class AppMenu {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setMenu(props: any): void {
|
setMenu(props) {
|
||||||
const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props);
|
const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props);
|
||||||
const menu = Menu.buildFromTemplate(tpl);
|
const menu = Menu.buildFromTemplate(tpl);
|
||||||
Menu.setApplicationMenu(menu);
|
Menu.setApplicationMenu(menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new AppMenu();
|
module.exports = new AppMenu();
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { app } from 'electron';
|
const { app } = require('electron');
|
||||||
|
const AutoLaunch = require('auto-launch');
|
||||||
|
const isDev = require('electron-is-dev');
|
||||||
|
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
|
||||||
|
|
||||||
import AutoLaunch = require('auto-launch');
|
const setAutoLaunch = AutoLaunchValue => {
|
||||||
import isDev = require('electron-is-dev');
|
|
||||||
import ConfigUtil = require('../renderer/js/utils/config-util');
|
|
||||||
|
|
||||||
export const setAutoLaunch = (AutoLaunchValue: boolean): void => {
|
|
||||||
// Don't run this in development
|
// Don't run this in development
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
return;
|
return;
|
||||||
@@ -29,3 +28,7 @@ export const setAutoLaunch = (AutoLaunchValue: boolean): void => {
|
|||||||
ZulipAutoLauncher.disable();
|
ZulipAutoLauncher.disable();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
setAutoLaunch
|
||||||
|
};
|
||||||
21
app/package-lock.json
generated
21
app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zulip",
|
"name": "zulip",
|
||||||
"version": "3.1.0-beta",
|
"version": "2.5.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -464,11 +464,6 @@
|
|||||||
"dns-packet": "^1.1.0"
|
"dns-packet": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dotenv": {
|
|
||||||
"version": "8.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.0.0.tgz",
|
|
||||||
"integrity": "sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg=="
|
|
||||||
},
|
|
||||||
"duplexer3": {
|
"duplexer3": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
|
||||||
@@ -780,7 +775,7 @@
|
|||||||
"hashids": {
|
"hashids": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/hashids/-/hashids-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/hashids/-/hashids-1.1.4.tgz",
|
||||||
"integrity": "sha512-U/fnTE3edW0AV92ZI/BfEluMZuVcu3MDOopsN7jS+HqDYcarQo8rXQiWlsBlm0uX48/taYSdxRsfzh2HRg5Z6w=="
|
"integrity": "sha1-5P+SrWa2hKO9aqznwX1mYY7l+iE="
|
||||||
},
|
},
|
||||||
"hawk": {
|
"hawk": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
@@ -1099,7 +1094,7 @@
|
|||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
@@ -1128,9 +1123,9 @@
|
|||||||
"integrity": "sha1-ltDNYQ69WNS03pzAxoKM2pnHVI8="
|
"integrity": "sha1-ltDNYQ69WNS03pzAxoKM2pnHVI8="
|
||||||
},
|
},
|
||||||
"node-json-db": {
|
"node-json-db": {
|
||||||
"version": "0.9.2",
|
"version": "0.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-0.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-0.9.1.tgz",
|
||||||
"integrity": "sha512-vd8A6wznm3hi82IKlVKdomZvsEnASYWl0n4iwLkI7ukbIQkXTtFdJG8aZg0BNbEN+Vr/VktDFSqwWx/iSdf31Q==",
|
"integrity": "sha512-4BydUI7a10W8QBdHq/J3UBswU1i8WhCgTS4BZU0MjlUKrSU7cuUti71eojistgqe5hIrb4adj/wvAT5dw63NPg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"mkdirp": "0.5.x"
|
"mkdirp": "0.5.x"
|
||||||
}
|
}
|
||||||
@@ -1502,7 +1497,7 @@
|
|||||||
"spawn-rx": {
|
"spawn-rx": {
|
||||||
"version": "2.0.12",
|
"version": "2.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz",
|
||||||
"integrity": "sha512-gOPXiQQFQ9lTOLuys0iMn3jfxxv9c7zzwhbYLOEbQGvEShHVJ5sSR1oD3Daj88os7jKArDYT7rbOKdvNhe7iEg==",
|
"integrity": "sha1-tihSlEmUJgib7qDDwewy1/xXo3Y=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^2.5.1",
|
"debug": "^2.5.1",
|
||||||
"lodash.assign": "^4.2.0",
|
"lodash.assign": "^4.2.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "zulip",
|
"name": "zulip",
|
||||||
"productName": "Zulip",
|
"productName": "Zulip",
|
||||||
"version": "3.1.0-beta",
|
"version": "2.5.0",
|
||||||
"description": "Zulip Desktop App",
|
"description": "Zulip Desktop App",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"copyright": "Kandra Labs, Inc.",
|
"copyright": "Kandra Labs, Inc.",
|
||||||
@@ -11,10 +11,10 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zulip/zulip-desktop.git"
|
"url": "https://github.com/zulip/zulip-electron.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/zulip/zulip-desktop/issues"
|
"url": "https://github.com/zulip/zulip-electron/issues"
|
||||||
},
|
},
|
||||||
"main": "main/index.js",
|
"main": "main/index.js",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
"@sentry/electron": "0.14.0",
|
"@sentry/electron": "0.14.0",
|
||||||
"adm-zip": "0.4.11",
|
"adm-zip": "0.4.11",
|
||||||
"auto-launch": "5.0.5",
|
"auto-launch": "5.0.5",
|
||||||
"dotenv": "8.0.0",
|
|
||||||
"electron-is-dev": "0.3.0",
|
"electron-is-dev": "0.3.0",
|
||||||
"electron-log": "2.2.14",
|
"electron-log": "2.2.14",
|
||||||
"electron-spellchecker": "1.1.2",
|
"electron-spellchecker": "1.1.2",
|
||||||
@@ -38,7 +37,7 @@
|
|||||||
"electron-window-state": "5.0.3",
|
"electron-window-state": "5.0.3",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"is-online": "7.0.0",
|
"is-online": "7.0.0",
|
||||||
"node-json-db": "0.9.2",
|
"node-json-db": "0.9.1",
|
||||||
"request": "2.85.0",
|
"request": "2.85.0",
|
||||||
"semver": "5.4.1",
|
"semver": "5.4.1",
|
||||||
"wurl": "2.5.0"
|
"wurl": "2.5.0"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="css/about.css">
|
<link rel="stylesheet" href="css/about.css">
|
||||||
<title>Zulip - About</title>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -36,7 +35,7 @@
|
|||||||
url = "https://zulipchat.com";
|
url = "https://zulipchat.com";
|
||||||
break;
|
break;
|
||||||
case 'license':
|
case 'license':
|
||||||
url = "https://github.com/zulip/zulip-desktop/blob/master/LICENSE";
|
url = "https://github.com/zulip/zulip-electron/blob/master/LICENSE";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
shell.openExternal(url);
|
shell.openExternal(url);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
body {
|
body {
|
||||||
background: rgba(250, 250, 250, 1.000);
|
background: #fafafa;
|
||||||
font-family: menu, "Helvetica Neue", sans-serif;
|
font-family: menu, "Helvetica Neue", sans-serif;
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#version {
|
#version {
|
||||||
color: rgba(68, 67, 67, 1.000);
|
color: #444343;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ body {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
color: rgba(68, 68, 68, 1.000);
|
color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maintenance-info p {
|
.maintenance-info p {
|
||||||
@@ -58,7 +58,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.detail a {
|
p.detail a {
|
||||||
color: rgba(53, 95, 76, 1.000);
|
color: #355f4c;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.detail a:hover {
|
p.detail a:hover {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toggle-sidebar {
|
.toggle-sidebar {
|
||||||
background: rgba(34, 44, 49, 1.000);
|
background: #222c31;
|
||||||
width: 54px;
|
width: 54px;
|
||||||
padding: 27px 0 20px 0;
|
padding: 27px 0 20px 0;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -60,8 +60,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#view-controls-container::-webkit-scrollbar-thumb {
|
#view-controls-container::-webkit-scrollbar-thumb {
|
||||||
background-color: rgba(169, 169, 169, 1.000);
|
background-color: darkgrey;
|
||||||
outline: 1px solid rgba(169, 169, 169, 1.000);
|
outline: 1px solid slategrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -119,12 +119,12 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-button i {
|
.action-button i {
|
||||||
color: rgba(108, 133, 146, 1.000);
|
color: #6c8592;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-button:hover i {
|
.action-button:hover i {
|
||||||
color: rgba(152, 169, 179, 1.000);
|
color: #98a9b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-button.disable {
|
.action-button.disable {
|
||||||
@@ -136,18 +136,18 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-button.disable:hover i {
|
.action-button.disable:hover i {
|
||||||
color: rgba(108, 133, 146, 1.000);
|
color: #6c8592;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-button.active {
|
.action-button.active {
|
||||||
/* background-color: rgba(255, 255, 255, 0.25); */
|
/* background-color: rgba(255, 255, 255, 0.25); */
|
||||||
background-color: rgba(239, 239, 239, 1.000);
|
background-color: #efefef;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
padding-right: 14px;
|
padding-right: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-button.active i {
|
.action-button.active i {
|
||||||
color: rgba(28, 38, 43, 1.000);
|
color: #1c262b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:first-child {
|
.tab:first-child {
|
||||||
@@ -177,7 +177,7 @@ body {
|
|||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
line-height: 31px;
|
line-height: 31px;
|
||||||
color: rgba(238, 238, 238, 1.000);
|
color: #eee;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
@@ -188,7 +188,7 @@ body {
|
|||||||
font-family: Verdana;
|
font-family: Verdana;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
border: 2px solid rgba(34, 44, 49, 1.000);
|
border: 2px solid #222c31;
|
||||||
margin-left: 17%;
|
margin-left: 17%;
|
||||||
width: 35px;
|
width: 35px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -216,7 +216,7 @@ body {
|
|||||||
|
|
||||||
.tab.active .server-tab {
|
.tab.active .server-tab {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background-color: rgba(100, 132, 120, 1.000);
|
background-color: #648478;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .server-tab-badge.active {
|
.tab .server-tab-badge.active {
|
||||||
@@ -224,7 +224,7 @@ body {
|
|||||||
min-width: 11px;
|
min-width: 11px;
|
||||||
padding: 0 3px;
|
padding: 0 3px;
|
||||||
height: 17px;
|
height: 17px;
|
||||||
background-color: rgba(244, 67, 54, 1.000);
|
background-color: #f44336;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -232,7 +232,7 @@ body {
|
|||||||
z-index: 15;
|
z-index: 15;
|
||||||
top: 6px;
|
top: 6px;
|
||||||
float: right;
|
float: right;
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 17px;
|
line-height: 17px;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -260,25 +260,13 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab .server-tab-shortcut {
|
.tab .server-tab-shortcut {
|
||||||
color: rgba(100, 132, 120, 1.000);
|
color: #648478;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh {
|
|
||||||
animation: rotate-loader 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rotate-loader {
|
|
||||||
from {
|
|
||||||
transform: rotate(0);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(-360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************
|
/*******************
|
||||||
* Webview Area *
|
* Webview Area *
|
||||||
@@ -295,7 +283,7 @@ body {
|
|||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background: rgba(255, 255, 255, 1.000) url(../img/ic_loading.gif) no-repeat;
|
background: #fff url(../img/ic_loading.gif) no-repeat;
|
||||||
background-size: 60px 60px;
|
background-size: 60px 60px;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -340,34 +328,32 @@ webview.focus {
|
|||||||
|
|
||||||
/* Tooltip styling */
|
/* Tooltip styling */
|
||||||
|
|
||||||
#loading-tooltip,
|
|
||||||
#dnd-tooltip,
|
#dnd-tooltip,
|
||||||
#back-tooltip,
|
#back-tooltip,
|
||||||
#reload-tooltip,
|
#reload-tooltip,
|
||||||
#setting-tooltip {
|
#setting-tooltip {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
background: rgba(34, 44, 49, 1.000);
|
background: #222c31;
|
||||||
margin-left: 48px;
|
margin-left: 48px;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: white;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 55px;
|
width: 55px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading-tooltip::after,
|
#dnd-tooltip:after,
|
||||||
#dnd-tooltip::after,
|
#back-tooltip:after,
|
||||||
#back-tooltip::after,
|
#reload-tooltip:after,
|
||||||
#reload-tooltip::after,
|
#setting-tooltip:after {
|
||||||
#setting-tooltip::after {
|
|
||||||
content: " ";
|
content: " ";
|
||||||
border-top: 8px solid transparent;
|
border-top: 8px solid transparent;
|
||||||
border-bottom: 8px solid transparent;
|
border-bottom: 8px solid transparent;
|
||||||
border-right: 8px solid rgba(34, 44, 49, 1.000);
|
border-right: 8px solid #222c31;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 7px;
|
top: 7px;
|
||||||
right: 68px;
|
right: 68px;
|
||||||
@@ -376,25 +362,25 @@ webview.focus {
|
|||||||
#add-server-tooltip,
|
#add-server-tooltip,
|
||||||
.server-tooltip {
|
.server-tooltip {
|
||||||
font-family: 'arial';
|
font-family: 'arial';
|
||||||
background: rgba(34, 44, 49, 1.000);
|
background: #222c31;
|
||||||
left: 56px;
|
left: 56px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
margin-top: 11px;
|
margin-top: 11px;
|
||||||
z-index: 5000 !important;
|
z-index: 5000 !important;
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: #fff;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#add-server-tooltip::after,
|
#add-server-tooltip:after,
|
||||||
.server-tooltip::after {
|
.server-tooltip:after {
|
||||||
content: " ";
|
content: " ";
|
||||||
border-top: 8px solid transparent;
|
border-top: 8px solid transparent;
|
||||||
border-bottom: 8px solid transparent;
|
border-bottom: 8px solid transparent;
|
||||||
border-right: 8px solid rgba(34, 44, 49, 1.000);
|
border-right: 8px solid #222c31;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: -5px;
|
left: -5px;
|
||||||
@@ -406,14 +392,14 @@ webview.focus {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
background: rgba(34, 44, 49, 1.000);
|
background: #222c31;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: rgba(153, 153, 153, 1.000) 1px 1px;
|
box-shadow: #999 1px 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#collapse-button i {
|
#collapse-button i {
|
||||||
color: rgba(239, 239, 239, 1.000);
|
color: #efefef;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-container {
|
#main-container {
|
||||||
@@ -434,8 +420,8 @@ webview.focus {
|
|||||||
|
|
||||||
.popup .popuptext {
|
.popup .popuptext {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
background-color: rgba(85, 85, 85, 1.000);
|
background-color: #555;
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 9px 0;
|
padding: 9px 0;
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
html,
|
html, body {
|
||||||
body {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgba(51, 51, 51, 1.000);
|
color: #333;
|
||||||
background: rgba(255, 255, 255, 1.000);
|
background: #fff;
|
||||||
user-select:none;
|
user-select:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,8 +27,8 @@ body {
|
|||||||
|
|
||||||
#reconnect {
|
#reconnect {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background: rgba(0, 150, 136, 1.000);
|
background: #009688;
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: #fff;
|
||||||
width: 84px;
|
width: 84px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|||||||
@@ -7,25 +7,25 @@ body {
|
|||||||
font-family: menu, "Helvetica Neue", sans-serif;
|
font-family: menu, "Helvetica Neue", sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
background: rgba(239, 239, 239, 1.000);
|
background: #efefef;
|
||||||
letter-spacing: -.08px;
|
letter-spacing: -.08px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
color: rgba(139, 142, 143, 1.000);
|
color: #8b8e8f;
|
||||||
}
|
}
|
||||||
|
|
||||||
kbd {
|
kbd {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid rgba(204, 204, 204, 1.000);
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-family: Courier New, Courier, monospace;
|
font-family: Courier New, Courier, monospace;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
background-color: rgba(247, 247, 247, 1.000);
|
background-color: #f7f7f7;
|
||||||
color: rgba(51, 51, 51, 1.000);
|
color: #333;
|
||||||
margin: 0 0.1em;
|
margin: 0 0.1em;
|
||||||
padding: 0.3em 0.8em;
|
padding: 0.3em 0.8em;
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 1.000);
|
text-shadow: 0 1px 0 #fff;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ table,
|
|||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
color: rgba(56, 52, 48, 1.000);
|
color: #383430;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
@@ -43,11 +43,11 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table tr:nth-child(even) {
|
table tr:nth-child(even) {
|
||||||
background-color: rgba(250, 250, 250, 1.000);
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tr:nth-child(odd) {
|
table tr:nth-child(odd) {
|
||||||
background-color: rgba(255, 255, 255, 1.000);
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
@@ -103,7 +103,7 @@ td:nth-child(odd) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background: rgba(242, 242, 242, 1.000);
|
background: #f2f2f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav-container {
|
#nav-container {
|
||||||
@@ -112,18 +112,18 @@ td:nth-child(odd) {
|
|||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
padding: 7px 0;
|
padding: 7px 0;
|
||||||
color: rgba(153, 153, 153, 1.000);
|
color: #999;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav.active {
|
.nav.active {
|
||||||
color: rgba(78, 191, 172, 1.000);
|
color: #4ebfac;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav.active::before {
|
.nav.active::before {
|
||||||
background: rgba(70, 78, 90, 1.000);
|
background: #464e5a;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -139,7 +139,7 @@ td:nth-child(odd) {
|
|||||||
|
|
||||||
#settings-header {
|
#settings-header {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
color: rgba(34, 44, 49, 1.000);
|
color: #222c31;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
@@ -159,11 +159,11 @@ td:nth-child(odd) {
|
|||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: rgba(34, 44, 49, 1.000);
|
color: #222c31;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
color: rgba(34, 44, 49, 1.000);
|
color: #222c31;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 4px 0 6px 0;
|
padding: 4px 0 6px 0;
|
||||||
@@ -182,7 +182,7 @@ td:nth-child(odd) {
|
|||||||
.sub-title {
|
.sub-title {
|
||||||
padding: 4px 0 6px 0;
|
padding: 4px 0 6px 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: rgba(97, 97, 97, 1.000);
|
color: #616161;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.server-info-icon {
|
img.server-info-icon {
|
||||||
@@ -249,18 +249,14 @@ img.server-info-icon {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 13px;
|
padding: 13px;
|
||||||
border: rgba(237, 237, 237, 1.000) 2px solid;
|
border: #ededed 2px solid;
|
||||||
outline-width: 0;
|
outline-width: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-input-value:focus {
|
.setting-input-value:focus {
|
||||||
border: rgba(78, 191, 172, 1.000) 2px solid;
|
border: #4EBFAC 2px solid;
|
||||||
}
|
|
||||||
|
|
||||||
.invalid-input-value:focus {
|
|
||||||
border: rgba(239, 83, 80, 1) 2px solid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.manual-proxy-block {
|
.manual-proxy-block {
|
||||||
@@ -270,7 +266,7 @@ img.server-info-icon {
|
|||||||
.actions-container {
|
.actions-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgba(35, 93, 58, 1.000);
|
color: #235d3a;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -291,7 +287,6 @@ img.server-info-icon {
|
|||||||
|
|
||||||
.settings-pane {
|
.settings-pane {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 550px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action:hover {
|
.action:hover {
|
||||||
@@ -303,7 +298,7 @@ img.server-info-icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action.disabled {
|
.action.disabled {
|
||||||
color: rgba(153, 153, 153, 1.000);
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-card {
|
.settings-card {
|
||||||
@@ -311,7 +306,7 @@ img.server-info-icon {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 12px 30px;
|
padding: 12px 30px;
|
||||||
margin: 10px 0 20px 0;
|
margin: 10px 0 20px 0;
|
||||||
background: rgba(255, 255, 255, 1.000);
|
background: #fff;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
@@ -326,29 +321,21 @@ img.server-info-icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.red {
|
.red {
|
||||||
color: rgb(240, 148, 148);
|
color: #ef5350;
|
||||||
background: rgba(255, 255, 255, 1.000);
|
padding: 8px;
|
||||||
|
border: rgba(239, 83, 80, 0.5) solid 1px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: inline-block;
|
border-color: #fff;
|
||||||
border: 2px solid rgb(240, 148, 148);
|
|
||||||
padding: 10px;
|
|
||||||
width: 100px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.red:hover {
|
.red:hover {
|
||||||
background-color: rgb(240, 148, 148);
|
background-color: rgba(230, 52, 49, 0.5);
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.green {
|
.green {
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: #fff;
|
||||||
background: rgba(78, 191, 172, 1.000);
|
background: #4EBFAC;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -363,8 +350,8 @@ img.server-info-icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.green:hover {
|
.green:hover {
|
||||||
background-color: rgba(60, 159, 141, 1.000);
|
background-color: #3c9f8d;
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-150 {
|
.w-150 {
|
||||||
@@ -376,9 +363,9 @@ img.server-info-icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.grey {
|
.grey {
|
||||||
color: rgba(158, 158, 158, 1.000);
|
color: #9E9E9E;
|
||||||
background: rgba(250, 250, 250, 1.000);
|
background: #FAFAFA;
|
||||||
border: 1px solid rgba(158, 158, 158, 1.000);
|
border: 1px solid #9E9E9E;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-row {
|
.setting-row {
|
||||||
@@ -405,7 +392,7 @@ i.open-tab-button {
|
|||||||
|
|
||||||
.selected-css-path,
|
.selected-css-path,
|
||||||
.download-folder-path {
|
.download-folder-path {
|
||||||
background: rgba(238, 238, 238, 1.000);
|
background: #eeeeee;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -457,12 +444,12 @@ input.toggle-round + label {
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
background-color: rgba(221, 221, 221, 1.000);
|
background-color: #dddddd;
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.toggle-round + label::before,
|
input.toggle-round+label:before,
|
||||||
input.toggle-round + label::after {
|
input.toggle-round+label:after {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
@@ -471,8 +458,8 @@ input.toggle-round + label::after {
|
|||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
input.toggle-round + label::before {
|
input.toggle-round+label:before {
|
||||||
background-color: rgba(241, 241, 241, 1.000);
|
background-color: #f1f1f1;
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
@@ -480,22 +467,22 @@ input.toggle-round + label::before {
|
|||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.toggle-round + label::after {
|
input.toggle-round+label:after {
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
background-color: rgba(255, 255, 255, 1.000);
|
background-color: #fff;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.toggle-round:checked + label::before {
|
input.toggle-round:checked+label:before {
|
||||||
background-color: rgba(78, 191, 172, 1.000);
|
background-color: #4EBFAC;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.toggle-round:checked + label::after {
|
input.toggle-round:checked+label:after {
|
||||||
margin-left: 25px;
|
margin-left: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,17 +499,17 @@ input.toggle-round:checked + label::after {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
/* background: rgba(61, 64, 67, 15); */
|
/* background: rgba(61, 64, 67, 15); */
|
||||||
background: linear-gradient(35deg, rgba(0, 59, 82, 1.000), rgba(69, 181, 155, 1.000));
|
background: linear-gradient(35deg, #003b52, #45b59b);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal Content */
|
/* Modal Content */
|
||||||
|
|
||||||
.modal-container {
|
.modal-container {
|
||||||
background-color: rgba(244, 247, 248, 1.000);
|
background-color: #f4f7f8;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 57px;
|
padding: 57px;
|
||||||
border: rgba(218, 225, 227, 1.000) 1px solid;
|
border: #dae1e3 1px solid;
|
||||||
width: 550px;
|
width: 550px;
|
||||||
height: 370px;
|
height: 370px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -536,7 +523,7 @@ input.toggle-round:checked + label::after {
|
|||||||
.divider {
|
.divider {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
color: rgba(125, 135, 138, 1.000);
|
color: #7d878a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider hr {
|
.divider hr {
|
||||||
@@ -567,8 +554,8 @@ input.toggle-round:checked + label::after {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: rgba(255, 255, 255, 1.000);
|
color: #fff;
|
||||||
background: rgba(78, 191, 172, 1.000);
|
background: #4EBFAC;
|
||||||
border-color: none;
|
border-color: none;
|
||||||
border: none;
|
border: none;
|
||||||
width: 98%;
|
width: 98%;
|
||||||
@@ -578,15 +565,15 @@ input.toggle-round:checked + label::after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.server-center button:hover {
|
.server-center button:hover {
|
||||||
background: rgba(50, 149, 136, 1.000);
|
background: #329588;
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-center button:focus {
|
.server-center button:focus {
|
||||||
background: rgba(50, 149, 136, 1.000);
|
background: #329588;
|
||||||
}
|
}
|
||||||
|
|
||||||
.certificates-card {
|
.certificates-card {
|
||||||
width: 80%;
|
width: 80%
|
||||||
}
|
}
|
||||||
|
|
||||||
.certificate-input {
|
.certificate-input {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class BaseComponent {
|
class BaseComponent {
|
||||||
generateNodeFromTemplate(template: string): Element | null {
|
generateNodeFromTemplate(template) {
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
wrapper.innerHTML = template;
|
wrapper.innerHTML = template;
|
||||||
return wrapper.firstElementChild;
|
return wrapper.firstElementChild;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = BaseComponent;
|
module.exports = BaseComponent;
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import Tab = require('./tab');
|
const Tab = require(__dirname + '/../components/tab.js');
|
||||||
|
|
||||||
class FunctionalTab extends Tab {
|
class FunctionalTab extends Tab {
|
||||||
$closeButton: Element;
|
template() {
|
||||||
template(): string {
|
|
||||||
return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}">
|
return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}">
|
||||||
<div class="server-tab-badge close-button">
|
<div class="server-tab-badge close-button">
|
||||||
<i class="material-icons">close</i>
|
<i class="material-icons">close</i>
|
||||||
@@ -15,22 +14,16 @@ class FunctionalTab extends Tab {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Typescript - This type for props should be TabProps
|
init() {
|
||||||
constructor(props: any) {
|
|
||||||
super(props);
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init(): void {
|
|
||||||
this.$el = this.generateNodeFromTemplate(this.template());
|
this.$el = this.generateNodeFromTemplate(this.template());
|
||||||
if (this.props.name !== 'Settings') {
|
if (this.props.name !== 'Settings') {
|
||||||
this.props.$root.append(this.$el);
|
this.props.$root.appendChild(this.$el);
|
||||||
this.$closeButton = this.$el.querySelectorAll('.server-tab-badge')[0];
|
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerListeners(): void {
|
registerListeners() {
|
||||||
super.registerListeners();
|
super.registerListeners();
|
||||||
|
|
||||||
this.$el.addEventListener('mouseover', () => {
|
this.$el.addEventListener('mouseover', () => {
|
||||||
@@ -41,11 +34,11 @@ class FunctionalTab extends Tab {
|
|||||||
this.$closeButton.classList.remove('active');
|
this.$closeButton.classList.remove('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$closeButton.addEventListener('click', (e: Event) => {
|
this.$closeButton.addEventListener('click', e => {
|
||||||
this.props.onDestroy();
|
this.props.onDestroy();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = FunctionalTab;
|
module.exports = FunctionalTab;
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import { ipcRenderer, remote } from 'electron';
|
const { ipcRenderer } = require('electron');
|
||||||
|
const { shell, app } = require('electron').remote;
|
||||||
import LinkUtil = require('../utils/link-util');
|
const LinkUtil = require('../utils/link-util');
|
||||||
import DomainUtil = require('../utils/domain-util');
|
const DomainUtil = require('../utils/domain-util');
|
||||||
import ConfigUtil = require('../utils/config-util');
|
const ConfigUtil = require('../utils/config-util');
|
||||||
|
|
||||||
const { shell, app } = remote;
|
|
||||||
|
|
||||||
const dingSound = new Audio('../resources/sounds/ding.ogg');
|
const dingSound = new Audio('../resources/sounds/ding.ogg');
|
||||||
|
|
||||||
// TODO: TypeScript - Figure out a way to pass correct type here.
|
function handleExternalLink(event) {
|
||||||
function handleExternalLink(this: any, event: any): void {
|
|
||||||
const { url } = event;
|
const { url } = event;
|
||||||
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
|
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
|
||||||
const downloadPath = ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`);
|
const downloadPath = ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`);
|
||||||
@@ -40,7 +37,7 @@ function handleExternalLink(this: any, event: any): void {
|
|||||||
// if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
|
// if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
|
||||||
if (!LinkUtil.isImage(url) && isUploadsURL) {
|
if (!LinkUtil.isImage(url) && isUploadsURL) {
|
||||||
ipcRenderer.send('downloadFile', url, downloadPath);
|
ipcRenderer.send('downloadFile', url, downloadPath);
|
||||||
ipcRenderer.once('downloadFileCompleted', (_event: Event, filePath: string, fileName: string) => {
|
ipcRenderer.once('downloadFileCompleted', (event, filePath, fileName) => {
|
||||||
const downloadNotification = new Notification('Download Complete', {
|
const downloadNotification = new Notification('Download Complete', {
|
||||||
body: shouldShowInFolder ? `Click to show ${fileName} in folder` : `Click to open ${fileName}`,
|
body: shouldShowInFolder ? `Click to show ${fileName} in folder` : `Click to open ${fileName}`,
|
||||||
silent: true // We'll play our own sound - ding.ogg
|
silent: true // We'll play our own sound - ding.ogg
|
||||||
@@ -51,7 +48,7 @@ function handleExternalLink(this: any, event: any): void {
|
|||||||
dingSound.play();
|
dingSound.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadNotification.addEventListener('click', () => {
|
downloadNotification.onclick = () => {
|
||||||
if (shouldShowInFolder) {
|
if (shouldShowInFolder) {
|
||||||
// Reveal file in download folder
|
// Reveal file in download folder
|
||||||
shell.showItemInFolder(filePath);
|
shell.showItemInFolder(filePath);
|
||||||
@@ -59,7 +56,7 @@ function handleExternalLink(this: any, event: any): void {
|
|||||||
// Open file in the default native app
|
// Open file in the default native app
|
||||||
shell.openItem(filePath);
|
shell.openItem(filePath);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
ipcRenderer.removeAllListeners('downloadFileFailed');
|
ipcRenderer.removeAllListeners('downloadFileFailed');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,4 +77,4 @@ function handleExternalLink(this: any, event: any): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = handleExternalLink;
|
module.exports = handleExternalLink;
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
const Tab = require(__dirname + '/../components/tab.js');
|
||||||
|
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||||
|
|
||||||
import Tab = require('./tab');
|
const {ipcRenderer} = require('electron');
|
||||||
import SystemUtil = require('../utils/system-util');
|
|
||||||
|
|
||||||
class ServerTab extends Tab {
|
class ServerTab extends Tab {
|
||||||
$badge: Element;
|
template() {
|
||||||
|
|
||||||
template(): string {
|
|
||||||
return `<div class="tab" data-tab-id="${this.props.tabIndex}">
|
return `<div class="tab" data-tab-id="${this.props.tabIndex}">
|
||||||
<div class="server-tooltip" style="display:none">${this.props.name}</div>
|
<div class="server-tooltip" style="display:none">${this.props.name}</div>
|
||||||
<div class="server-tab-badge"></div>
|
<div class="server-tab-badge"></div>
|
||||||
@@ -19,22 +17,16 @@ class ServerTab extends Tab {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Typescript - This type for props should be TabProps
|
init() {
|
||||||
constructor(props: any) {
|
super.init();
|
||||||
super(props);
|
|
||||||
this.init();
|
this.$badge = this.$el.getElementsByClassName('server-tab-badge')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
updateBadge(count) {
|
||||||
this.$el = this.generateNodeFromTemplate(this.template());
|
|
||||||
this.props.$root.append(this.$el);
|
|
||||||
this.registerListeners();
|
|
||||||
this.$badge = this.$el.querySelectorAll('.server-tab-badge')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBadge(count: number): void {
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
const formattedCount = count > 999 ? '1K+' : count.toString();
|
const formattedCount = count > 999 ? '1K+' : count;
|
||||||
|
|
||||||
this.$badge.innerHTML = formattedCount;
|
this.$badge.innerHTML = formattedCount;
|
||||||
this.$badge.classList.add('active');
|
this.$badge.classList.add('active');
|
||||||
} else {
|
} else {
|
||||||
@@ -42,7 +34,7 @@ class ServerTab extends Tab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateShortcutText(): string {
|
generateShortcutText() {
|
||||||
// Only provide shortcuts for server [0..10]
|
// Only provide shortcuts for server [0..10]
|
||||||
if (this.props.index >= 10) {
|
if (this.props.index >= 10) {
|
||||||
return '';
|
return '';
|
||||||
@@ -65,4 +57,4 @@ class ServerTab extends Tab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = ServerTab;
|
module.exports = ServerTab;
|
||||||
@@ -1,44 +1,48 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import WebView = require('./webview');
|
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||||
import BaseComponent = require('./base');
|
|
||||||
|
|
||||||
// TODO: TypeScript - Type annotate props
|
|
||||||
interface TabProps {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Tab extends BaseComponent {
|
class Tab extends BaseComponent {
|
||||||
props: TabProps;
|
constructor(props) {
|
||||||
webview: WebView;
|
|
||||||
$el: Element;
|
|
||||||
constructor(props: TabProps) {
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.props = props;
|
this.props = props;
|
||||||
this.webview = this.props.webview;
|
this.webview = this.props.webview;
|
||||||
|
|
||||||
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerListeners(): void {
|
init() {
|
||||||
|
this.$el = this.generateNodeFromTemplate(this.template());
|
||||||
|
this.props.$root.appendChild(this.$el);
|
||||||
|
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerListeners() {
|
||||||
this.$el.addEventListener('click', this.props.onClick);
|
this.$el.addEventListener('click', this.props.onClick);
|
||||||
this.$el.addEventListener('mouseover', this.props.onHover);
|
this.$el.addEventListener('mouseover', this.props.onHover);
|
||||||
this.$el.addEventListener('mouseout', this.props.onHoverOut);
|
this.$el.addEventListener('mouseout', this.props.onHoverOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
activate(): void {
|
isLoading() {
|
||||||
|
return this.webview.isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
activate() {
|
||||||
this.$el.classList.add('active');
|
this.$el.classList.add('active');
|
||||||
this.webview.load();
|
this.webview.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate(): void {
|
deactivate() {
|
||||||
this.$el.classList.remove('active');
|
this.$el.classList.remove('active');
|
||||||
this.webview.hide();
|
this.webview.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy() {
|
||||||
this.$el.parentNode.removeChild(this.$el);
|
this.$el.parentNode.removeChild(this.$el);
|
||||||
this.webview.$el.parentNode.removeChild(this.webview.$el);
|
this.webview.$el.parentNode.removeChild(this.webview.$el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = Tab;
|
module.exports = Tab;
|
||||||
@@ -1,39 +1,22 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { remote } from 'electron';
|
|
||||||
|
|
||||||
import path = require('path');
|
const path = require('path');
|
||||||
import fs = require('fs');
|
const fs = require('fs');
|
||||||
import ConfigUtil = require('../utils/config-util');
|
|
||||||
import SystemUtil = require('../utils/system-util');
|
|
||||||
import BaseComponent = require('../components/base');
|
|
||||||
import handleExternalLink = require('../components/handle-external-link');
|
|
||||||
|
|
||||||
const { app, dialog } = remote;
|
const ConfigUtil = require(__dirname + '/../utils/config-util.js');
|
||||||
|
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||||
|
const { app, dialog } = require('electron').remote;
|
||||||
|
|
||||||
|
const BaseComponent = require(__dirname + '/../components/base.js');
|
||||||
|
const handleExternalLink = require(__dirname + '/../components/handle-external-link.js');
|
||||||
|
|
||||||
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
|
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
|
||||||
|
|
||||||
// TODO: TypeScript - Type annotate WebViewProps.
|
|
||||||
interface WebViewProps {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
class WebView extends BaseComponent {
|
class WebView extends BaseComponent {
|
||||||
props: any;
|
constructor(props) {
|
||||||
zoomFactor: number;
|
|
||||||
badgeCount: number;
|
|
||||||
loading: boolean;
|
|
||||||
customCSS: string;
|
|
||||||
$webviewsContainer: DOMTokenList;
|
|
||||||
$el: Electron.WebviewTag;
|
|
||||||
|
|
||||||
// This is required because in main.js we access WebView.method as
|
|
||||||
// webview[method].
|
|
||||||
[key: string]: any;
|
|
||||||
|
|
||||||
constructor(props: WebViewProps) {
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.props = props;
|
this.props = props;
|
||||||
|
|
||||||
this.zoomFactor = 1.0;
|
this.zoomFactor = 1.0;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.badgeCount = 0;
|
this.badgeCount = 0;
|
||||||
@@ -41,7 +24,7 @@ class WebView extends BaseComponent {
|
|||||||
this.$webviewsContainer = document.querySelector('#webviews-container').classList;
|
this.$webviewsContainer = document.querySelector('#webviews-container').classList;
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
return `<webview
|
return `<webview
|
||||||
class="disabled"
|
class="disabled"
|
||||||
data-tab-id="${this.props.tabIndex}"
|
data-tab-id="${this.props.tabIndex}"
|
||||||
@@ -55,14 +38,14 @@ class WebView extends BaseComponent {
|
|||||||
</webview>`;
|
</webview>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.$el = this.generateNodeFromTemplate(this.template()) as Electron.WebviewTag;
|
this.$el = this.generateNodeFromTemplate(this.template());
|
||||||
this.props.$root.append(this.$el);
|
this.props.$root.appendChild(this.$el);
|
||||||
|
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerListeners(): void {
|
registerListeners() {
|
||||||
this.$el.addEventListener('new-window', event => {
|
this.$el.addEventListener('new-window', event => {
|
||||||
handleExternalLink.call(this, event);
|
handleExternalLink.call(this, event);
|
||||||
});
|
});
|
||||||
@@ -111,18 +94,12 @@ class WebView extends BaseComponent {
|
|||||||
this.$el.classList.add('onload');
|
this.$el.classList.add('onload');
|
||||||
}
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.props.switchLoading(false, this.props.url);
|
|
||||||
this.show();
|
this.show();
|
||||||
|
|
||||||
// Refocus text boxes after reload
|
|
||||||
// Remove when upstream issue https://github.com/electron/electron/issues/14474 is fixed
|
|
||||||
this.$el.blur();
|
|
||||||
this.$el.focus();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$el.addEventListener('did-fail-load', event => {
|
this.$el.addEventListener('did-fail-load', event => {
|
||||||
const { errorDescription } = event;
|
const { errorDescription } = event;
|
||||||
const hasConnectivityErr = SystemUtil.connectivityERR.includes(errorDescription);
|
const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0);
|
||||||
if (hasConnectivityErr) {
|
if (hasConnectivityErr) {
|
||||||
console.error('error', errorDescription);
|
console.error('error', errorDescription);
|
||||||
this.props.onNetworkError();
|
this.props.onNetworkError();
|
||||||
@@ -130,10 +107,6 @@ class WebView extends BaseComponent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.$el.addEventListener('did-start-loading', () => {
|
this.$el.addEventListener('did-start-loading', () => {
|
||||||
const isSettingPage = this.props.url.includes('renderer/preference.html');
|
|
||||||
if (!isSettingPage) {
|
|
||||||
this.props.switchLoading(true, this.props.url);
|
|
||||||
}
|
|
||||||
let userAgent = SystemUtil.getUserAgent();
|
let userAgent = SystemUtil.getUserAgent();
|
||||||
if (!userAgent) {
|
if (!userAgent) {
|
||||||
SystemUtil.setUserAgent(this.$el.getUserAgent());
|
SystemUtil.setUserAgent(this.$el.getUserAgent());
|
||||||
@@ -141,18 +114,14 @@ class WebView extends BaseComponent {
|
|||||||
}
|
}
|
||||||
this.$el.setUserAgent(userAgent);
|
this.$el.setUserAgent(userAgent);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$el.addEventListener('did-stop-loading', () => {
|
|
||||||
this.props.switchLoading(false, this.props.url);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBadgeCount(title: string): number {
|
getBadgeCount(title) {
|
||||||
const messageCountInTitle = (/\((\d+)\)/).exec(title);
|
const messageCountInTitle = (/\(([0-9]+)\)/).exec(title);
|
||||||
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
|
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
show(): void {
|
show() {
|
||||||
// Do not show WebView if another tab was selected and this tab should be in background.
|
// Do not show WebView if another tab was selected and this tab should be in background.
|
||||||
if (!this.props.isActive()) {
|
if (!this.props.isActive()) {
|
||||||
return;
|
return;
|
||||||
@@ -193,27 +162,21 @@ class WebView extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
focus(): void {
|
focus() {
|
||||||
// focus Webview and it's contents when Window regain focus.
|
// focus Webview and it's contents when Window regain focus.
|
||||||
const webContents = this.$el.getWebContents();
|
const webContents = this.$el.getWebContents();
|
||||||
// HACK: webContents.isFocused() seems to be true even without the element
|
if (webContents && !webContents.isFocused()) {
|
||||||
// being in focus. So, we check against `document.activeElement`.
|
|
||||||
if (webContents && this.$el !== document.activeElement) {
|
|
||||||
// HACK: Looks like blur needs to be called on the previously focused
|
|
||||||
// element to transfer focus correctly, in Electron v3.0.10
|
|
||||||
// See https://github.com/electron/electron/issues/15718
|
|
||||||
(document.activeElement as HTMLElement).blur();
|
|
||||||
this.$el.focus();
|
this.$el.focus();
|
||||||
webContents.focus();
|
webContents.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hide(): void {
|
hide() {
|
||||||
this.$el.classList.add('disabled');
|
this.$el.classList.add('disabled');
|
||||||
this.$el.classList.remove('active');
|
this.$el.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
load(): void {
|
load() {
|
||||||
if (this.$el) {
|
if (this.$el) {
|
||||||
this.show();
|
this.show();
|
||||||
} else {
|
} else {
|
||||||
@@ -221,41 +184,40 @@ class WebView extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomIn(): void {
|
zoomIn() {
|
||||||
this.zoomFactor += 0.1;
|
this.zoomFactor += 0.1;
|
||||||
this.$el.setZoomFactor(this.zoomFactor);
|
this.$el.setZoomFactor(this.zoomFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut(): void {
|
zoomOut() {
|
||||||
this.zoomFactor -= 0.1;
|
this.zoomFactor -= 0.1;
|
||||||
this.$el.setZoomFactor(this.zoomFactor);
|
this.$el.setZoomFactor(this.zoomFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomActualSize(): void {
|
zoomActualSize() {
|
||||||
this.zoomFactor = 1.0;
|
this.zoomFactor = 1.0;
|
||||||
this.$el.setZoomFactor(this.zoomFactor);
|
this.$el.setZoomFactor(this.zoomFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
logOut(): void {
|
logOut() {
|
||||||
this.$el.executeJavaScript('logout()');
|
this.$el.executeJavaScript('logout()');
|
||||||
}
|
}
|
||||||
|
|
||||||
showShortcut(): void {
|
showShortcut() {
|
||||||
this.$el.executeJavaScript('shortcut()');
|
this.$el.executeJavaScript('shortcut()');
|
||||||
}
|
}
|
||||||
|
|
||||||
openDevTools(): void {
|
openDevTools() {
|
||||||
this.$el.openDevTools();
|
this.$el.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
back(): void {
|
back() {
|
||||||
if (this.$el.canGoBack()) {
|
if (this.$el.canGoBack()) {
|
||||||
this.$el.goBack();
|
this.$el.goBack();
|
||||||
this.focus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canGoBackButton(): void {
|
canGoBackButton() {
|
||||||
const $backButton = document.querySelector('#actions-container #back-action');
|
const $backButton = document.querySelector('#actions-container #back-action');
|
||||||
if (this.$el.canGoBack()) {
|
if (this.$el.canGoBack()) {
|
||||||
$backButton.classList.remove('disable');
|
$backButton.classList.remove('disable');
|
||||||
@@ -264,13 +226,13 @@ class WebView extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
forward(): void {
|
forward() {
|
||||||
if (this.$el.canGoForward()) {
|
if (this.$el.canGoForward()) {
|
||||||
this.$el.goForward();
|
this.$el.goForward();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reload(): void {
|
reload() {
|
||||||
this.hide();
|
this.hide();
|
||||||
// Shows the loading indicator till the webview is reloaded
|
// Shows the loading indicator till the webview is reloaded
|
||||||
this.$webviewsContainer.remove('loaded');
|
this.$webviewsContainer.remove('loaded');
|
||||||
@@ -278,9 +240,9 @@ class WebView extends BaseComponent {
|
|||||||
this.$el.reload();
|
this.$el.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
send(channel: string, ...param: any[]): void {
|
send(...param) {
|
||||||
this.$el.send(channel, ...param);
|
this.$el.send(...param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = WebView;
|
module.exports = WebView;
|
||||||
@@ -1,20 +1,17 @@
|
|||||||
import { ipcRenderer } from 'electron';
|
const events = require('events');
|
||||||
|
const { ipcRenderer } = require('electron');
|
||||||
import events = require('events');
|
|
||||||
|
|
||||||
type ListenerType = ((...args: any[]) => void);
|
|
||||||
|
|
||||||
// we have and will have some non camelcase stuff
|
// we have and will have some non camelcase stuff
|
||||||
// while working with zulip so just turning the rule off
|
// while working with zulip so just turning the rule off
|
||||||
// for the whole file.
|
// for the whole file.
|
||||||
/* eslint-disable @typescript-eslint/camelcase */
|
/* eslint-disable camelcase */
|
||||||
class ElectronBridge extends events {
|
class ElectronBridge extends events {
|
||||||
send_event(eventName: string | symbol, ...args: any[]): void {
|
send_event(...args) {
|
||||||
this.emit(eventName, ...args);
|
this.emit(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
on_event(eventName: string, listener: ListenerType): void {
|
on_event(...args) {
|
||||||
this.on(eventName, listener);
|
this.on(...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +37,4 @@ electron_bridge.on('realm_icon_url', iconURL => {
|
|||||||
// functions zulip side will emit event using ElectronBrigde.send_event
|
// functions zulip side will emit event using ElectronBrigde.send_event
|
||||||
// which is alias of .emit and on this side we can handle the data by adding
|
// which is alias of .emit and on this side we can handle the data by adding
|
||||||
// a listener for the event.
|
// a listener for the event.
|
||||||
export = electron_bridge;
|
module.exports = electron_bridge;
|
||||||
|
|
||||||
/* eslint-enable @typescript-eslint/camelcase */
|
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
import { remote } from 'electron';
|
const { app } = require('electron').remote;
|
||||||
import SendFeedback from '@electron-elements/send-feedback';
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
import path = require('path');
|
const SendFeedback = require('@electron-elements/send-feedback');
|
||||||
import fs = require('fs');
|
|
||||||
|
|
||||||
const { app } = remote;
|
|
||||||
|
|
||||||
interface SendFeedback extends HTMLElement {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
type SendFeedbackType = SendFeedback;
|
|
||||||
|
|
||||||
// make the button color match zulip app's theme
|
// make the button color match zulip app's theme
|
||||||
SendFeedback.customStyles = `
|
SendFeedback.customStyles = `
|
||||||
@@ -31,8 +22,8 @@ button {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
customElements.define('send-feedback', SendFeedback);
|
customElements.define('send-feedback', SendFeedback);
|
||||||
export const sendFeedback: SendFeedbackType = document.querySelector('send-feedback');
|
const sendFeedback = document.querySelector('send-feedback');
|
||||||
export const feedbackHolder = sendFeedback.parentElement;
|
const feedbackHolder = sendFeedback.parentElement;
|
||||||
|
|
||||||
// customize the fields of custom elements
|
// customize the fields of custom elements
|
||||||
sendFeedback.title = 'Report Issue';
|
sendFeedback.title = 'Report Issue';
|
||||||
@@ -47,7 +38,7 @@ sendFeedback.useReporter('emailReporter', {
|
|||||||
email: 'akash@zulipchat.com'
|
email: 'akash@zulipchat.com'
|
||||||
});
|
});
|
||||||
|
|
||||||
feedbackHolder.addEventListener('click', (e: Event) => {
|
feedbackHolder.addEventListener('click', e => {
|
||||||
// only remove the class if the grey out faded
|
// only remove the class if the grey out faded
|
||||||
// part is clicked and not the feedback element itself
|
// part is clicked and not the feedback element itself
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
@@ -64,3 +55,8 @@ sendFeedback.addEventListener('feedback-submitted', () => {
|
|||||||
const dataDir = app.getPath('userData');
|
const dataDir = app.getPath('userData');
|
||||||
const logsDir = path.join(dataDir, '/Logs');
|
const logsDir = path.join(dataDir, '/Logs');
|
||||||
sendFeedback.logs.push(...fs.readdirSync(logsDir).map(file => path.join(logsDir, file)));
|
sendFeedback.logs.push(...fs.readdirSync(logsDir).map(file => path.join(logsDir, file)));
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
feedbackHolder,
|
||||||
|
sendFeedback
|
||||||
|
};
|
||||||
@@ -1,138 +1,61 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer, remote, clipboard, shell } from 'electron';
|
const { ipcRenderer, remote } = require('electron');
|
||||||
import { feedbackHolder } from './feedback';
|
const isDev = require('electron-is-dev');
|
||||||
|
|
||||||
import path = require('path');
|
|
||||||
import escape = require('escape-html');
|
|
||||||
import isDev = require('electron-is-dev');
|
|
||||||
const { session, app, Menu, dialog } = remote;
|
const { session, app, Menu, dialog } = remote;
|
||||||
|
const escape = require('escape-html');
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
require(__dirname + '/js/tray.js');
|
||||||
require('./tray');
|
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
|
||||||
|
const WebView = require(__dirname + '/js/components/webview.js');
|
||||||
|
const ServerTab = require(__dirname + '/js/components/server-tab.js');
|
||||||
|
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
|
||||||
|
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
|
||||||
|
const DNDUtil = require(__dirname + '/js/utils/dnd-util.js');
|
||||||
|
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
|
||||||
|
const Logger = require(__dirname + '/js/utils/logger-util.js');
|
||||||
|
const CommonUtil = require(__dirname + '/js/utils/common-util.js');
|
||||||
|
|
||||||
import DomainUtil = require('./utils/domain-util');
|
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
|
||||||
import WebView = require('./components/webview');
|
|
||||||
import ServerTab = require('./components/server-tab');
|
|
||||||
import FunctionalTab = require('./components/functional-tab');
|
|
||||||
import ConfigUtil = require('./utils/config-util');
|
|
||||||
import DNDUtil = require('./utils/dnd-util');
|
|
||||||
import ReconnectUtil = require('./utils/reconnect-util');
|
|
||||||
import Logger = require('./utils/logger-util');
|
|
||||||
import CommonUtil = require('./utils/common-util');
|
|
||||||
|
|
||||||
const logger = new Logger({
|
const logger = new Logger({
|
||||||
file: 'errors.log',
|
file: 'errors.log',
|
||||||
timestamp: true
|
timestamp: true
|
||||||
});
|
});
|
||||||
|
|
||||||
interface FunctionalTabProps {
|
|
||||||
name: string;
|
|
||||||
materialIcon: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AnyObject {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SettingsOptions {
|
|
||||||
autoHideMenubar: boolean;
|
|
||||||
trayIcon: boolean;
|
|
||||||
useManualProxy: boolean;
|
|
||||||
useSystemProxy: boolean;
|
|
||||||
showSidebar: boolean;
|
|
||||||
badgeOption: boolean;
|
|
||||||
startAtLogin: boolean;
|
|
||||||
startMinimized: boolean;
|
|
||||||
enableSpellchecker: boolean;
|
|
||||||
showNotification: boolean;
|
|
||||||
autoUpdate: boolean;
|
|
||||||
betaUpdate: boolean;
|
|
||||||
errorReporting: boolean;
|
|
||||||
customCSS: boolean;
|
|
||||||
silent: boolean;
|
|
||||||
lastActiveTab: number;
|
|
||||||
dnd: boolean;
|
|
||||||
dndPreviousSettings: {
|
|
||||||
showNotification: boolean;
|
|
||||||
silent: boolean;
|
|
||||||
flashTaskbarOnMessage?: boolean;
|
|
||||||
};
|
|
||||||
downloadsPath: string;
|
|
||||||
showDownloadFolder: boolean;
|
|
||||||
flashTaskbarOnMessage?: boolean;
|
|
||||||
dockBouncing?: boolean;
|
|
||||||
loading?: AnyObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rendererDirectory = path.resolve(__dirname, '..');
|
|
||||||
type ServerOrFunctionalTab = ServerTab | FunctionalTab;
|
|
||||||
|
|
||||||
class ServerManagerView {
|
class ServerManagerView {
|
||||||
$addServerButton: HTMLButtonElement;
|
|
||||||
$tabsContainer: Element;
|
|
||||||
$reloadButton: HTMLButtonElement;
|
|
||||||
$loadingIndicator: HTMLButtonElement;
|
|
||||||
$settingsButton: HTMLButtonElement;
|
|
||||||
$webviewsContainer: Element;
|
|
||||||
$backButton: HTMLButtonElement;
|
|
||||||
$dndButton: HTMLButtonElement;
|
|
||||||
$addServerTooltip: HTMLElement;
|
|
||||||
$reloadTooltip: HTMLElement;
|
|
||||||
$loadingTooltip: HTMLElement;
|
|
||||||
$settingsTooltip: HTMLElement;
|
|
||||||
$serverIconTooltip: HTMLCollectionOf<HTMLElement>;
|
|
||||||
$backTooltip: HTMLElement;
|
|
||||||
$dndTooltip: HTMLElement;
|
|
||||||
$sidebar: Element;
|
|
||||||
$fullscreenPopup: Element;
|
|
||||||
$fullscreenEscapeKey: string;
|
|
||||||
loading: AnyObject;
|
|
||||||
activeTabIndex: number;
|
|
||||||
tabs: ServerOrFunctionalTab[];
|
|
||||||
functionalTabs: AnyObject;
|
|
||||||
tabIndex: number;
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.$addServerButton = document.querySelector('#add-tab');
|
this.$addServerButton = document.getElementById('add-tab');
|
||||||
this.$tabsContainer = document.querySelector('#tabs-container');
|
this.$tabsContainer = document.getElementById('tabs-container');
|
||||||
|
|
||||||
const $actionsContainer = document.querySelector('#actions-container');
|
const $actionsContainer = document.getElementById('actions-container');
|
||||||
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
|
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
|
||||||
this.$loadingIndicator = $actionsContainer.querySelector('#loading-action');
|
|
||||||
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
|
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
|
||||||
this.$webviewsContainer = document.querySelector('#webviews-container');
|
this.$webviewsContainer = document.getElementById('webviews-container');
|
||||||
this.$backButton = $actionsContainer.querySelector('#back-action');
|
this.$backButton = $actionsContainer.querySelector('#back-action');
|
||||||
this.$dndButton = $actionsContainer.querySelector('#dnd-action');
|
this.$dndButton = $actionsContainer.querySelector('#dnd-action');
|
||||||
|
|
||||||
this.$addServerTooltip = document.querySelector('#add-server-tooltip');
|
this.$addServerTooltip = document.getElementById('add-server-tooltip');
|
||||||
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
|
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
|
||||||
this.$loadingTooltip = $actionsContainer.querySelector('#loading-tooltip');
|
|
||||||
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
|
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
|
||||||
|
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip');
|
||||||
// TODO: This should have been querySelector but the problem is that
|
|
||||||
// querySelector doesn't return elements not present in dom whereas somehow
|
|
||||||
// getElementsByClassName does. To fix this we need to call this after this.initTabs
|
|
||||||
// is called in this.init.
|
|
||||||
// eslint-disable-next-line unicorn/prefer-query-selector
|
|
||||||
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip') as HTMLCollectionOf<HTMLElement>;
|
|
||||||
this.$backTooltip = $actionsContainer.querySelector('#back-tooltip');
|
this.$backTooltip = $actionsContainer.querySelector('#back-tooltip');
|
||||||
this.$dndTooltip = $actionsContainer.querySelector('#dnd-tooltip');
|
this.$dndTooltip = $actionsContainer.querySelector('#dnd-tooltip');
|
||||||
|
|
||||||
this.$sidebar = document.querySelector('#sidebar');
|
this.$sidebar = document.getElementById('sidebar');
|
||||||
|
|
||||||
this.$fullscreenPopup = document.querySelector('#fullscreen-popup');
|
this.$fullscreenPopup = document.getElementById('fullscreen-popup');
|
||||||
this.$fullscreenEscapeKey = process.platform === 'darwin' ? '^⌘F' : 'F11';
|
this.$fullscreenEscapeKey = process.platform === 'darwin' ? '^⌘F' : 'F11';
|
||||||
this.$fullscreenPopup.innerHTML = `Press ${this.$fullscreenEscapeKey} to exit full screen`;
|
this.$fullscreenPopup.innerHTML = `Press ${this.$fullscreenEscapeKey} to exit full screen`;
|
||||||
|
|
||||||
this.loading = {};
|
|
||||||
this.activeTabIndex = -1;
|
this.activeTabIndex = -1;
|
||||||
this.tabs = [];
|
this.tabs = [];
|
||||||
this.functionalTabs = {};
|
this.functionalTabs = {};
|
||||||
this.tabIndex = 0;
|
this.tabIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.loadProxy().then(() => {
|
this.loadProxy().then(() => {
|
||||||
this.initSidebar();
|
this.initSidebar();
|
||||||
this.initTabs();
|
this.initTabs();
|
||||||
@@ -142,7 +65,7 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadProxy(): Promise<boolean> {
|
loadProxy() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// To change proxyEnable to useManualProxy in older versions
|
// To change proxyEnable to useManualProxy in older versions
|
||||||
const proxyEnabledOld = ConfigUtil.isConfigItemExists('useProxy');
|
const proxyEnabledOld = ConfigUtil.isConfigItemExists('useProxy');
|
||||||
@@ -174,22 +97,20 @@ class ServerManagerView {
|
|||||||
// Settings are initialized only when user clicks on General/Server/Network section settings
|
// Settings are initialized only when user clicks on General/Server/Network section settings
|
||||||
// In case, user doesn't visit these section, those values set to be null automatically
|
// In case, user doesn't visit these section, those values set to be null automatically
|
||||||
// This will make sure the default settings are correctly set to either true or false
|
// This will make sure the default settings are correctly set to either true or false
|
||||||
initDefaultSettings(): void {
|
initDefaultSettings() {
|
||||||
// Default settings which should be respected
|
// Default settings which should be respected
|
||||||
const settingOptions: SettingsOptions = {
|
const settingOptions = {
|
||||||
autoHideMenubar: false,
|
|
||||||
trayIcon: true,
|
trayIcon: true,
|
||||||
useManualProxy: false,
|
useManualProxy: false,
|
||||||
useSystemProxy: false,
|
useSystemProxy: false,
|
||||||
showSidebar: true,
|
showSidebar: true,
|
||||||
badgeOption: true,
|
badgeOption: true,
|
||||||
startAtLogin: true,
|
startAtLogin: false,
|
||||||
startMinimized: false,
|
startMinimized: false,
|
||||||
enableSpellchecker: true,
|
enableSpellchecker: true,
|
||||||
showNotification: true,
|
showNotification: true,
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
betaUpdate: false,
|
betaUpdate: false,
|
||||||
errorReporting: true,
|
|
||||||
customCSS: false,
|
customCSS: false,
|
||||||
silent: false,
|
silent: false,
|
||||||
lastActiveTab: 0,
|
lastActiveTab: 0,
|
||||||
@@ -220,19 +141,18 @@ class ServerManagerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const i in settingOptions) {
|
for (const i in settingOptions) {
|
||||||
const setting = i as keyof SettingsOptions;
|
|
||||||
if (ConfigUtil.getConfigItem(i) === null) {
|
if (ConfigUtil.getConfigItem(i) === null) {
|
||||||
ConfigUtil.setConfigItem(i, settingOptions[setting]);
|
ConfigUtil.setConfigItem(i, settingOptions[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initSidebar(): void {
|
initSidebar() {
|
||||||
const showSidebar = ConfigUtil.getConfigItem('showSidebar', true);
|
const showSidebar = ConfigUtil.getConfigItem('showSidebar', true);
|
||||||
this.toggleSidebar(showSidebar);
|
this.toggleSidebar(showSidebar);
|
||||||
}
|
}
|
||||||
|
|
||||||
initTabs(): void {
|
initTabs() {
|
||||||
const servers = DomainUtil.getDomains();
|
const servers = DomainUtil.getDomains();
|
||||||
if (servers.length > 0) {
|
if (servers.length > 0) {
|
||||||
for (let i = 0; i < servers.length; i++) {
|
for (let i = 0; i < servers.length; i++) {
|
||||||
@@ -249,7 +169,7 @@ class ServerManagerView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initServer(server: any, index: number): void {
|
initServer(server, index) {
|
||||||
const tabIndex = this.getTabIndex();
|
const tabIndex = this.getTabIndex();
|
||||||
this.tabs.push(new ServerTab({
|
this.tabs.push(new ServerTab({
|
||||||
role: 'server',
|
role: 'server',
|
||||||
@@ -266,36 +186,26 @@ class ServerManagerView {
|
|||||||
index,
|
index,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
url: server.url,
|
url: server.url,
|
||||||
role: 'server',
|
|
||||||
name: CommonUtil.decodeString(server.alias),
|
name: CommonUtil.decodeString(server.alias),
|
||||||
isActive: () => {
|
isActive: () => {
|
||||||
return index === this.activeTabIndex;
|
return index === this.activeTabIndex;
|
||||||
},
|
},
|
||||||
switchLoading: (loading: boolean, url: string) => {
|
|
||||||
if (!loading && this.loading[url]) {
|
|
||||||
this.loading[url] = false;
|
|
||||||
} else if (loading && !this.loading[url]) {
|
|
||||||
this.loading[url] = true;
|
|
||||||
}
|
|
||||||
this.showLoading(this.loading[this.tabs[this.activeTabIndex].webview.props.url]);
|
|
||||||
},
|
|
||||||
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
||||||
onTitleChange: this.updateBadge.bind(this),
|
onTitleChange: this.updateBadge.bind(this),
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
preload: true
|
preload: true
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
this.loading[server.url] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initActions(): void {
|
initActions() {
|
||||||
this.initDNDButton();
|
this.initDNDButton();
|
||||||
this.initServerActions();
|
this.initServerActions();
|
||||||
this.initLeftSidebarEvents();
|
this.initLeftSidebarEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
initServerActions(): void {
|
initServerActions() {
|
||||||
const $serverImgs: NodeListOf<HTMLImageElement> = document.querySelectorAll('.server-icons');
|
const $serverImgs = document.querySelectorAll('.server-icons');
|
||||||
$serverImgs.forEach(($serverImg, index) => {
|
$serverImgs.forEach(($serverImg, index) => {
|
||||||
this.addContextMenu($serverImg, index);
|
this.addContextMenu($serverImg, index);
|
||||||
if ($serverImg.src.includes('img/icon.png')) {
|
if ($serverImg.src.includes('img/icon.png')) {
|
||||||
@@ -307,7 +217,7 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initLeftSidebarEvents(): void {
|
initLeftSidebarEvents() {
|
||||||
this.$dndButton.addEventListener('click', () => {
|
this.$dndButton.addEventListener('click', () => {
|
||||||
const dndUtil = DNDUtil.toggle();
|
const dndUtil = DNDUtil.toggle();
|
||||||
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
|
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
|
||||||
@@ -326,29 +236,24 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip, true);
|
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip, true);
|
||||||
this.sidebarHoverEvent(this.$loadingIndicator, this.$loadingTooltip);
|
|
||||||
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
|
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
|
||||||
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
|
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
|
||||||
this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
|
this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
|
||||||
this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
|
this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
initDNDButton(): void {
|
initDNDButton() {
|
||||||
const dnd = ConfigUtil.getConfigItem('dnd', false);
|
const dnd = ConfigUtil.getConfigItem('dnd', false);
|
||||||
this.toggleDNDButton(dnd);
|
this.toggleDNDButton(dnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTabIndex(): number {
|
getTabIndex() {
|
||||||
const currentIndex = this.tabIndex;
|
const currentIndex = this.tabIndex;
|
||||||
this.tabIndex++;
|
this.tabIndex++;
|
||||||
return currentIndex;
|
return currentIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentActiveServer(): string {
|
displayInitialCharLogo($img, index) {
|
||||||
return this.tabs[this.activeTabIndex].webview.props.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
displayInitialCharLogo($img: HTMLImageElement, index: number): void {
|
|
||||||
/*
|
/*
|
||||||
index parameter needed because webview[data-tab-id] can increment
|
index parameter needed because webview[data-tab-id] can increment
|
||||||
beyond size of sidebar org array and throw error
|
beyond size of sidebar org array and throw error
|
||||||
@@ -371,12 +276,12 @@ class ServerManagerView {
|
|||||||
$altIcon.classList.add('alt-icon');
|
$altIcon.classList.add('alt-icon');
|
||||||
|
|
||||||
$parent.removeChild($img);
|
$parent.removeChild($img);
|
||||||
$parent.append($altIcon);
|
$parent.appendChild($altIcon);
|
||||||
|
|
||||||
this.addContextMenu($altIcon as HTMLImageElement, index);
|
this.addContextMenu($altIcon, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebarHoverEvent(SidebarButton: HTMLButtonElement, SidebarTooltip: HTMLElement, addServer = false): void {
|
sidebarHoverEvent(SidebarButton, SidebarTooltip, addServer = false) {
|
||||||
SidebarButton.addEventListener('mouseover', () => {
|
SidebarButton.addEventListener('mouseover', () => {
|
||||||
SidebarTooltip.removeAttribute('style');
|
SidebarTooltip.removeAttribute('style');
|
||||||
// To handle position of add server tooltip due to scrolling of list of organizations
|
// To handle position of add server tooltip due to scrolling of list of organizations
|
||||||
@@ -393,7 +298,7 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onHover(index: number): void {
|
onHover(index) {
|
||||||
// this.$serverIconTooltip[index].innerHTML already has realm name, so we are just
|
// this.$serverIconTooltip[index].innerHTML already has realm name, so we are just
|
||||||
// removing the style.
|
// removing the style.
|
||||||
this.$serverIconTooltip[index].removeAttribute('style');
|
this.$serverIconTooltip[index].removeAttribute('style');
|
||||||
@@ -404,11 +309,11 @@ class ServerManagerView {
|
|||||||
this.$serverIconTooltip[index].style.top = top + 'px';
|
this.$serverIconTooltip[index].style.top = top + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
onHoverOut(index: number): void {
|
onHoverOut(index) {
|
||||||
this.$serverIconTooltip[index].style.display = 'none';
|
this.$serverIconTooltip[index].style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
openFunctionalTab(tabProps: FunctionalTabProps): void {
|
openFunctionalTab(tabProps) {
|
||||||
if (this.functionalTabs[tabProps.name] !== undefined) {
|
if (this.functionalTabs[tabProps.name] !== undefined) {
|
||||||
this.activateTab(this.functionalTabs[tabProps.name]);
|
this.activateTab(this.functionalTabs[tabProps.name]);
|
||||||
return;
|
return;
|
||||||
@@ -432,19 +337,10 @@ class ServerManagerView {
|
|||||||
index: this.functionalTabs[tabProps.name],
|
index: this.functionalTabs[tabProps.name],
|
||||||
tabIndex,
|
tabIndex,
|
||||||
url: tabProps.url,
|
url: tabProps.url,
|
||||||
role: 'function',
|
|
||||||
name: tabProps.name,
|
name: tabProps.name,
|
||||||
isActive: () => {
|
isActive: () => {
|
||||||
return this.functionalTabs[tabProps.name] === this.activeTabIndex;
|
return this.functionalTabs[tabProps.name] === this.activeTabIndex;
|
||||||
},
|
},
|
||||||
switchLoading: (loading: AnyObject, url: string) => {
|
|
||||||
if (!loading && this.loading[url]) {
|
|
||||||
this.loading[url] = false;
|
|
||||||
} else if (loading && !this.loading[url]) {
|
|
||||||
this.loading[url] = true;
|
|
||||||
}
|
|
||||||
this.showLoading(this.loading[this.tabs[this.activeTabIndex].webview.props.url]);
|
|
||||||
},
|
|
||||||
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
onNetworkError: this.openNetworkTroubleshooting.bind(this),
|
||||||
onTitleChange: this.updateBadge.bind(this),
|
onTitleChange: this.updateBadge.bind(this),
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
@@ -459,46 +355,46 @@ class ServerManagerView {
|
|||||||
this.activateTab(this.functionalTabs[tabProps.name]);
|
this.activateTab(this.functionalTabs[tabProps.name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
openSettings(nav = 'General'): void {
|
openSettings(nav = 'General') {
|
||||||
this.openFunctionalTab({
|
this.openFunctionalTab({
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
materialIcon: 'settings',
|
materialIcon: 'settings',
|
||||||
url: `file://${rendererDirectory}/preference.html#${nav}`
|
url: `file://${__dirname}/preference.html#${nav}`
|
||||||
});
|
});
|
||||||
this.$settingsButton.classList.add('active');
|
this.$settingsButton.classList.add('active');
|
||||||
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
|
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
|
||||||
}
|
}
|
||||||
|
|
||||||
openAbout(): void {
|
openAbout() {
|
||||||
this.openFunctionalTab({
|
this.openFunctionalTab({
|
||||||
name: 'About',
|
name: 'About',
|
||||||
materialIcon: 'sentiment_very_satisfied',
|
materialIcon: 'sentiment_very_satisfied',
|
||||||
url: `file://${rendererDirectory}/about.html`
|
url: `file://${__dirname}/about.html`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openNetworkTroubleshooting(): void {
|
openNetworkTroubleshooting() {
|
||||||
this.openFunctionalTab({
|
this.openFunctionalTab({
|
||||||
name: 'Network Troubleshooting',
|
name: 'Network Troubleshooting',
|
||||||
materialIcon: 'network_check',
|
materialIcon: 'network_check',
|
||||||
url: `file://${rendererDirectory}/network.html`
|
url: `file://${__dirname}/network.html`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
activateLastTab(index: number): void {
|
activateLastTab(index) {
|
||||||
// Open all the tabs in background, also activate the tab based on the index
|
// Open all the tabs in background, also activate the tab based on the index
|
||||||
this.activateTab(index);
|
this.activateTab(index);
|
||||||
// Save last active tab via main process to avoid JSON DB errors
|
// Save last active tab
|
||||||
ipcRenderer.send('save-last-tab', index);
|
ConfigUtil.setConfigItem('lastActiveTab', index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns this.tabs in an way that does
|
// returns this.tabs in an way that does
|
||||||
// not crash app when this.tabs is passed into
|
// not crash app when this.tabs is passed into
|
||||||
// ipcRenderer. Something about webview, and props.webview
|
// ipcRenderer. Something about webview, and props.webview
|
||||||
// properties in ServerTab causes the app to crash.
|
// properties in ServerTab causes the app to crash.
|
||||||
get tabsForIpc(): ServerOrFunctionalTab[] {
|
get tabsForIpc() {
|
||||||
const tabs: ServerOrFunctionalTab[] = [];
|
const tabs = [];
|
||||||
this.tabs.forEach((tab: ServerOrFunctionalTab) => {
|
this.tabs.forEach(tab => {
|
||||||
const proto = Object.create(Object.getPrototypeOf(tab));
|
const proto = Object.create(Object.getPrototypeOf(tab));
|
||||||
const tabClone = Object.assign(proto, tab);
|
const tabClone = Object.assign(proto, tab);
|
||||||
|
|
||||||
@@ -511,7 +407,7 @@ class ServerManagerView {
|
|||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
activateTab(index: number, hideOldTab = true): void {
|
activateTab(index, hideOldTab = true) {
|
||||||
if (!this.tabs[index]) {
|
if (!this.tabs[index]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -536,8 +432,6 @@ class ServerManagerView {
|
|||||||
this.activeTabIndex = index;
|
this.activeTabIndex = index;
|
||||||
this.tabs[index].activate();
|
this.tabs[index].activate();
|
||||||
|
|
||||||
this.showLoading(this.loading[this.tabs[this.activeTabIndex].webview.props.url]);
|
|
||||||
|
|
||||||
ipcRenderer.send('update-menu', {
|
ipcRenderer.send('update-menu', {
|
||||||
// JSON stringify this.tabs to avoid a crash
|
// JSON stringify this.tabs to avoid a crash
|
||||||
// util.inspect is being used to handle circular references
|
// util.inspect is being used to handle circular references
|
||||||
@@ -548,17 +442,7 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showLoading(loading: boolean): void {
|
destroyTab(name, index) {
|
||||||
if (!loading) {
|
|
||||||
this.$reloadButton.removeAttribute('style');
|
|
||||||
this.$loadingIndicator.style.display = 'none';
|
|
||||||
} else if (loading) {
|
|
||||||
this.$reloadButton.style.display = 'none';
|
|
||||||
this.$loadingIndicator.removeAttribute('style');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyTab(name: string, index: number): void {
|
|
||||||
if (this.tabs[index].webview.loading) {
|
if (this.tabs[index].webview.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -574,7 +458,7 @@ class ServerManagerView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyView(): void {
|
destroyView() {
|
||||||
// Show loading indicator
|
// Show loading indicator
|
||||||
this.$webviewsContainer.classList.remove('loaded');
|
this.$webviewsContainer.classList.remove('loaded');
|
||||||
|
|
||||||
@@ -588,7 +472,7 @@ class ServerManagerView {
|
|||||||
this.$webviewsContainer.innerHTML = '';
|
this.$webviewsContainer.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadView(): void {
|
reloadView() {
|
||||||
// Save and remember the index of last active tab so that we can use it later
|
// Save and remember the index of last active tab so that we can use it later
|
||||||
const lastActiveTab = this.tabs[this.activeTabIndex].props.index;
|
const lastActiveTab = this.tabs[this.activeTabIndex].props.index;
|
||||||
ConfigUtil.setConfigItem('lastActiveTab', lastActiveTab);
|
ConfigUtil.setConfigItem('lastActiveTab', lastActiveTab);
|
||||||
@@ -601,33 +485,24 @@ class ServerManagerView {
|
|||||||
|
|
||||||
// This will trigger when pressed CTRL/CMD + R [WIP]
|
// This will trigger when pressed CTRL/CMD + R [WIP]
|
||||||
// It won't reload the current view properly when you add/delete a server.
|
// It won't reload the current view properly when you add/delete a server.
|
||||||
reloadCurrentView(): void {
|
reloadCurrentView() {
|
||||||
this.$reloadButton.click();
|
this.$reloadButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBadge(): void {
|
updateBadge() {
|
||||||
let messageCountAll = 0;
|
let messageCountAll = 0;
|
||||||
for (const tab of this.tabs) {
|
for (let i = 0; i < this.tabs.length; i++) {
|
||||||
if (tab && tab instanceof ServerTab && tab.updateBadge) {
|
if (this.tabs[i] && this.tabs[i].updateBadge) {
|
||||||
const count = tab.webview.badgeCount;
|
const count = this.tabs[i].webview.badgeCount;
|
||||||
messageCountAll += count;
|
messageCountAll += count;
|
||||||
tab.updateBadge(count);
|
this.tabs[i].updateBadge(count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.send('update-badge', messageCountAll);
|
ipcRenderer.send('update-badge', messageCountAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGeneralSettings(setting: string, value: any): void {
|
toggleSidebar(show) {
|
||||||
const selector = 'webview:not([class*=disabled])';
|
|
||||||
const webview: Electron.WebviewTag = document.querySelector(selector);
|
|
||||||
if (webview) {
|
|
||||||
const webContents = webview.getWebContents();
|
|
||||||
webContents.send(setting, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSidebar(show: boolean): void {
|
|
||||||
if (show) {
|
if (show) {
|
||||||
this.$sidebar.classList.remove('sidebar-hide');
|
this.$sidebar.classList.remove('sidebar-hide');
|
||||||
} else {
|
} else {
|
||||||
@@ -636,12 +511,12 @@ class ServerManagerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toggles the dnd button icon.
|
// Toggles the dnd button icon.
|
||||||
toggleDNDButton(alert: boolean): void {
|
toggleDNDButton(alert) {
|
||||||
this.$dndTooltip.textContent = (alert ? 'Disable' : 'Enable') + ' Do Not Disturb';
|
this.$dndTooltip.textContent = (alert ? 'Turn Off' : 'Enable') + ' Do Not Disturb';
|
||||||
this.$dndButton.querySelector('i').textContent = alert ? 'notifications_off' : 'notifications';
|
this.$dndButton.querySelector('i').textContent = alert ? 'notifications_off' : 'notifications';
|
||||||
}
|
}
|
||||||
|
|
||||||
addContextMenu($serverImg: HTMLImageElement, index: number): void {
|
addContextMenu($serverImg, index) {
|
||||||
$serverImg.addEventListener('contextmenu', e => {
|
$serverImg.addEventListener('contextmenu', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const template = [
|
const template = [
|
||||||
@@ -660,12 +535,6 @@ class ServerManagerView {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Copy Zulip URL',
|
|
||||||
click: () => {
|
|
||||||
clipboard.writeText(DomainUtil.getDomain(index).url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const contextMenu = Menu.buildFromTemplate(template);
|
const contextMenu = Menu.buildFromTemplate(template);
|
||||||
@@ -673,8 +542,8 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerIpcs(): void {
|
registerIpcs() {
|
||||||
const webviewListeners: AnyObject = {
|
const webviewListeners = {
|
||||||
'webview-reload': 'reload',
|
'webview-reload': 'reload',
|
||||||
back: 'back',
|
back: 'back',
|
||||||
focus: 'focus',
|
focus: 'focus',
|
||||||
@@ -691,23 +560,17 @@ class ServerManagerView {
|
|||||||
ipcRenderer.on(key, () => {
|
ipcRenderer.on(key, () => {
|
||||||
const activeWebview = this.tabs[this.activeTabIndex].webview;
|
const activeWebview = this.tabs[this.activeTabIndex].webview;
|
||||||
if (activeWebview) {
|
if (activeWebview) {
|
||||||
activeWebview[webviewListeners[key] as string]();
|
activeWebview[webviewListeners[key]]();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.on('open-settings', (event: Event, settingNav: string) => {
|
ipcRenderer.on('open-settings', (event, settingNav) => {
|
||||||
this.openSettings(settingNav);
|
this.openSettings(settingNav);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('open-about', this.openAbout.bind(this));
|
ipcRenderer.on('open-about', this.openAbout.bind(this));
|
||||||
|
|
||||||
ipcRenderer.on('open-help', () => {
|
|
||||||
// Open help page of current active server
|
|
||||||
const helpPage = this.getCurrentActiveServer() + '/help';
|
|
||||||
shell.openExternal(helpPage);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('reload-viewer', this.reloadView.bind(this, this.tabs[this.activeTabIndex].props.index));
|
ipcRenderer.on('reload-viewer', this.reloadView.bind(this, this.tabs[this.activeTabIndex].props.index));
|
||||||
|
|
||||||
ipcRenderer.on('reload-current-viewer', this.reloadCurrentView.bind(this));
|
ipcRenderer.on('reload-current-viewer', this.reloadCurrentView.bind(this));
|
||||||
@@ -720,7 +583,7 @@ class ServerManagerView {
|
|||||||
ipcRenderer.send('clear-app-settings');
|
ipcRenderer.send('clear-app-settings');
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('switch-server-tab', (event: Event, index: number) => {
|
ipcRenderer.on('switch-server-tab', (event, index) => {
|
||||||
this.activateLastTab(index);
|
this.activateLastTab(index);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -728,7 +591,7 @@ class ServerManagerView {
|
|||||||
this.openSettings('AddServer');
|
this.openSettings('AddServer');
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('reload-proxy', (event: Event, showAlert: boolean) => {
|
ipcRenderer.on('reload-proxy', (event, showAlert) => {
|
||||||
this.loadProxy().then(() => {
|
this.loadProxy().then(() => {
|
||||||
if (showAlert) {
|
if (showAlert) {
|
||||||
alert('Proxy settings saved!');
|
alert('Proxy settings saved!');
|
||||||
@@ -737,16 +600,19 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-sidebar', (event: Event, show: boolean) => {
|
ipcRenderer.on('toggle-sidebar', (event, show) => {
|
||||||
// Toggle the left sidebar
|
// Toggle the left sidebar
|
||||||
this.toggleSidebar(show);
|
this.toggleSidebar(show);
|
||||||
|
|
||||||
// Toggle sidebar switch in the general settings
|
// Toggle sidebar switch in the general settings
|
||||||
this.updateGeneralSettings('toggle-sidebar-setting', show);
|
const selector = 'webview:not([class*=disabled])';
|
||||||
|
const webview = document.querySelector(selector);
|
||||||
|
const webContents = webview.getWebContents();
|
||||||
|
webContents.send('toggle-sidebar-setting', show);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-silent', (event: Event, state: boolean) => {
|
ipcRenderer.on('toggle-silent', (event, state) => {
|
||||||
const webviews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webview');
|
const webviews = document.querySelectorAll('webview');
|
||||||
webviews.forEach(webview => {
|
webviews.forEach(webview => {
|
||||||
try {
|
try {
|
||||||
webview.setAudioMuted(state);
|
webview.setAudioMuted(state);
|
||||||
@@ -759,29 +625,17 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-autohide-menubar', (event: Event, autoHideMenubar: boolean, updateMenu: boolean) => {
|
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
|
||||||
if (updateMenu) {
|
|
||||||
ipcRenderer.send('update-menu', {
|
|
||||||
tabs: this.tabsForIpc,
|
|
||||||
activeTabIndex: this.activeTabIndex
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.updateGeneralSettings('toggle-menubar-setting', autoHideMenubar);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('toggle-dnd', (event: Event, state: boolean, newSettings: SettingsOptions) => {
|
|
||||||
this.toggleDNDButton(state);
|
this.toggleDNDButton(state);
|
||||||
ipcRenderer.send('forward-message', 'toggle-silent', newSettings.silent);
|
ipcRenderer.send('forward-message', 'toggle-silent', newSettings.silent);
|
||||||
const selector = 'webview:not([class*=disabled])';
|
const selector = 'webview:not([class*=disabled])';
|
||||||
const webview: Electron.WebviewTag = document.querySelector(selector);
|
const webview = document.querySelector(selector);
|
||||||
const webContents = webview.getWebContents();
|
const webContents = webview.getWebContents();
|
||||||
webContents.send('toggle-dnd', state, newSettings);
|
webContents.send('toggle-dnd', state, newSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('update-realm-name', (event: Event, serverURL: string, realmName: string) => {
|
ipcRenderer.on('update-realm-name', (event, serverURL, realmName) => {
|
||||||
// TODO: TypeScript - Type annotate getDomains() or this domain paramter.
|
DomainUtil.getDomains().forEach((domain, index) => {
|
||||||
DomainUtil.getDomains().forEach((domain: any, index: number) => {
|
|
||||||
if (domain.url.includes(serverURL)) {
|
if (domain.url.includes(serverURL)) {
|
||||||
const serverTooltipSelector = `.tab .server-tooltip`;
|
const serverTooltipSelector = `.tab .server-tooltip`;
|
||||||
const serverTooltips = document.querySelectorAll(serverTooltipSelector);
|
const serverTooltips = document.querySelectorAll(serverTooltipSelector);
|
||||||
@@ -801,13 +655,12 @@ class ServerManagerView {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('update-realm-icon', (event: Event, serverURL: string, iconURL: string) => {
|
ipcRenderer.on('update-realm-icon', (event, serverURL, iconURL) => {
|
||||||
// TODO: TypeScript - Type annotate getDomains() or this domain paramter.
|
DomainUtil.getDomains().forEach((domain, index) => {
|
||||||
DomainUtil.getDomains().forEach((domain: any, index: number) => {
|
|
||||||
if (domain.url.includes(serverURL)) {
|
if (domain.url.includes(serverURL)) {
|
||||||
DomainUtil.saveServerIcon(iconURL).then((localIconUrl: string) => {
|
DomainUtil.saveServerIcon(iconURL).then(localIconUrl => {
|
||||||
const serverImgsSelector = `.tab .server-icons`;
|
const serverImgsSelector = `.tab .server-icons`;
|
||||||
const serverImgs: NodeListOf<HTMLImageElement> = document.querySelectorAll(serverImgsSelector);
|
const serverImgs = document.querySelectorAll(serverImgsSelector);
|
||||||
serverImgs[index].src = localIconUrl;
|
serverImgs[index].src = localIconUrl;
|
||||||
|
|
||||||
domain.icon = localIconUrl;
|
domain.icon = localIconUrl;
|
||||||
@@ -827,21 +680,21 @@ class ServerManagerView {
|
|||||||
this.$fullscreenPopup.classList.remove('show');
|
this.$fullscreenPopup.classList.remove('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('focus-webview-with-id', (event: Event, webviewId: number) => {
|
ipcRenderer.on('focus-webview-with-id', (event, webviewId) => {
|
||||||
const webviews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webview');
|
const webviews = document.querySelectorAll('webview');
|
||||||
webviews.forEach(webview => {
|
webviews.forEach(webview => {
|
||||||
const currentId = webview.getWebContents().id;
|
const currentId = webview.getWebContents().id;
|
||||||
const tabId = webview.getAttribute('data-tab-id');
|
const tabId = webview.getAttribute('data-tab-id');
|
||||||
const concurrentTab: HTMLButtonElement = document.querySelector(`div[data-tab-id="${tabId}"]`);
|
const concurrentTab = document.querySelector(`div[data-tab-id="${tabId}"]`);
|
||||||
if (currentId === webviewId) {
|
if (currentId === webviewId) {
|
||||||
concurrentTab.click();
|
concurrentTab.click();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('render-taskbar-icon', (event: Event, messageCount: number) => {
|
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
|
||||||
// Create a canvas from unread messagecounts
|
// Create a canvas from unread messagecounts
|
||||||
function createOverlayIcon(messageCount: number): HTMLCanvasElement {
|
function createOverlayIcon(messageCount) {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.height = 128;
|
canvas.height = 128;
|
||||||
canvas.width = 128;
|
canvas.width = 128;
|
||||||
@@ -871,18 +724,10 @@ class ServerManagerView {
|
|||||||
ipcRenderer.on('open-feedback-modal', () => {
|
ipcRenderer.on('open-feedback-modal', () => {
|
||||||
feedbackHolder.classList.add('show');
|
feedbackHolder.classList.add('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('copy-zulip-url', () => {
|
|
||||||
clipboard.writeText(this.getCurrentActiveServer());
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('new-server', () => {
|
|
||||||
this.openSettings('AddServer');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.onload = () => {
|
||||||
const serverManagerView = new ServerManagerView();
|
const serverManagerView = new ServerManagerView();
|
||||||
const reconnectUtil = new ReconnectUtil(serverManagerView);
|
const reconnectUtil = new ReconnectUtil(serverManagerView);
|
||||||
serverManagerView.init();
|
serverManagerView.init();
|
||||||
@@ -901,8 +746,7 @@ window.addEventListener('load', () => {
|
|||||||
// `--no-electron-connect`
|
// `--no-electron-connect`
|
||||||
const mainProcessArgv = remote.getGlobal('process').argv;
|
const mainProcessArgv = remote.getGlobal('process').argv;
|
||||||
if (isDev && !mainProcessArgv.includes('--no-electron-connect')) {
|
if (isDev && !mainProcessArgv.includes('--no-electron-connect')) {
|
||||||
require('electron-connect').client.create();
|
const electronConnect = require('electron-connect');
|
||||||
|
electronConnect.client.create();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
export = new ServerManagerView();
|
|
||||||
@@ -1,27 +1,18 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { ipcRenderer } from 'electron';
|
|
||||||
import {
|
const { ipcRenderer } = require('electron');
|
||||||
|
const url = require('url');
|
||||||
|
const MacNotifier = require('node-mac-notifier');
|
||||||
|
const ConfigUtil = require('../utils/config-util');
|
||||||
|
const {
|
||||||
appId, customReply, focusCurrentServer, parseReply, setupReply
|
appId, customReply, focusCurrentServer, parseReply, setupReply
|
||||||
} from './helpers';
|
} = require('./helpers');
|
||||||
|
|
||||||
import url = require('url');
|
|
||||||
import MacNotifier = require('node-mac-notifier');
|
|
||||||
import ConfigUtil = require('../utils/config-util');
|
|
||||||
|
|
||||||
type ReplyHandler = (response: string) => void;
|
|
||||||
type ClickHandler = () => void;
|
|
||||||
let replyHandler: ReplyHandler;
|
|
||||||
let clickHandler: ClickHandler;
|
|
||||||
|
|
||||||
interface NotificationHandlerArgs {
|
|
||||||
response: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let replyHandler;
|
||||||
|
let clickHandler;
|
||||||
class DarwinNotification {
|
class DarwinNotification {
|
||||||
tag: string;
|
constructor(title, opts) {
|
||||||
|
const silent = ConfigUtil.getConfigItem('silent') || false;
|
||||||
constructor(title: string, opts: NotificationOptions) {
|
|
||||||
const silent: boolean = ConfigUtil.getConfigItem('silent') || false;
|
|
||||||
const { host, protocol } = location;
|
const { host, protocol } = location;
|
||||||
const { icon } = opts;
|
const { icon } = opts;
|
||||||
const profilePic = url.resolve(`${protocol}//${host}`, icon);
|
const profilePic = url.resolve(`${protocol}//${host}`, icon);
|
||||||
@@ -48,45 +39,45 @@ class DarwinNotification {
|
|||||||
notification.addEventListener('reply', this.notificationHandler);
|
notification.addEventListener('reply', this.notificationHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
static requestPermission(): void {
|
static requestPermission() {
|
||||||
return; // eslint-disable-line no-useless-return
|
return; // eslint-disable-line no-useless-return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override default Notification permission
|
// Override default Notification permission
|
||||||
static get permission(): NotificationPermission {
|
static get permission() {
|
||||||
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
|
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
|
||||||
}
|
}
|
||||||
|
|
||||||
set onreply(handler: ReplyHandler) {
|
set onreply(handler) {
|
||||||
replyHandler = handler;
|
replyHandler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
get onreply(): ReplyHandler {
|
get onreply() {
|
||||||
return replyHandler;
|
return replyHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
set onclick(handler: ClickHandler) {
|
set onclick(handler) {
|
||||||
clickHandler = handler;
|
clickHandler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
get onclick(): ClickHandler {
|
get onclick() {
|
||||||
return clickHandler;
|
return clickHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not something that is common or
|
// not something that is common or
|
||||||
// used by zulip server but added to be
|
// used by zulip server but added to be
|
||||||
// future proff.
|
// future proff.
|
||||||
addEventListener(event: string, handler: ClickHandler | ReplyHandler): void {
|
addEventListener(event, handler) {
|
||||||
if (event === 'click') {
|
if (event === 'click') {
|
||||||
clickHandler = handler as ClickHandler;
|
clickHandler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event === 'reply') {
|
if (event === 'reply') {
|
||||||
replyHandler = handler as ReplyHandler;
|
replyHandler = handler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationHandler({ response }: NotificationHandlerArgs): void {
|
notificationHandler({ response }) {
|
||||||
response = parseReply(response);
|
response = parseReply(response);
|
||||||
focusCurrentServer();
|
focusCurrentServer();
|
||||||
setupReply(this.tag);
|
setupReply(this.tag);
|
||||||
@@ -101,7 +92,7 @@ class DarwinNotification {
|
|||||||
|
|
||||||
// method specific to notification api
|
// method specific to notification api
|
||||||
// used by zulip
|
// used by zulip
|
||||||
close(): void {
|
close() {
|
||||||
return; // eslint-disable-line no-useless-return
|
return; // eslint-disable-line no-useless-return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
const { ipcRenderer } = require('electron');
|
||||||
import { focusCurrentServer } from './helpers';
|
const ConfigUtil = require('../utils/config-util');
|
||||||
|
const { focusCurrentServer } = require('./helpers');
|
||||||
import ConfigUtil = require('../utils/config-util');
|
|
||||||
|
|
||||||
const NativeNotification = window.Notification;
|
const NativeNotification = window.Notification;
|
||||||
class BaseNotification extends NativeNotification {
|
class BaseNotification extends NativeNotification {
|
||||||
constructor(title: string, opts: NotificationOptions) {
|
constructor(title, opts) {
|
||||||
opts.silent = true;
|
opts.silent = true;
|
||||||
super(title, opts);
|
super(title, opts);
|
||||||
|
|
||||||
@@ -19,14 +18,14 @@ class BaseNotification extends NativeNotification {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static requestPermission(): void {
|
static requestPermission() {
|
||||||
return; // eslint-disable-line no-useless-return
|
return; // eslint-disable-line no-useless-return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override default Notification permission
|
// Override default Notification permission
|
||||||
static get permission(): NotificationPermission {
|
static get permission() {
|
||||||
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
|
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = BaseNotification;
|
module.exports = BaseNotification;
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { remote } from 'electron';
|
const { remote } = require('electron');
|
||||||
|
const Logger = require('../utils/logger-util.js');
|
||||||
import Logger = require('../utils/logger-util');
|
|
||||||
|
|
||||||
const logger = new Logger({
|
const logger = new Logger({
|
||||||
file: 'errors.log',
|
file: 'errors.log',
|
||||||
@@ -8,25 +7,24 @@ const logger = new Logger({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Do not change this
|
// Do not change this
|
||||||
export const appId = 'org.zulip.zulip-electron';
|
const appId = 'org.zulip.zulip-electron';
|
||||||
|
|
||||||
export type BotListItem = [string, string];
|
const botsList = [];
|
||||||
const botsList: BotListItem[] = [];
|
|
||||||
let botsListLoaded = false;
|
let botsListLoaded = false;
|
||||||
|
|
||||||
// this function load list of bots from the server
|
// this function load list of bots from the server
|
||||||
// sync=True for a synchronous getJSON request
|
// sync=True for a synchronous getJSON request
|
||||||
// in case botsList isn't already completely loaded when required in parseRely
|
// in case botsList isn't already completely loaded when required in parseRely
|
||||||
export function loadBots(sync = false): void {
|
function loadBots(sync = false) {
|
||||||
const { $ } = window;
|
const { $ } = window;
|
||||||
botsList.length = 0;
|
botsList.length = 0;
|
||||||
if (sync) {
|
if (sync) {
|
||||||
$.ajaxSetup({async: false});
|
$.ajaxSetup({async: false});
|
||||||
}
|
}
|
||||||
$.getJSON('/json/users')
|
$.getJSON('/json/users')
|
||||||
.done((data: any) => {
|
.done(data => {
|
||||||
const { members } = data;
|
const members = data.members;
|
||||||
members.forEach((membersRow: any) => {
|
members.forEach(membersRow => {
|
||||||
if (membersRow.is_bot) {
|
if (membersRow.is_bot) {
|
||||||
const bot = `@${membersRow.full_name}`;
|
const bot = `@${membersRow.full_name}`;
|
||||||
const mention = `@**${bot.replace(/^@/, '')}**`;
|
const mention = `@**${bot.replace(/^@/, '')}**`;
|
||||||
@@ -35,7 +33,7 @@ export function loadBots(sync = false): void {
|
|||||||
});
|
});
|
||||||
botsListLoaded = true;
|
botsListLoaded = true;
|
||||||
})
|
})
|
||||||
.fail((error: any) => {
|
.fail(error => {
|
||||||
logger.log('Load bots request failed: ', error.responseText);
|
logger.log('Load bots request failed: ', error.responseText);
|
||||||
logger.log('Load bots request status: ', error.statusText);
|
logger.log('Load bots request status: ', error.statusText);
|
||||||
});
|
});
|
||||||
@@ -44,7 +42,7 @@ export function loadBots(sync = false): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkElements(...elements: any[]): boolean {
|
function checkElements(...elements) {
|
||||||
let status = true;
|
let status = true;
|
||||||
elements.forEach(element => {
|
elements.forEach(element => {
|
||||||
if (element === null || element === undefined) {
|
if (element === null || element === undefined) {
|
||||||
@@ -54,13 +52,13 @@ export function checkElements(...elements: any[]): boolean {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function customReply(reply: string): void {
|
function customReply(reply) {
|
||||||
// server does not support notification reply yet.
|
// server does not support notification reply yet.
|
||||||
const buttonSelector = '.messagebox #send_controls button[type=submit]';
|
const buttonSelector = '.messagebox #send_controls button[type=submit]';
|
||||||
const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content';
|
const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content';
|
||||||
const textarea: HTMLInputElement = document.querySelector('#compose-textarea');
|
const textarea = document.querySelector('#compose-textarea');
|
||||||
const messagebox: HTMLButtonElement = document.querySelector(messageboxSelector);
|
const messagebox = document.querySelector(messageboxSelector);
|
||||||
const sendButton: HTMLButtonElement = document.querySelector(buttonSelector);
|
const sendButton = document.querySelector(buttonSelector);
|
||||||
|
|
||||||
// sanity check for old server versions
|
// sanity check for old server versions
|
||||||
const elementsExists = checkElements(textarea, messagebox, sendButton);
|
const elementsExists = checkElements(textarea, messagebox, sendButton);
|
||||||
@@ -79,24 +77,19 @@ const webContentsId = webContents.id;
|
|||||||
|
|
||||||
// this function will focus the server that sent
|
// this function will focus the server that sent
|
||||||
// the notification. Main function implemented in main.js
|
// the notification. Main function implemented in main.js
|
||||||
export function focusCurrentServer(): void {
|
function focusCurrentServer() {
|
||||||
// TODO: TypeScript: currentWindow of type BrowserWindow doesn't
|
currentWindow.send('focus-webview-with-id', webContentsId);
|
||||||
// have a .send() property per typescript.
|
|
||||||
(currentWindow as any).send('focus-webview-with-id', webContentsId);
|
|
||||||
}
|
}
|
||||||
// this function parses the reply from to notification
|
// this function parses the reply from to notification
|
||||||
// making it easier to reply from notification eg
|
// making it easier to reply from notification eg
|
||||||
// @username in reply will be converted to @**username**
|
// @username in reply will be converted to @**username**
|
||||||
// #stream in reply will be converted to #**stream**
|
// #stream in reply will be converted to #**stream**
|
||||||
// bot mentions are not yet supported
|
// bot mentions are not yet supported
|
||||||
export function parseReply(reply: string): string {
|
function parseReply(reply) {
|
||||||
const usersDiv = document.querySelectorAll('#user_presences li');
|
const usersDiv = document.querySelectorAll('#user_presences li');
|
||||||
const streamHolder = document.querySelectorAll('#stream_filters li');
|
const streamHolder = document.querySelectorAll('#stream_filters li');
|
||||||
|
const users = [];
|
||||||
type UsersItem = BotListItem;
|
const streams = [];
|
||||||
type StreamsItem = BotListItem;
|
|
||||||
const users: UsersItem[] = [];
|
|
||||||
const streams: StreamsItem[] = [];
|
|
||||||
|
|
||||||
usersDiv.forEach(userRow => {
|
usersDiv.forEach(userRow => {
|
||||||
const anchor = userRow.querySelector('span a');
|
const anchor = userRow.querySelector('span a');
|
||||||
@@ -146,8 +139,18 @@ export function parseReply(reply: string): string {
|
|||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupReply(id: string): void {
|
function setupReply(id) {
|
||||||
const { narrow } = window;
|
const { narrow } = window;
|
||||||
const narrowByTopic = narrow.by_topic || narrow.by_subject;
|
const narrowByTopic = narrow.by_topic || narrow.by_subject;
|
||||||
narrowByTopic(id, { trigger: 'notification' });
|
narrowByTopic(id, { trigger: 'notification' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
appId,
|
||||||
|
checkElements,
|
||||||
|
customReply,
|
||||||
|
parseReply,
|
||||||
|
setupReply,
|
||||||
|
focusCurrentServer,
|
||||||
|
loadBots
|
||||||
|
};
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { remote } from 'electron';
|
const {
|
||||||
import * as params from '../utils/params-util';
|
remote: { app }
|
||||||
import { appId, loadBots } from './helpers';
|
} = require('electron');
|
||||||
|
|
||||||
import DefaultNotification = require('./default-notification');
|
const params = require('../utils/params-util.js');
|
||||||
const { app } = remote;
|
const DefaultNotification = require('./default-notification');
|
||||||
|
const { appId, loadBots } = require('./helpers');
|
||||||
|
|
||||||
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
|
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
|
||||||
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
|
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
|
||||||
@@ -14,11 +15,12 @@ app.setAppUserModelId(appId);
|
|||||||
window.Notification = DefaultNotification;
|
window.Notification = DefaultNotification;
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
window.Notification = require('./darwin-notifications');
|
const DarwinNotification = require('./darwin-notifications');
|
||||||
|
window.Notification = DarwinNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
// eslint-disable-next-line no-undef, @typescript-eslint/camelcase
|
// eslint-disable-next-line no-undef, camelcase
|
||||||
if (params.isPageParams() && page_params.realm_uri) {
|
if (params.isPageParams() && page_params.realm_uri) {
|
||||||
loadBots();
|
loadBots();
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
class NetworkTroubleshootingView {
|
class NetworkTroubleshootingView {
|
||||||
$reconnectButton: Element;
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.$reconnectButton = document.querySelector('#reconnect');
|
this.$reconnectButton = document.getElementById('reconnect');
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.$reconnectButton.addEventListener('click', () => {
|
this.$reconnectButton.addEventListener('click', () => {
|
||||||
ipcRenderer.send('forward-message', 'reload-viewer');
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.onload = () => {
|
||||||
const networkTroubleshootingView = new NetworkTroubleshootingView();
|
const networkTroubleshootingView = new NetworkTroubleshootingView();
|
||||||
networkTroubleshootingView.init();
|
networkTroubleshootingView.init();
|
||||||
});
|
};
|
||||||
@@ -1,28 +1,20 @@
|
|||||||
'use-strict';
|
'use-strict';
|
||||||
|
|
||||||
import { remote, OpenDialogOptions } from 'electron';
|
const { dialog } = require('electron').remote;
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
import path = require('path');
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
import BaseComponent = require('../../components/base');
|
const CertificateUtil = require(__dirname + '/../../utils/certificate-util.js');
|
||||||
import CertificateUtil = require('../../utils/certificate-util');
|
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||||
import DomainUtil = require('../../utils/domain-util');
|
|
||||||
|
|
||||||
const { dialog } = remote;
|
|
||||||
|
|
||||||
class AddCertificate extends BaseComponent {
|
class AddCertificate extends BaseComponent {
|
||||||
// TODO: TypeScript - Here props should be object type
|
constructor(props) {
|
||||||
props: any;
|
|
||||||
_certFile: string;
|
|
||||||
$addCertificate: Element | null;
|
|
||||||
addCertificateButton: Element | null;
|
|
||||||
serverUrl: HTMLInputElement | null;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
super();
|
||||||
this.props = props;
|
this.props = props;
|
||||||
this._certFile = '';
|
this._certFile = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div class="settings-card certificates-card">
|
<div class="settings-card certificates-card">
|
||||||
<div class="certificate-input">
|
<div class="certificate-input">
|
||||||
@@ -37,15 +29,15 @@ class AddCertificate extends BaseComponent {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.$addCertificate = this.generateNodeFromTemplate(this.template());
|
this.$addCertificate = this.generateNodeFromTemplate(this.template());
|
||||||
this.props.$root.append(this.$addCertificate);
|
this.props.$root.appendChild(this.$addCertificate);
|
||||||
this.addCertificateButton = this.$addCertificate.querySelector('#add-certificate-button');
|
this.addCertificateButton = this.$addCertificate.querySelector('#add-certificate-button');
|
||||||
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
|
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0];
|
||||||
this.initListeners();
|
this.initListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAndAdd(): void {
|
validateAndAdd() {
|
||||||
const certificate = this._certFile;
|
const certificate = this._certFile;
|
||||||
const serverUrl = this.serverUrl.value;
|
const serverUrl = this.serverUrl.value;
|
||||||
if (certificate !== '' && serverUrl !== '') {
|
if (certificate !== '' && serverUrl !== '') {
|
||||||
@@ -67,9 +59,10 @@ class AddCertificate extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addHandler(): void {
|
addHandler() {
|
||||||
const showDialogOptions: OpenDialogOptions = {
|
const showDialogOptions = {
|
||||||
title: 'Select file',
|
title: 'Select file',
|
||||||
|
defaultId: 1,
|
||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
filters: [{ name: 'crt, pem', extensions: ['crt', 'pem'] }]
|
filters: [{ name: 'crt, pem', extensions: ['crt', 'pem'] }]
|
||||||
};
|
};
|
||||||
@@ -81,7 +74,7 @@ class AddCertificate extends BaseComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initListeners(): void {
|
initListeners() {
|
||||||
this.addCertificateButton.addEventListener('click', () => {
|
this.addCertificateButton.addEventListener('click', () => {
|
||||||
this.addHandler();
|
this.addHandler();
|
||||||
});
|
});
|
||||||
@@ -96,4 +89,4 @@ class AddCertificate extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = AddCertificate;
|
module.exports = AddCertificate;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { app } from 'electron';
|
const electron = require('electron');
|
||||||
|
const { app } = require('electron');
|
||||||
|
|
||||||
import electron = require('electron');
|
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
|
||||||
import ConfigUtil = require('../../utils/config-util');
|
|
||||||
|
|
||||||
let instance: BadgeSettings | any = null;
|
let instance = null;
|
||||||
|
|
||||||
class BadgeSettings {
|
class BadgeSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -17,7 +17,7 @@ class BadgeSettings {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
showBadgeCount(messageCount: number, mainWindow: electron.BrowserWindow): void {
|
showBadgeCount(messageCount, mainWindow) {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
app.setBadgeCount(messageCount);
|
app.setBadgeCount(messageCount);
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ class BadgeSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hideBadgeCount(mainWindow: electron.BrowserWindow): void {
|
hideBadgeCount(mainWindow) {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
app.setBadgeCount(0);
|
app.setBadgeCount(0);
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ class BadgeSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBadge(badgeCount: number, mainWindow: electron.BrowserWindow): void {
|
updateBadge(badgeCount, mainWindow) {
|
||||||
if (ConfigUtil.getConfigItem('badgeOption', true)) {
|
if (ConfigUtil.getConfigItem('badgeOption', true)) {
|
||||||
this.showBadgeCount(badgeCount, mainWindow);
|
this.showBadgeCount(badgeCount, mainWindow);
|
||||||
} else {
|
} else {
|
||||||
@@ -43,7 +43,7 @@ class BadgeSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOverlayIcon(messageCount: number, mainWindow: electron.BrowserWindow): void {
|
updateOverlayIcon(messageCount, mainWindow) {
|
||||||
if (!mainWindow.isFocused()) {
|
if (!mainWindow.isFocused()) {
|
||||||
mainWindow.flashFrame(ConfigUtil.getConfigItem('flashTaskbarOnMessage'));
|
mainWindow.flashFrame(ConfigUtil.getConfigItem('flashTaskbarOnMessage'));
|
||||||
}
|
}
|
||||||
@@ -54,10 +54,10 @@ class BadgeSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTaskbarIcon(data: string, text: string, mainWindow: electron.BrowserWindow): void {
|
updateTaskbarIcon(data, text, mainWindow) {
|
||||||
const img = electron.nativeImage.createFromDataURL(data);
|
const img = electron.nativeImage.createFromDataURL(data);
|
||||||
mainWindow.setOverlayIcon(img, text);
|
mainWindow.setOverlayIcon(img, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new BadgeSettings();
|
module.exports = new BadgeSettings();
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
import BaseComponent = require('../../components/base');
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
|
||||||
class BaseSection extends BaseComponent {
|
class BaseSection extends BaseComponent {
|
||||||
// TODO: TypeScript - Here props should be object type
|
generateSettingOption(props) {
|
||||||
generateSettingOption(props: any): void {
|
|
||||||
const {$element, value, clickHandler} = props;
|
const {$element, value, clickHandler} = props;
|
||||||
|
|
||||||
$element.innerHTML = '';
|
$element.innerHTML = '';
|
||||||
|
|
||||||
const $optionControl = this.generateNodeFromTemplate(this.generateOptionTemplate(value));
|
const $optionControl = this.generateNodeFromTemplate(this.generateOptionTemplate(value));
|
||||||
$element.append($optionControl);
|
$element.appendChild($optionControl);
|
||||||
|
|
||||||
$optionControl.addEventListener('click', clickHandler);
|
$optionControl.addEventListener('click', clickHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateOptionTemplate(settingOption: boolean): string {
|
generateOptionTemplate(settingOption) {
|
||||||
if (settingOption) {
|
if (settingOption) {
|
||||||
return `
|
return `
|
||||||
<div class="action">
|
<div class="action">
|
||||||
@@ -39,9 +38,9 @@ class BaseSection extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadApp(): void {
|
reloadApp() {
|
||||||
ipcRenderer.send('forward-message', 'reload-viewer');
|
ipcRenderer.send('forward-message', 'reload-viewer');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = BaseSection;
|
module.exports = BaseSection;
|
||||||
@@ -1,27 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
const BaseSection = require(__dirname + '/base-section.js');
|
||||||
|
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||||
import BaseSection = require('./base-section');
|
const ServerInfoForm = require(__dirname + '/server-info-form.js');
|
||||||
import DomainUtil = require('../../utils/domain-util');
|
const AddCertificate = require(__dirname + '/add-certificate.js');
|
||||||
import ServerInfoForm = require('./server-info-form');
|
|
||||||
import AddCertificate = require('./add-certificate');
|
|
||||||
import FindAccounts = require('./find-accounts');
|
|
||||||
|
|
||||||
class ConnectedOrgSection extends BaseSection {
|
class ConnectedOrgSection extends BaseSection {
|
||||||
// TODO: TypeScript - Here props should be object type
|
constructor(props) {
|
||||||
props: any;
|
|
||||||
$serverInfoContainer: Element | null;
|
|
||||||
$existingServers: Element | null;
|
|
||||||
$newOrgButton: HTMLButtonElement | null;
|
|
||||||
$addCertificateContainer: Element | null;
|
|
||||||
$findAccountsContainer: Element | null;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
super();
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div class="settings-pane" id="server-settings-pane">
|
<div class="settings-pane" id="server-settings-pane">
|
||||||
<div class="page-title">Connected organizations</div>
|
<div class="page-title">Connected organizations</div>
|
||||||
@@ -30,27 +20,24 @@ class ConnectedOrgSection extends BaseSection {
|
|||||||
<div id="new-org-button"><button class="green sea w-250">Connect to another organization</button></div>
|
<div id="new-org-button"><button class="green sea w-250">Connect to another organization</button></div>
|
||||||
<div class="page-title">Add Custom Certificates</div>
|
<div class="page-title">Add Custom Certificates</div>
|
||||||
<div id="add-certificate-container"></div>
|
<div id="add-certificate-container"></div>
|
||||||
<div class="page-title">Find accounts by email</div>
|
|
||||||
<div id="find-accounts-container"></div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.initServers();
|
this.initServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
initServers(): void {
|
initServers() {
|
||||||
this.props.$root.innerHTML = '';
|
this.props.$root.innerHTML = '';
|
||||||
|
|
||||||
const servers = DomainUtil.getDomains();
|
const servers = DomainUtil.getDomains();
|
||||||
this.props.$root.innerHTML = this.template();
|
this.props.$root.innerHTML = this.template();
|
||||||
|
|
||||||
this.$serverInfoContainer = document.querySelector('#server-info-container');
|
this.$serverInfoContainer = document.getElementById('server-info-container');
|
||||||
this.$existingServers = document.querySelector('#existing-servers');
|
this.$existingServers = document.getElementById('existing-servers');
|
||||||
this.$newOrgButton = document.querySelector('#new-org-button');
|
this.$newOrgButton = document.getElementById('new-org-button');
|
||||||
this.$addCertificateContainer = document.querySelector('#add-certificate-container');
|
this.$addCertificateContainer = document.getElementById('add-certificate-container');
|
||||||
this.$findAccountsContainer = document.querySelector('#find-accounts-container');
|
|
||||||
|
|
||||||
const noServerText = 'All the connected orgnizations will appear here';
|
const noServerText = 'All the connected orgnizations will appear here';
|
||||||
// Show noServerText if no servers are there otherwise hide it
|
// Show noServerText if no servers are there otherwise hide it
|
||||||
@@ -66,24 +53,20 @@ class ConnectedOrgSection extends BaseSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$newOrgButton.addEventListener('click', () => {
|
this.$newOrgButton.addEventListener('click', () => {
|
||||||
|
// We don't need to import this since it's already imported in other files
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
ipcRenderer.send('forward-message', 'open-org-tab');
|
ipcRenderer.send('forward-message', 'open-org-tab');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.initAddCertificate();
|
this.initAddCertificate();
|
||||||
this.initFindAccounts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initAddCertificate(): void {
|
initAddCertificate() {
|
||||||
new AddCertificate({
|
new AddCertificate({
|
||||||
$root: this.$addCertificateContainer
|
$root: this.$addCertificateContainer
|
||||||
}).init();
|
}).init();
|
||||||
}
|
}
|
||||||
|
|
||||||
initFindAccounts(): void {
|
|
||||||
new FindAccounts({
|
|
||||||
$root: this.$findAccountsContainer
|
|
||||||
}).init();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export = ConnectedOrgSection;
|
module.exports = ConnectedOrgSection;
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
'use-strict';
|
|
||||||
|
|
||||||
import { shell } from 'electron';
|
|
||||||
|
|
||||||
import BaseComponent = require('../../components/base');
|
|
||||||
|
|
||||||
class FindAccounts extends BaseComponent {
|
|
||||||
// TODO: TypeScript - Here props should be object type
|
|
||||||
props: any;
|
|
||||||
$findAccounts: Element | null;
|
|
||||||
$findAccountsButton: Element | null;
|
|
||||||
$serverUrlField: HTMLInputElement | null;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
|
||||||
this.props = props;
|
|
||||||
}
|
|
||||||
|
|
||||||
template(): string {
|
|
||||||
return `
|
|
||||||
<div class="settings-card certificate-card">
|
|
||||||
<div class="certificate-input">
|
|
||||||
<div>Organization URL</div>
|
|
||||||
<input class="setting-input-value" value="zulipchat.com"/>
|
|
||||||
</div>
|
|
||||||
<div class="certificate-input">
|
|
||||||
<button class="green w-150" id="find-accounts-button">Find accounts</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
init(): void {
|
|
||||||
this.$findAccounts = this.generateNodeFromTemplate(this.template());
|
|
||||||
this.props.$root.append(this.$findAccounts);
|
|
||||||
this.$findAccountsButton = this.$findAccounts.querySelector('#find-accounts-button');
|
|
||||||
this.$serverUrlField = this.$findAccounts.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
|
|
||||||
this.initListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
findAccounts(url: string): void {
|
|
||||||
if (!url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!url.startsWith('http')) {
|
|
||||||
url = 'https://' + url;
|
|
||||||
}
|
|
||||||
shell.openExternal(url + '/accounts/find');
|
|
||||||
}
|
|
||||||
|
|
||||||
initListeners(): void {
|
|
||||||
this.$findAccountsButton.addEventListener('click', () => {
|
|
||||||
this.findAccounts(this.$serverUrlField.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$serverUrlField.addEventListener('click', () => {
|
|
||||||
if (this.$serverUrlField.value === 'zulipchat.com') {
|
|
||||||
this.$serverUrlField.setSelectionRange(0, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$serverUrlField.addEventListener('keypress', event => {
|
|
||||||
if (event.keyCode === 13) {
|
|
||||||
this.findAccounts(this.$serverUrlField.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$serverUrlField.addEventListener('input', () => {
|
|
||||||
if (this.$serverUrlField.value) {
|
|
||||||
this.$serverUrlField.classList.remove('invalid-input-value');
|
|
||||||
} else {
|
|
||||||
this.$serverUrlField.classList.add('invalid-input-value');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export = FindAccounts;
|
|
||||||
@@ -1,24 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { ipcRenderer, remote, OpenDialogOptions } from 'electron';
|
const path = require('path');
|
||||||
|
const { ipcRenderer, remote } = require('electron');
|
||||||
import path = require('path');
|
const fs = require('fs-extra');
|
||||||
import fs = require('fs-extra');
|
|
||||||
|
|
||||||
const { app, dialog } = remote;
|
const { app, dialog } = remote;
|
||||||
const currentBrowserWindow = remote.getCurrentWindow();
|
const currentBrowserWindow = remote.getCurrentWindow();
|
||||||
|
const BaseSection = require(__dirname + '/base-section.js');
|
||||||
import BaseSection = require('./base-section');
|
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
|
||||||
import ConfigUtil = require('../../utils/config-util');
|
|
||||||
|
|
||||||
class GeneralSection extends BaseSection {
|
class GeneralSection extends BaseSection {
|
||||||
// TODO: TypeScript - Here props should be object type
|
constructor(props) {
|
||||||
props: any;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
super();
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div class="settings-pane">
|
<div class="settings-pane">
|
||||||
<div class="title">Appearance</div>
|
<div class="title">Appearance</div>
|
||||||
@@ -28,7 +24,7 @@ class GeneralSection extends BaseSection {
|
|||||||
<div class="setting-control"></div>
|
<div class="setting-control"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row" id="menubar-option" style= "display:${process.platform === 'darwin' ? 'none' : ''}">
|
<div class="setting-row" id="menubar-option" style= "display:${process.platform === 'darwin' ? 'none' : ''}">
|
||||||
<div class="setting-description">Auto hide menu bar (Press Alt key to display)</div>
|
<div class="setting-description">Auto hide Menubar (Press Alt key to display)</div>
|
||||||
<div class="setting-control"></div>
|
<div class="setting-control"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row" id="sidebar-option">
|
<div class="setting-row" id="sidebar-option">
|
||||||
@@ -51,7 +47,7 @@ class GeneralSection extends BaseSection {
|
|||||||
<div class="title">Desktop Notifications</div>
|
<div class="title">Desktop Notifications</div>
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<div class="setting-row" id="show-notification-option">
|
<div class="setting-row" id="show-notification-option">
|
||||||
<div class="setting-description">Show desktop notifications</div>
|
<div class="setting-description">Show Desktop Notifications</div>
|
||||||
<div class="setting-control"></div>
|
<div class="setting-control"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row" id="silent-option">
|
<div class="setting-row" id="silent-option">
|
||||||
@@ -87,10 +83,6 @@ class GeneralSection extends BaseSection {
|
|||||||
</div>
|
</div>
|
||||||
<div class="title">Advanced</div>
|
<div class="title">Advanced</div>
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<div class="setting-row" id="enable-error-reporting">
|
|
||||||
<div class="setting-description">Enable error reporting (requires restart)</div>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-row" id="show-download-folder">
|
<div class="setting-row" id="show-download-folder">
|
||||||
<div class="setting-description">Show downloaded files in file manager</div>
|
<div class="setting-description">Show downloaded files in file manager</div>
|
||||||
<div class="setting-control"></div>
|
<div class="setting-control"></div>
|
||||||
@@ -128,14 +120,14 @@ class GeneralSection extends BaseSection {
|
|||||||
<div class="setting-row" id="resetdata-option">
|
<div class="setting-row" id="resetdata-option">
|
||||||
<div class="setting-description">This will delete all application data including all added accounts and preferences
|
<div class="setting-description">This will delete all application data including all added accounts and preferences
|
||||||
</div>
|
</div>
|
||||||
<button class="reset-data-button red w-150">Reset App Data</button>
|
<button class="reset-data-button green w-150">Reset App Data</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.props.$root.innerHTML = this.template();
|
this.props.$root.innerHTML = this.template();
|
||||||
this.updateTrayOption();
|
this.updateTrayOption();
|
||||||
this.updateBadgeOption();
|
this.updateBadgeOption();
|
||||||
@@ -153,7 +145,6 @@ class GeneralSection extends BaseSection {
|
|||||||
this.removeCustomCSS();
|
this.removeCustomCSS();
|
||||||
this.downloadFolder();
|
this.downloadFolder();
|
||||||
this.showDownloadFolder();
|
this.showDownloadFolder();
|
||||||
this.enableErrorReporting();
|
|
||||||
|
|
||||||
// Platform specific settings
|
// Platform specific settings
|
||||||
|
|
||||||
@@ -172,7 +163,7 @@ class GeneralSection extends BaseSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTrayOption(): void {
|
updateTrayOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#tray-option .setting-control'),
|
$element: document.querySelector('#tray-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('trayIcon', true),
|
value: ConfigUtil.getConfigItem('trayIcon', true),
|
||||||
@@ -185,7 +176,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMenubarOption(): void {
|
updateMenubarOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#menubar-option .setting-control'),
|
$element: document.querySelector('#menubar-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('autoHideMenubar', false),
|
value: ConfigUtil.getConfigItem('autoHideMenubar', false),
|
||||||
@@ -198,7 +189,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBadgeOption(): void {
|
updateBadgeOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#badge-option .setting-control'),
|
$element: document.querySelector('#badge-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('badgeOption', true),
|
value: ConfigUtil.getConfigItem('badgeOption', true),
|
||||||
@@ -211,7 +202,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDockBouncing(): void {
|
updateDockBouncing() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#dock-bounce-option .setting-control'),
|
$element: document.querySelector('#dock-bounce-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('dockBouncing', true),
|
value: ConfigUtil.getConfigItem('dockBouncing', true),
|
||||||
@@ -223,7 +214,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFlashTaskbar(): void {
|
updateFlashTaskbar() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#flash-taskbar-option .setting-control'),
|
$element: document.querySelector('#flash-taskbar-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true),
|
value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true),
|
||||||
@@ -235,7 +226,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
autoUpdateOption(): void {
|
autoUpdateOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#autoupdate-option .setting-control'),
|
$element: document.querySelector('#autoupdate-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('autoUpdate', true),
|
value: ConfigUtil.getConfigItem('autoUpdate', true),
|
||||||
@@ -251,7 +242,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
betaUpdateOption(): void {
|
betaUpdateOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#betaupdate-option .setting-control'),
|
$element: document.querySelector('#betaupdate-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('betaUpdate', false),
|
value: ConfigUtil.getConfigItem('betaUpdate', false),
|
||||||
@@ -265,7 +256,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSilentOption(): void {
|
updateSilentOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#silent-option .setting-control'),
|
$element: document.querySelector('#silent-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('silent', false),
|
value: ConfigUtil.getConfigItem('silent', false),
|
||||||
@@ -273,14 +264,12 @@ class GeneralSection extends BaseSection {
|
|||||||
const newValue = !ConfigUtil.getConfigItem('silent', true);
|
const newValue = !ConfigUtil.getConfigItem('silent', true);
|
||||||
ConfigUtil.setConfigItem('silent', newValue);
|
ConfigUtil.setConfigItem('silent', newValue);
|
||||||
this.updateSilentOption();
|
this.updateSilentOption();
|
||||||
// TODO: TypeScript: currentWindow of type BrowserWindow doesn't
|
currentBrowserWindow.send('toggle-silent', newValue);
|
||||||
// have a .send() property per typescript.
|
|
||||||
(currentBrowserWindow as any).send('toggle-silent', newValue);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showDesktopNotification(): void {
|
showDesktopNotification() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#show-notification-option .setting-control'),
|
$element: document.querySelector('#show-notification-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('showNotification', true),
|
value: ConfigUtil.getConfigItem('showNotification', true),
|
||||||
@@ -292,7 +281,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSidebarOption(): void {
|
updateSidebarOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#sidebar-option .setting-control'),
|
$element: document.querySelector('#sidebar-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('showSidebar', true),
|
value: ConfigUtil.getConfigItem('showSidebar', true),
|
||||||
@@ -305,7 +294,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStartAtLoginOption(): void {
|
updateStartAtLoginOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#startAtLogin-option .setting-control'),
|
$element: document.querySelector('#startAtLogin-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('startAtLogin', false),
|
value: ConfigUtil.getConfigItem('startAtLogin', false),
|
||||||
@@ -318,7 +307,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
enableSpellchecker(): void {
|
enableSpellchecker() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#enable-spellchecker-option .setting-control'),
|
$element: document.querySelector('#enable-spellchecker-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('enableSpellchecker', true),
|
value: ConfigUtil.getConfigItem('enableSpellchecker', true),
|
||||||
@@ -330,19 +319,7 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
enableErrorReporting(): void {
|
clearAppDataDialog() {
|
||||||
this.generateSettingOption({
|
|
||||||
$element: document.querySelector('#enable-error-reporting .setting-control'),
|
|
||||||
value: ConfigUtil.getConfigItem('errorReporting', true),
|
|
||||||
clickHandler: () => {
|
|
||||||
const newValue = !ConfigUtil.getConfigItem('errorReporting');
|
|
||||||
ConfigUtil.setConfigItem('errorReporting', newValue);
|
|
||||||
this.enableErrorReporting();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAppDataDialog(): void {
|
|
||||||
const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
|
const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
|
||||||
const getAppPath = path.join(app.getPath('appData'), app.getName());
|
const getAppPath = path.join(app.getPath('appData'), app.getName());
|
||||||
|
|
||||||
@@ -360,9 +337,10 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
customCssDialog(): void {
|
customCssDialog() {
|
||||||
const showDialogOptions: OpenDialogOptions = {
|
const showDialogOptions = {
|
||||||
title: 'Select file',
|
title: 'Select file',
|
||||||
|
defaultId: 1,
|
||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
filters: [{ name: 'CSS file', extensions: ['css'] }]
|
filters: [{ name: 'CSS file', extensions: ['css'] }]
|
||||||
};
|
};
|
||||||
@@ -375,14 +353,14 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateResetDataOption(): void {
|
updateResetDataOption() {
|
||||||
const resetDataButton = document.querySelector('#resetdata-option .reset-data-button');
|
const resetDataButton = document.querySelector('#resetdata-option .reset-data-button');
|
||||||
resetDataButton.addEventListener('click', () => {
|
resetDataButton.addEventListener('click', () => {
|
||||||
this.clearAppDataDialog();
|
this.clearAppDataDialog();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
minimizeOnStart(): void {
|
minimizeOnStart() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#start-minimize-option .setting-control'),
|
$element: document.querySelector('#start-minimize-option .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('startMinimized', false),
|
value: ConfigUtil.getConfigItem('startMinimized', false),
|
||||||
@@ -394,50 +372,51 @@ class GeneralSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addCustomCSS(): void {
|
addCustomCSS() {
|
||||||
const customCSSButton = document.querySelector('#add-custom-css .custom-css-button');
|
const customCSSButton = document.querySelector('#add-custom-css .custom-css-button');
|
||||||
customCSSButton.addEventListener('click', () => {
|
customCSSButton.addEventListener('click', () => {
|
||||||
this.customCssDialog();
|
this.customCssDialog();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showCustomCSSPath(): void {
|
showCustomCSSPath() {
|
||||||
if (!ConfigUtil.getConfigItem('customCSS')) {
|
if (!ConfigUtil.getConfigItem('customCSS')) {
|
||||||
const cssPATH: HTMLElement = document.querySelector('#remove-custom-css');
|
const cssPATH = document.getElementById('remove-custom-css');
|
||||||
cssPATH.style.display = 'none';
|
cssPATH.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCustomCSS(): void {
|
removeCustomCSS() {
|
||||||
const removeCSSButton = document.querySelector('#css-delete-action');
|
const removeCSSButton = document.getElementById('css-delete-action');
|
||||||
removeCSSButton.addEventListener('click', () => {
|
removeCSSButton.addEventListener('click', () => {
|
||||||
ConfigUtil.setConfigItem('customCSS', "");
|
ConfigUtil.setConfigItem('customCSS');
|
||||||
ipcRenderer.send('forward-message', 'hard-reload');
|
ipcRenderer.send('forward-message', 'hard-reload');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadFolderDialog(): void {
|
downloadFolderDialog() {
|
||||||
const showDialogOptions: OpenDialogOptions = {
|
const showDialogOptions = {
|
||||||
title: 'Select Download Location',
|
title: 'Select Download Location',
|
||||||
|
defaultId: 1,
|
||||||
properties: ['openDirectory']
|
properties: ['openDirectory']
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.showOpenDialog(showDialogOptions, selectedFolder => {
|
dialog.showOpenDialog(showDialogOptions, selectedFolder => {
|
||||||
if (selectedFolder) {
|
if (selectedFolder) {
|
||||||
ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
|
ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
|
||||||
const downloadFolderPath: HTMLElement = document.querySelector('.download-folder-path');
|
const downloadFolderPath = document.querySelector('.download-folder-path');
|
||||||
downloadFolderPath.innerText = selectedFolder[0];
|
downloadFolderPath.innerText = selectedFolder[0];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
downloadFolder(): void {
|
downloadFolder() {
|
||||||
const downloadFolder = document.querySelector('#download-folder .download-folder-button');
|
const downloadFolder = document.querySelector('#download-folder .download-folder-button');
|
||||||
downloadFolder.addEventListener('click', () => {
|
downloadFolder.addEventListener('click', () => {
|
||||||
this.downloadFolderDialog();
|
this.downloadFolderDialog();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showDownloadFolder(): void {
|
showDownloadFolder() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#show-download-folder .setting-control'),
|
$element: document.querySelector('#show-download-folder .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('showDownloadFolder', false),
|
value: ConfigUtil.getConfigItem('showDownloadFolder', false),
|
||||||
@@ -448,6 +427,7 @@ class GeneralSection extends BaseSection {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export = GeneralSection;
|
module.exports = GeneralSection;
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import BaseComponent = require('../../components/base');
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
|
||||||
class PreferenceNav extends BaseComponent {
|
class PreferenceNav extends BaseComponent {
|
||||||
// TODO: TypeScript - Here props should be object type
|
constructor(props) {
|
||||||
props: any;
|
|
||||||
navItems: string[];
|
|
||||||
$el: Element;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.props = props;
|
this.props = props;
|
||||||
|
|
||||||
this.navItems = ['General', 'Network', 'AddServer', 'Organizations', 'Shortcuts'];
|
this.navItems = ['General', 'Network', 'AddServer', 'Organizations', 'Shortcuts'];
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
let navItemsTemplate = '';
|
let navItemsTemplate = '';
|
||||||
for (const navItem of this.navItems) {
|
for (const navItem of this.navItems) {
|
||||||
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${navItem}</div>`;
|
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${navItem}</div>`;
|
||||||
@@ -28,22 +27,23 @@ class PreferenceNav extends BaseComponent {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.$el = this.generateNodeFromTemplate(this.template());
|
this.$el = this.generateNodeFromTemplate(this.template());
|
||||||
this.props.$root.append(this.$el);
|
this.props.$root.appendChild(this.$el);
|
||||||
|
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerListeners(): void {
|
registerListeners() {
|
||||||
for (const navItem of this.navItems) {
|
for (const navItem of this.navItems) {
|
||||||
const $item = document.querySelector(`#nav-${navItem}`);
|
const $item = document.getElementById(`nav-${navItem}`);
|
||||||
$item.addEventListener('click', () => {
|
$item.addEventListener('click', () => {
|
||||||
this.props.onItemSelected(navItem);
|
this.props.onItemSelected(navItem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select(navItemToSelect: string): void {
|
select(navItemToSelect) {
|
||||||
for (const navItem of this.navItems) {
|
for (const navItem of this.navItems) {
|
||||||
if (navItem === navItemToSelect) {
|
if (navItem === navItemToSelect) {
|
||||||
this.activate(navItem);
|
this.activate(navItem);
|
||||||
@@ -53,15 +53,15 @@ class PreferenceNav extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activate(navItem: string): void {
|
activate(navItem) {
|
||||||
const $item = document.querySelector(`#nav-${navItem}`);
|
const $item = document.getElementById(`nav-${navItem}`);
|
||||||
$item.classList.add('active');
|
$item.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate(navItem: string): void {
|
deactivate(navItem) {
|
||||||
const $item = document.querySelector(`#nav-${navItem}`);
|
const $item = document.getElementById(`nav-${navItem}`);
|
||||||
$item.classList.remove('active');
|
$item.classList.remove('active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = PreferenceNav;
|
module.exports = PreferenceNav;
|
||||||
@@ -1,24 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
import BaseSection = require('./base-section');
|
const BaseSection = require(__dirname + '/base-section.js');
|
||||||
import ConfigUtil = require('../../utils/config-util');
|
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
|
||||||
|
|
||||||
class NetworkSection extends BaseSection {
|
class NetworkSection extends BaseSection {
|
||||||
// TODO: TypeScript - Here props should be object type
|
constructor(props) {
|
||||||
props: any;
|
|
||||||
$proxyPAC: HTMLInputElement;
|
|
||||||
$proxyRules: HTMLInputElement;
|
|
||||||
$proxyBypass: HTMLInputElement;
|
|
||||||
$proxySaveAction: Element;
|
|
||||||
$manualProxyBlock: Element;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
super();
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div class="settings-pane">
|
<div class="settings-pane">
|
||||||
<div class="title">Proxy</div>
|
<div class="title">Proxy</div>
|
||||||
@@ -55,12 +48,12 @@ class NetworkSection extends BaseSection {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.props.$root.innerHTML = this.template();
|
this.props.$root.innerHTML = this.template();
|
||||||
this.$proxyPAC = document.querySelector('#proxy-pac-option .setting-input-value');
|
this.$proxyPAC = document.querySelector('#proxy-pac-option .setting-input-value');
|
||||||
this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
|
this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
|
||||||
this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
|
this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
|
||||||
this.$proxySaveAction = document.querySelector('#proxy-save-action');
|
this.$proxySaveAction = document.getElementById('proxy-save-action');
|
||||||
this.$manualProxyBlock = this.props.$root.querySelector('.manual-proxy-block');
|
this.$manualProxyBlock = this.props.$root.querySelector('.manual-proxy-block');
|
||||||
this.initProxyOption();
|
this.initProxyOption();
|
||||||
|
|
||||||
@@ -77,14 +70,14 @@ class NetworkSection extends BaseSection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initProxyOption(): void {
|
initProxyOption() {
|
||||||
const manualProxyEnabled = ConfigUtil.getConfigItem('useManualProxy', false);
|
const manualProxyEnabled = ConfigUtil.getConfigItem('useManualProxy', false);
|
||||||
this.toggleManualProxySettings(manualProxyEnabled);
|
this.toggleManualProxySettings(manualProxyEnabled);
|
||||||
|
|
||||||
this.updateProxyOption();
|
this.updateProxyOption();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleManualProxySettings(option: boolean): void {
|
toggleManualProxySettings(option) {
|
||||||
if (option) {
|
if (option) {
|
||||||
this.$manualProxyBlock.classList.remove('hidden');
|
this.$manualProxyBlock.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
@@ -92,7 +85,7 @@ class NetworkSection extends BaseSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProxyOption(): void {
|
updateProxyOption() {
|
||||||
this.generateSettingOption({
|
this.generateSettingOption({
|
||||||
$element: document.querySelector('#use-system-settings .setting-control'),
|
$element: document.querySelector('#use-system-settings .setting-control'),
|
||||||
value: ConfigUtil.getConfigItem('useSystemProxy', false),
|
value: ConfigUtil.getConfigItem('useSystemProxy', false),
|
||||||
@@ -132,4 +125,4 @@ class NetworkSection extends BaseSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = NetworkSection;
|
module.exports = NetworkSection;
|
||||||
@@ -1,22 +1,16 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { shell } from 'electron';
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||||
import BaseComponent = require('../../components/base');
|
const shell = require('electron').shell;
|
||||||
import DomainUtil = require('../../utils/domain-util');
|
|
||||||
|
|
||||||
class NewServerForm extends BaseComponent {
|
class NewServerForm extends BaseComponent {
|
||||||
// TODO: TypeScript - Here props should be object type
|
constructor(props) {
|
||||||
props: any;
|
|
||||||
$newServerForm: Element;
|
|
||||||
$saveServerButton: HTMLButtonElement;
|
|
||||||
$newServerUrl: HTMLInputElement;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
super();
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div class="server-input-container">
|
<div class="server-input-container">
|
||||||
<div class="title">Organization URL</div>
|
<div class="title">Organization URL</div>
|
||||||
@@ -42,20 +36,21 @@ class NewServerForm extends BaseComponent {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.initForm();
|
this.initForm();
|
||||||
this.initActions();
|
this.initActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
initForm(): void {
|
initForm() {
|
||||||
this.$newServerForm = this.generateNodeFromTemplate(this.template());
|
this.$newServerForm = this.generateNodeFromTemplate(this.template());
|
||||||
this.$saveServerButton = this.$newServerForm.querySelectorAll('.server-save-action')[0] as HTMLButtonElement;
|
this.$saveServerButton = this.$newServerForm.getElementsByClassName('server-save-action')[0];
|
||||||
this.props.$root.innerHTML = '';
|
this.props.$root.innerHTML = '';
|
||||||
this.props.$root.append(this.$newServerForm);
|
this.props.$root.appendChild(this.$newServerForm);
|
||||||
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
|
|
||||||
|
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.setting-input-value')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
submitFormHandler(): void {
|
submitFormHandler() {
|
||||||
this.$saveServerButton.children[0].innerHTML = 'Connecting...';
|
this.$saveServerButton.children[0].innerHTML = 'Connecting...';
|
||||||
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
|
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
|
||||||
DomainUtil.addDomain(serverConf).then(() => {
|
DomainUtil.addDomain(serverConf).then(() => {
|
||||||
@@ -67,15 +62,15 @@ class NewServerForm extends BaseComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openCreateNewOrgExternalLink(): void {
|
openCreateNewOrgExternalLink() {
|
||||||
const link = 'https://zulipchat.com/new/';
|
const link = 'https://zulipchat.com/new/';
|
||||||
const externalCreateNewOrgEl = document.querySelector('#open-create-org-link');
|
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
|
||||||
externalCreateNewOrgEl.addEventListener('click', () => {
|
externalCreateNewOrgEl.addEventListener('click', () => {
|
||||||
shell.openExternal(link);
|
shell.openExternal(link);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initActions(): void {
|
initActions() {
|
||||||
this.$saveServerButton.addEventListener('click', () => {
|
this.$saveServerButton.addEventListener('click', () => {
|
||||||
this.submitFormHandler();
|
this.submitFormHandler();
|
||||||
});
|
});
|
||||||
@@ -91,4 +86,4 @@ class NewServerForm extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = NewServerForm;
|
module.exports = NewServerForm;
|
||||||
@@ -1,29 +1,24 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
const BaseComponent = require(__dirname + '/js/components/base.js');
|
||||||
|
const { ipcRenderer } = require('electron');
|
||||||
|
|
||||||
import BaseComponent = require('../../components/base');
|
const Nav = require(__dirname + '/js/pages/preference/nav.js');
|
||||||
import Nav = require('./nav');
|
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
|
||||||
import ServersSection = require('./servers-section');
|
const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js');
|
||||||
import GeneralSection = require('./general-section');
|
const NetworkSection = require(__dirname + '/js/pages/preference/network-section.js');
|
||||||
import NetworkSection = require('./network-section');
|
const ConnectedOrgSection = require(__dirname + '/js/pages/preference/connected-org-section.js');
|
||||||
import ConnectedOrgSection = require('./connected-org-section');
|
const ShortcutsSection = require(__dirname + '/js/pages/preference/shortcuts-section.js');
|
||||||
import ShortcutsSection = require('./shortcuts-section');
|
|
||||||
|
|
||||||
type Section = ServersSection | GeneralSection | NetworkSection | ConnectedOrgSection | ShortcutsSection;
|
|
||||||
|
|
||||||
class PreferenceView extends BaseComponent {
|
class PreferenceView extends BaseComponent {
|
||||||
$sidebarContainer: Element;
|
|
||||||
$settingsContainer: Element;
|
|
||||||
nav: Nav;
|
|
||||||
section: Section;
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.$sidebarContainer = document.querySelector('#sidebar');
|
|
||||||
this.$settingsContainer = document.querySelector('#settings-container');
|
this.$sidebarContainer = document.getElementById('sidebar');
|
||||||
|
this.$settingsContainer = document.getElementById('settings-container');
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.nav = new Nav({
|
this.nav = new Nav({
|
||||||
$root: this.$sidebarContainer,
|
$root: this.$sidebarContainer,
|
||||||
onItemSelected: this.handleNavigation.bind(this)
|
onItemSelected: this.handleNavigation.bind(this)
|
||||||
@@ -33,7 +28,7 @@ class PreferenceView extends BaseComponent {
|
|||||||
this.registerIpcs();
|
this.registerIpcs();
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultView(): void {
|
setDefaultView() {
|
||||||
let nav = 'General';
|
let nav = 'General';
|
||||||
const hasTag = window.location.hash;
|
const hasTag = window.location.hash;
|
||||||
if (hasTag) {
|
if (hasTag) {
|
||||||
@@ -42,7 +37,7 @@ class PreferenceView extends BaseComponent {
|
|||||||
this.handleNavigation(nav);
|
this.handleNavigation(nav);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNavigation(navItem: string): void {
|
handleNavigation(navItem) {
|
||||||
this.nav.select(navItem);
|
this.nav.select(navItem);
|
||||||
switch (navItem) {
|
switch (navItem) {
|
||||||
case 'AddServer': {
|
case 'AddServer': {
|
||||||
@@ -82,32 +77,28 @@ class PreferenceView extends BaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle toggling and reflect changes in preference page
|
// Handle toggling and reflect changes in preference page
|
||||||
handleToggle(elementName: string, state: boolean): void {
|
handleToggle(elementName, state) {
|
||||||
const inputSelector = `#${elementName} .action .switch input`;
|
const inputSelector = `#${elementName} .action .switch input`;
|
||||||
const input: HTMLInputElement = document.querySelector(inputSelector);
|
const input = document.querySelector(inputSelector);
|
||||||
if (input) {
|
if (input) {
|
||||||
input.checked = state;
|
input.checked = state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerIpcs(): void {
|
registerIpcs() {
|
||||||
ipcRenderer.on('switch-settings-nav', (_event: Event, navItem: string) => {
|
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
|
||||||
this.handleNavigation(navItem);
|
this.handleNavigation(navItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-sidebar-setting', (_event: Event, state: boolean) => {
|
ipcRenderer.on('toggle-sidebar-setting', (event, state) => {
|
||||||
this.handleToggle('sidebar-option', state);
|
this.handleToggle('sidebar-option', state);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-menubar-setting', (_event: Event, state: boolean) => {
|
ipcRenderer.on('toggletray', (event, state) => {
|
||||||
this.handleToggle('menubar-option', state);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('toggletray', (_event: Event, state: boolean) => {
|
|
||||||
this.handleToggle('tray-option', state);
|
this.handleToggle('tray-option', state);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-dnd', (_event: Event, _state: boolean, newSettings: any) => {
|
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
|
||||||
this.handleToggle('show-notification-option', newSettings.showNotification);
|
this.handleToggle('show-notification-option', newSettings.showNotification);
|
||||||
this.handleToggle('silent-option', newSettings.silent);
|
this.handleToggle('silent-option', newSettings.silent);
|
||||||
|
|
||||||
@@ -118,9 +109,7 @@ class PreferenceView extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.onload = () => {
|
||||||
const preferenceView = new PreferenceView();
|
const preferenceView = new PreferenceView();
|
||||||
preferenceView.init();
|
preferenceView.init();
|
||||||
});
|
};
|
||||||
|
|
||||||
export = PreferenceView;
|
|
||||||
@@ -1,26 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
const { dialog } = require('electron').remote;
|
||||||
|
const { ipcRenderer } = require('electron');
|
||||||
|
|
||||||
import { remote, ipcRenderer } from 'electron';
|
const BaseComponent = require(__dirname + '/../../components/base.js');
|
||||||
|
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
|
||||||
import BaseComponent = require('../../components/base');
|
|
||||||
import DomainUtil = require('../../utils/domain-util');
|
|
||||||
|
|
||||||
const { dialog } = remote;
|
|
||||||
|
|
||||||
class ServerInfoForm extends BaseComponent {
|
class ServerInfoForm extends BaseComponent {
|
||||||
// TODO: TypeScript - Here props should be object type
|
constructor(props) {
|
||||||
props: any;
|
|
||||||
$serverInfoForm: Element;
|
|
||||||
$serverInfoAlias: Element;
|
|
||||||
$serverIcon: Element;
|
|
||||||
$deleteServerButton: Element;
|
|
||||||
$openServerButton: Element;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
super();
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<div class="server-info-left">
|
<div class="server-info-left">
|
||||||
@@ -44,21 +35,21 @@ class ServerInfoForm extends BaseComponent {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.initForm();
|
this.initForm();
|
||||||
this.initActions();
|
this.initActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
initForm(): void {
|
initForm() {
|
||||||
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
|
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
|
||||||
this.$serverInfoAlias = this.$serverInfoForm.querySelectorAll('.server-info-alias')[0];
|
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0];
|
||||||
this.$serverIcon = this.$serverInfoForm.querySelectorAll('.server-info-icon')[0];
|
this.$serverIcon = this.$serverInfoForm.getElementsByClassName('server-info-icon')[0];
|
||||||
this.$deleteServerButton = this.$serverInfoForm.querySelectorAll('.server-delete-action')[0];
|
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
|
||||||
this.$openServerButton = this.$serverInfoForm.querySelectorAll('.open-tab-button')[0];
|
this.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0];
|
||||||
this.props.$root.append(this.$serverInfoForm);
|
this.props.$root.appendChild(this.$serverInfoForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
initActions(): void {
|
initActions() {
|
||||||
this.$deleteServerButton.addEventListener('click', () => {
|
this.$deleteServerButton.addEventListener('click', () => {
|
||||||
dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -85,6 +76,7 @@ class ServerInfoForm extends BaseComponent {
|
|||||||
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
|
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export = ServerInfoForm;
|
module.exports = ServerInfoForm;
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import BaseSection = require('./base-section');
|
const BaseSection = require(__dirname + '/base-section.js');
|
||||||
import NewServerForm = require('./new-server-form');
|
const NewServerForm = require(__dirname + '/new-server-form.js');
|
||||||
|
|
||||||
class ServersSection extends BaseSection {
|
class ServersSection extends BaseSection {
|
||||||
// TODO: TypeScript - Here props should be object type
|
constructor(props) {
|
||||||
props: any;
|
|
||||||
$newServerContainer: Element;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
super();
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
template(): string {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div class="add-server-modal">
|
<div class="add-server-modal">
|
||||||
<div class="modal-container">
|
<div class="modal-container">
|
||||||
@@ -25,20 +22,20 @@ class ServersSection extends BaseSection {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init() {
|
||||||
this.initServers();
|
this.initServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
initServers(): void {
|
initServers() {
|
||||||
this.props.$root.innerHTML = '';
|
this.props.$root.innerHTML = '';
|
||||||
|
|
||||||
this.props.$root.innerHTML = this.template();
|
this.props.$root.innerHTML = this.template();
|
||||||
this.$newServerContainer = document.querySelector('#new-server-container');
|
this.$newServerContainer = document.getElementById('new-server-container');
|
||||||
|
|
||||||
this.initNewServerForm();
|
this.initNewServerForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
initNewServerForm(): void {
|
initNewServerForm() {
|
||||||
new NewServerForm({
|
new NewServerForm({
|
||||||
$root: this.$newServerContainer,
|
$root: this.$newServerContainer,
|
||||||
onChange: this.reloadApp
|
onChange: this.reloadApp
|
||||||
@@ -46,4 +43,4 @@ class ServersSection extends BaseSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = ServersSection;
|
module.exports = ServersSection;
|
||||||
322
app/renderer/js/pages/preference/shortcuts-section.js
Normal file
322
app/renderer/js/pages/preference/shortcuts-section.js
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseSection = require(__dirname + '/base-section.js');
|
||||||
|
const shell = require('electron').shell;
|
||||||
|
|
||||||
|
class ShortcutsSection extends BaseSection {
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
templateMac() {
|
||||||
|
const userOSKey = '⌘';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="settings-pane">
|
||||||
|
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link"> keyboard shortcuts</span>.</p></div>
|
||||||
|
<div class="title">Application Shortcuts</div>
|
||||||
|
<div class="settings-card">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>,</kbd></td>
|
||||||
|
<td>Settings</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
|
||||||
|
<td>Keyboard Shortcuts</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
|
||||||
|
<td>Toggle Do Not Disturb</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
|
||||||
|
<td>Reset App Settings</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>L</kbd></td>
|
||||||
|
<td>Log Out</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
|
||||||
|
<td>Hide Zulip</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
|
||||||
|
<td>Hide Others</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>Q</kbd></td>
|
||||||
|
<td>Quit Zulip</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="title">Edit Shortcuts</div>
|
||||||
|
<div class="settings-card">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
|
||||||
|
<td>Undo</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
|
||||||
|
<td>Redo</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>X</kbd></td>
|
||||||
|
<td>Cut</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>C</kbd></td>
|
||||||
|
<td>Copy</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
|
||||||
|
<td>Paste</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
|
||||||
|
<td>Paste and Match Style</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
|
||||||
|
<td>Select All</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
|
||||||
|
<td>Emoji & Symbols</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="title">View Shortcuts</div>
|
||||||
|
<div class="settings-card">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
|
||||||
|
<td>Reload</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
|
||||||
|
<td>Hard Reload</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
|
||||||
|
<td>Enter Full Screen</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>+</kbd></td>
|
||||||
|
<td>Zoom In</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>-</kbd></td>
|
||||||
|
<td>Zoom Out</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>0</kbd></td>
|
||||||
|
<td>Actual Size</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
|
||||||
|
<td>Toggle Sidebar</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>I</kbd></td>
|
||||||
|
<td>Toggle DevTools for Zulip App</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>U</kbd></td>
|
||||||
|
<td>Toggle DevTools for Active Tab</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="title">History Shortcuts</div>
|
||||||
|
<div class="settings-card">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>←</kbd></td>
|
||||||
|
<td>Back</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>→</kbd></td>
|
||||||
|
<td>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>Minimize</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd><kbd>W</kbd></td>
|
||||||
|
<td>Close</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
templateWinLin() {
|
||||||
|
const userOSKey = 'Ctrl';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="settings-pane">
|
||||||
|
<div class="title">Application Shortcuts</div>
|
||||||
|
<div class="settings-card">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>,</kbd></td>
|
||||||
|
<td>Settings</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
|
||||||
|
<td>Keyboard Shortcuts</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
|
||||||
|
<td>Toggle Do Not Disturb</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
|
||||||
|
<td>Log Out</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Q</kbd></td>
|
||||||
|
<td>Quit Zulip</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="title">Edit Shortcuts</div>
|
||||||
|
<div class="settings-card">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Z</kbd></td>
|
||||||
|
<td>Undo</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Y</kbd></td>
|
||||||
|
<td>Redo</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>X</kbd></td>
|
||||||
|
<td>Cut</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>C</kbd></td>
|
||||||
|
<td>Copy</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>V</kbd></td>
|
||||||
|
<td>Paste</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd></td>
|
||||||
|
<td>Paste and Match Style</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>A</kbd></td>
|
||||||
|
<td>Select All</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="title">View Shortcuts</div>
|
||||||
|
<div class="settings-card">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>R</kbd></td>
|
||||||
|
<td>Reload</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd></td>
|
||||||
|
<td>Hard Reload</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>F11</kbd></td>
|
||||||
|
<td>Toggle Full Screen</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>=</kbd></td>
|
||||||
|
<td>Zoom In</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>-</kbd></td>
|
||||||
|
<td>Zoom Out</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>0</kbd></td>
|
||||||
|
<td>Actual Size</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
|
||||||
|
<td>Toggle Sidebar</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td>
|
||||||
|
<td>Toggle DevTools for Zulip App</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td>
|
||||||
|
<td>Toggle DevTools for Active Tab</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="title">History Shortcuts</div>
|
||||||
|
<div class="settings-card">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Alt</kbd> + <kbd>←</kbd></td>
|
||||||
|
<td>Back</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Alt</kbd> + <kbd>→</kbd></td>
|
||||||
|
<td>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>Minimize</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>${userOSKey}</kbd> + <kbd>W</kbd></td>
|
||||||
|
<td>Close</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="setting-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="tip"><b><i class="material-icons md-14">lightbulb_outline</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
openHotkeysExternalLink() {
|
||||||
|
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
|
||||||
|
const externalCreateNewOrgEl = document.getElementById('open-hotkeys-link');
|
||||||
|
externalCreateNewOrgEl.addEventListener('click', () => {
|
||||||
|
shell.openExternal(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.props.$root.innerHTML = (process.platform === 'darwin') ?
|
||||||
|
this.templateMac() : this.templateWinLin();
|
||||||
|
this.openHotkeysExternalLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ShortcutsSection;
|
||||||
@@ -1,344 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import { shell } from 'electron';
|
|
||||||
|
|
||||||
import BaseSection = require('./base-section');
|
|
||||||
|
|
||||||
class ShortcutsSection extends BaseSection {
|
|
||||||
// TODO: TypeScript - Here props should be object type
|
|
||||||
props: any;
|
|
||||||
constructor(props: any) {
|
|
||||||
super();
|
|
||||||
this.props = props;
|
|
||||||
}
|
|
||||||
// TODO - Deduplicate templateMac and templateWinLin 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 +.
|
|
||||||
templateMac(): string {
|
|
||||||
const userOSKey = '⌘';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="settings-pane">
|
|
||||||
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link"> keyboard shortcuts</span>.</p></div>
|
|
||||||
<div class="title">Application Shortcuts</div>
|
|
||||||
<div class="settings-card">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>,</kbd></td>
|
|
||||||
<td>Settings</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
|
|
||||||
<td>Keyboard Shortcuts</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
|
|
||||||
<td>Toggle Do Not Disturb</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
|
|
||||||
<td>Reset App Settings</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>L</kbd></td>
|
|
||||||
<td>Log Out</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
|
|
||||||
<td>Hide Zulip</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
|
|
||||||
<td>Hide Others</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>Q</kbd></td>
|
|
||||||
<td>Quit Zulip</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
<div class="title">Edit Shortcuts</div>
|
|
||||||
<div class="settings-card">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
|
|
||||||
<td>Undo</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
|
|
||||||
<td>Redo</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>X</kbd></td>
|
|
||||||
<td>Cut</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>C</kbd></td>
|
|
||||||
<td>Copy</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
|
|
||||||
<td>Paste</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
|
|
||||||
<td>Paste and Match Style</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
|
|
||||||
<td>Select All</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
|
|
||||||
<td>Emoji & Symbols</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
<div class="title">View Shortcuts</div>
|
|
||||||
<div class="settings-card">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
|
|
||||||
<td>Reload</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
|
|
||||||
<td>Hard Reload</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
|
|
||||||
<td>Enter Full Screen</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>+</kbd></td>
|
|
||||||
<td>Zoom In</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>-</kbd></td>
|
|
||||||
<td>Zoom Out</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>0</kbd></td>
|
|
||||||
<td>Actual Size</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
|
|
||||||
<td>Toggle Sidebar</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>I</kbd></td>
|
|
||||||
<td>Toggle DevTools for Zulip App</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>U</kbd></td>
|
|
||||||
<td>Toggle DevTools for Active Tab</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Ctrl</kbd> + <kbd>Tab</kbd></td>
|
|
||||||
<td>Switch to Next Organization</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
|
|
||||||
<td>Switch to Previous Organization</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
<div class="title">History Shortcuts</div>
|
|
||||||
<div class="settings-card">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>←</kbd></td>
|
|
||||||
<td>Back</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>→</kbd></td>
|
|
||||||
<td>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>Minimize</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd><kbd>W</kbd></td>
|
|
||||||
<td>Close</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
templateWinLin(): string {
|
|
||||||
const userOSKey = 'Ctrl';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="settings-pane">
|
|
||||||
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link"> keyboard shortcuts</span>.</p></div>
|
|
||||||
<div class="title">Application Shortcuts</div>
|
|
||||||
<div class="settings-card">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>,</kbd></td>
|
|
||||||
<td>Settings</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
|
|
||||||
<td>Keyboard Shortcuts</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
|
|
||||||
<td>Toggle Do Not Disturb</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
|
|
||||||
<td>Log Out</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Q</kbd></td>
|
|
||||||
<td>Quit Zulip</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
<div class="title">Edit Shortcuts</div>
|
|
||||||
<div class="settings-card">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Z</kbd></td>
|
|
||||||
<td>Undo</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Y</kbd></td>
|
|
||||||
<td>Redo</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>X</kbd></td>
|
|
||||||
<td>Cut</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>C</kbd></td>
|
|
||||||
<td>Copy</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>V</kbd></td>
|
|
||||||
<td>Paste</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd></td>
|
|
||||||
<td>Paste and Match Style</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>A</kbd></td>
|
|
||||||
<td>Select All</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
<div class="title">View Shortcuts</div>
|
|
||||||
<div class="settings-card">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>R</kbd></td>
|
|
||||||
<td>Reload</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd></td>
|
|
||||||
<td>Hard Reload</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>F11</kbd></td>
|
|
||||||
<td>Toggle Full Screen</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>=</kbd></td>
|
|
||||||
<td>Zoom In</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>-</kbd></td>
|
|
||||||
<td>Zoom Out</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>0</kbd></td>
|
|
||||||
<td>Actual Size</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
|
|
||||||
<td>Toggle Sidebar</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td>
|
|
||||||
<td>Toggle DevTools for Zulip App</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td>
|
|
||||||
<td>Toggle DevTools for Active Tab</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Tab</kbd></td>
|
|
||||||
<td>Switch to Next Organization</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
|
|
||||||
<td>Switch to Previous Organization</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
<div class="title">History Shortcuts</div>
|
|
||||||
<div class="settings-card">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Alt</kbd> + <kbd>←</kbd></td>
|
|
||||||
<td>Back</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>Alt</kbd> + <kbd>→</kbd></td>
|
|
||||||
<td>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>Minimize</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>${userOSKey}</kbd> + <kbd>W</kbd></td>
|
|
||||||
<td>Close</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="setting-control"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
openHotkeysExternalLink(): void {
|
|
||||||
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
|
|
||||||
const externalCreateNewOrgEl = document.querySelector('#open-hotkeys-link');
|
|
||||||
externalCreateNewOrgEl.addEventListener('click', () => {
|
|
||||||
shell.openExternal(link);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
init(): void {
|
|
||||||
this.props.$root.innerHTML = (process.platform === 'darwin') ?
|
|
||||||
this.templateMac() : this.templateWinLin();
|
|
||||||
this.openHotkeysExternalLink();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export = ShortcutsSection;
|
|
||||||
@@ -1,17 +1,11 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ipcRenderer, shell } from 'electron';
|
const { ipcRenderer, shell } = require('electron');
|
||||||
import SetupSpellChecker from './spellchecker';
|
const SetupSpellChecker = require('./spellchecker');
|
||||||
|
|
||||||
import LinkUtil = require('./utils/link-util');
|
const ConfigUtil = require(__dirname + '/utils/config-util.js');
|
||||||
import params = require('./utils/params-util');
|
const LinkUtil = require(__dirname + '/utils/link-util.js');
|
||||||
|
const params = require(__dirname + '/utils/params-util.js');
|
||||||
interface PatchedGlobal extends NodeJS.Global {
|
|
||||||
logout: () => void;
|
|
||||||
shortcut: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalPatched = global as PatchedGlobal;
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
require('./notification');
|
require('./notification');
|
||||||
@@ -19,46 +13,44 @@ require('./notification');
|
|||||||
// Prevent drag and drop event in main process which prevents remote code executaion
|
// Prevent drag and drop event in main process which prevents remote code executaion
|
||||||
require(__dirname + '/shared/preventdrag.js');
|
require(__dirname + '/shared/preventdrag.js');
|
||||||
|
|
||||||
declare let window: ZulipWebWindow;
|
// eslint-disable-next-line camelcase
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
|
||||||
window.electron_bridge = require('./electron-bridge');
|
window.electron_bridge = require('./electron-bridge');
|
||||||
|
|
||||||
const logout = (): void => {
|
const logout = () => {
|
||||||
// Create the menu for the below
|
// Create the menu for the below
|
||||||
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
|
document.querySelector('.dropdown-toggle').click();
|
||||||
dropdown.click();
|
|
||||||
|
|
||||||
const nodes: NodeListOf<HTMLElement> = document.querySelectorAll('.dropdown-menu li:last-child a');
|
const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
|
||||||
nodes[nodes.length - 1].click();
|
nodes[nodes.length - 1].click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const shortcut = (): void => {
|
const shortcut = () => {
|
||||||
// Create the menu for the below
|
// Create the menu for the below
|
||||||
const node: HTMLElement = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
|
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
|
||||||
// Additional check
|
// Additional check
|
||||||
if (node.textContent.trim().toLowerCase() === 'keyboard shortcuts (?)') {
|
if (node.text.trim().toLowerCase() === 'keyboard shortcuts (?)') {
|
||||||
node.click();
|
node.click();
|
||||||
} else {
|
} else {
|
||||||
// Atleast click the dropdown
|
// Atleast click the dropdown
|
||||||
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
|
document.querySelector('.dropdown-toggle').click();
|
||||||
dropdown.click();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
process.once('loaded', (): void => {
|
process.once('loaded', () => {
|
||||||
globalPatched.logout = logout;
|
global.logout = logout;
|
||||||
globalPatched.shortcut = shortcut;
|
global.shortcut = shortcut;
|
||||||
});
|
});
|
||||||
|
|
||||||
// To prevent failing this script on linux we need to load it after the document loaded
|
// To prevent failing this script on linux we need to load it after the document loaded
|
||||||
document.addEventListener('DOMContentLoaded', (): void => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
if (params.isPageParams()) {
|
if (params.isPageParams()) {
|
||||||
// Get the default language of the server
|
// Get the default language of the server
|
||||||
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, @typescript-eslint/camelcase
|
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
|
||||||
if (serverLanguage) {
|
if (serverLanguage) {
|
||||||
|
// Set spellcheker language
|
||||||
|
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
|
||||||
// Init spellchecker
|
// Init spellchecker
|
||||||
SetupSpellChecker.init(serverLanguage);
|
SetupSpellChecker.init();
|
||||||
}
|
}
|
||||||
// redirect users to network troubleshooting page
|
// redirect users to network troubleshooting page
|
||||||
const getRestartButton = document.querySelector('.restart_get_events_button');
|
const getRestartButton = document.querySelector('.restart_get_events_button');
|
||||||
@@ -69,7 +61,7 @@ document.addEventListener('DOMContentLoaded', (): void => {
|
|||||||
}
|
}
|
||||||
// Open image attachment link in the lightbox instead of opening in the default browser
|
// Open image attachment link in the lightbox instead of opening in the default browser
|
||||||
const { $, lightbox } = window;
|
const { $, lightbox } = window;
|
||||||
$('#main_div').on('click', '.message_content p a', function (this: HTMLElement, e: Event) {
|
$('#main_div').on('click', '.message_content p a', function (e) {
|
||||||
const url = $(this).attr('href');
|
const url = $(this).attr('href');
|
||||||
|
|
||||||
if (LinkUtil.isImage(url)) {
|
if (LinkUtil.isImage(url)) {
|
||||||
@@ -93,21 +85,14 @@ document.addEventListener('DOMContentLoaded', (): void => {
|
|||||||
|
|
||||||
// Clean up spellchecker events after you navigate away from this page;
|
// Clean up spellchecker events after you navigate away from this page;
|
||||||
// otherwise, you may experience errors
|
// otherwise, you may experience errors
|
||||||
window.addEventListener('beforeunload', (): void => {
|
window.addEventListener('beforeunload', () => {
|
||||||
SetupSpellChecker.unsubscribeSpellChecker();
|
SetupSpellChecker.unsubscribeSpellChecker();
|
||||||
});
|
});
|
||||||
|
|
||||||
// electron's globalShortcut can cause unexpected results
|
// electron's globalShortcut can cause unexpected results
|
||||||
// so adding the reload shortcut in the old-school way
|
// 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 => {
|
document.addEventListener('keydown', event => {
|
||||||
if (event.code === 'F5') {
|
if (event.code === 'F5') {
|
||||||
ipcRenderer.send('forward-message', 'hard-reload');
|
ipcRenderer.send('forward-message', 'hard-reload');
|
||||||
} else if (event.ctrlKey && event.code === 'NumpadAdd') {
|
|
||||||
ipcRenderer.send('forward-message', 'zoomIn');
|
|
||||||
} else if (event.ctrlKey && event.code === 'NumpadSubtract') {
|
|
||||||
ipcRenderer.send('forward-message', 'zoomOut');
|
|
||||||
} else if (event.ctrlKey && event.code === 'Numpad0') {
|
|
||||||
ipcRenderer.send('forward-message', 'zoomActualSize');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
// It doesn't affect the compose box so that users can still
|
// It doesn't affect the compose box so that users can still
|
||||||
// use drag and drop event to share files etc
|
// use drag and drop event to share files etc
|
||||||
|
|
||||||
const preventDragAndDrop = (): void => {
|
const preventDragAndDrop = () => {
|
||||||
const preventEvents = ['dragover', 'drop'];
|
const preventEvents = ['dragover', 'drop'];
|
||||||
preventEvents.forEach(dragEvents => {
|
preventEvents.forEach(dragEvents => {
|
||||||
document.addEventListener(dragEvents, event => {
|
document.addEventListener(dragEvents, event => {
|
||||||
62
app/renderer/js/spellchecker.js
Normal file
62
app/renderer/js/spellchecker.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
|
||||||
|
|
||||||
|
const ConfigUtil = require(__dirname + '/utils/config-util.js');
|
||||||
|
const Logger = require(__dirname + '/utils/logger-util.js');
|
||||||
|
|
||||||
|
const logger = new Logger({
|
||||||
|
file: 'errors.log',
|
||||||
|
timestamp: true
|
||||||
|
});
|
||||||
|
|
||||||
|
class SetupSpellChecker {
|
||||||
|
init() {
|
||||||
|
if (ConfigUtil.getConfigItem('enableSpellchecker')) {
|
||||||
|
this.enableSpellChecker();
|
||||||
|
}
|
||||||
|
this.enableContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
enableSpellChecker() {
|
||||||
|
try {
|
||||||
|
this.SpellCheckHandler = new SpellCheckHandler();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableContextMenu() {
|
||||||
|
if (this.SpellCheckHandler) {
|
||||||
|
this.SpellCheckHandler.attachToInput();
|
||||||
|
|
||||||
|
const userLanguage = ConfigUtil.getConfigItem('spellcheckerLanguage');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
process.platform === 'darwin' ?
|
||||||
|
// On macOS, spellchecker fails to auto-detect the lanugage user is typing in
|
||||||
|
// that's why we need to mention it explicitly
|
||||||
|
this.SpellCheckHandler.switchLanguage(userLanguage) :
|
||||||
|
// On Linux and Windows, spellchecker can automatically detects the language the user is typing in
|
||||||
|
// and silently switches on the fly; thus we can start off as US English
|
||||||
|
this.SpellCheckHandler.switchLanguage('en-US');
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler);
|
||||||
|
this.contextMenuListener = new ContextMenuListener(info => {
|
||||||
|
contextMenuBuilder.showPopupMenu(info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribeSpellChecker() {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
if (this.SpellCheckHandler) {
|
||||||
|
this.SpellCheckHandler.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.contextMenuListener) {
|
||||||
|
this.contextMenuListener.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new SetupSpellChecker();
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } from 'electron-spellchecker';
|
|
||||||
|
|
||||||
import ConfigUtil = require('./utils/config-util');
|
|
||||||
import Logger = require('./utils/logger-util');
|
|
||||||
|
|
||||||
const logger = new Logger({
|
|
||||||
file: 'errors.log',
|
|
||||||
timestamp: true
|
|
||||||
});
|
|
||||||
|
|
||||||
class SetupSpellChecker {
|
|
||||||
SpellCheckHandler: typeof SpellCheckHandler;
|
|
||||||
contextMenuListener: typeof ContextMenuListener;
|
|
||||||
init(serverLanguage: string): void {
|
|
||||||
if (ConfigUtil.getConfigItem('enableSpellchecker')) {
|
|
||||||
this.enableSpellChecker();
|
|
||||||
}
|
|
||||||
this.enableContextMenu(serverLanguage);
|
|
||||||
}
|
|
||||||
|
|
||||||
enableSpellChecker(): void {
|
|
||||||
try {
|
|
||||||
this.SpellCheckHandler = new SpellCheckHandler();
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enableContextMenu(serverLanguage: string): void {
|
|
||||||
if (this.SpellCheckHandler) {
|
|
||||||
this.SpellCheckHandler.attachToInput();
|
|
||||||
this.SpellCheckHandler.switchLanguage(serverLanguage);
|
|
||||||
this.SpellCheckHandler.currentSpellcheckerChanged.subscribe(() => {
|
|
||||||
this.SpellCheckHandler.switchLanguage(this.SpellCheckHandler.currentSpellcheckerLanguage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler);
|
|
||||||
this.contextMenuListener = new ContextMenuListener((info: object) => {
|
|
||||||
contextMenuBuilder.showPopupMenu(info);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribeSpellChecker(): void {
|
|
||||||
if (this.SpellCheckHandler) {
|
|
||||||
this.SpellCheckHandler.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.contextMenuListener) {
|
|
||||||
this.contextMenuListener.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export = new SetupSpellChecker();
|
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { ipcRenderer, remote, WebviewTag, NativeImage } from 'electron';
|
const path = require('path');
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const { ipcRenderer, remote } = electron;
|
||||||
|
|
||||||
import path = require('path');
|
|
||||||
import ConfigUtil = require('./utils/config-util.js');
|
|
||||||
const { Tray, Menu, nativeImage, BrowserWindow } = remote;
|
const { Tray, Menu, nativeImage, BrowserWindow } = remote;
|
||||||
|
|
||||||
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
|
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
|
||||||
|
|
||||||
declare let window: ZulipWebWindow;
|
const ConfigUtil = require(__dirname + '/utils/config-util.js');
|
||||||
|
|
||||||
const iconPath = (): string => {
|
const iconPath = () => {
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
return APP_ICON + 'linux.png';
|
return APP_ICON + 'linux.png';
|
||||||
}
|
}
|
||||||
@@ -18,7 +20,7 @@ const iconPath = (): string => {
|
|||||||
|
|
||||||
let unread = 0;
|
let unread = 0;
|
||||||
|
|
||||||
const trayIconSize = (): number => {
|
const trayIconSize = () => {
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case 'darwin':
|
case 'darwin':
|
||||||
return 20;
|
return 20;
|
||||||
@@ -43,7 +45,7 @@ const config = {
|
|||||||
thick: process.platform === 'win32'
|
thick: process.platform === 'win32'
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCanvas = function (arg: number): Promise<HTMLCanvasElement> {
|
const renderCanvas = function (arg) {
|
||||||
config.unreadCount = arg;
|
config.unreadCount = arg;
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
@@ -77,10 +79,10 @@ const renderCanvas = function (arg: number): Promise<HTMLCanvasElement> {
|
|||||||
ctx.fillText('99+', CENTER, CENTER + (SIZE * 0.15));
|
ctx.fillText('99+', CENTER, CENTER + (SIZE * 0.15));
|
||||||
} else if (config.unreadCount < 10) {
|
} else if (config.unreadCount < 10) {
|
||||||
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
|
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
|
||||||
ctx.fillText(String(config.unreadCount), CENTER, CENTER + (SIZE * 0.20));
|
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.20));
|
||||||
} else {
|
} else {
|
||||||
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
|
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
|
||||||
ctx.fillText(String(config.unreadCount), CENTER, CENTER + (SIZE * 0.15));
|
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.15));
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(canvas);
|
resolve(canvas);
|
||||||
@@ -92,23 +94,18 @@ const renderCanvas = function (arg: number): Promise<HTMLCanvasElement> {
|
|||||||
* @param arg: Unread count
|
* @param arg: Unread count
|
||||||
* @return the native image
|
* @return the native image
|
||||||
*/
|
*/
|
||||||
const renderNativeImage = function (arg: number): Promise<NativeImage> {
|
const renderNativeImage = function (arg) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => renderCanvas(arg))
|
.then(() => renderCanvas(arg))
|
||||||
.then(canvas => {
|
.then(canvas => {
|
||||||
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPNG();
|
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPNG();
|
||||||
|
|
||||||
// TODO: Fix the function to correctly use Promise correctly.
|
|
||||||
// the Promise.resolve().then(...) above is useless we should
|
|
||||||
// start with renderCanvas(arg).then
|
|
||||||
// eslint-disable-next-line promise/no-return-wrap
|
|
||||||
return Promise.resolve(nativeImage.createFromBuffer(pngData, {
|
return Promise.resolve(nativeImage.createFromBuffer(pngData, {
|
||||||
scaleFactor: config.pixelRatio
|
scaleFactor: config.pixelRatio
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function sendAction(action: string): void {
|
function sendAction(action) {
|
||||||
const win = BrowserWindow.getAllWindows()[0];
|
const win = BrowserWindow.getAllWindows()[0];
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
@@ -118,7 +115,7 @@ function sendAction(action: string): void {
|
|||||||
win.webContents.send(action);
|
win.webContents.send(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
const createTray = function (): void {
|
const createTray = function () {
|
||||||
window.tray = new Tray(iconPath());
|
window.tray = new Tray(iconPath());
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
@@ -152,9 +149,9 @@ const createTray = function (): void {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.on('destroytray', (event: Event): Event => {
|
ipcRenderer.on('destroytray', event => {
|
||||||
if (!window.tray) {
|
if (!window.tray) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.tray.destroy();
|
window.tray.destroy();
|
||||||
@@ -167,7 +164,7 @@ ipcRenderer.on('destroytray', (event: Event): Event => {
|
|||||||
return event;
|
return event;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('tray', (_event: Event, arg: number): void => {
|
ipcRenderer.on('tray', (event, arg) => {
|
||||||
if (!window.tray) {
|
if (!window.tray) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -187,7 +184,7 @@ ipcRenderer.on('tray', (_event: Event, arg: number): void => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function toggleTray(): void {
|
function toggleTray() {
|
||||||
let state;
|
let state;
|
||||||
if (window.tray) {
|
if (window.tray) {
|
||||||
state = false;
|
state = false;
|
||||||
@@ -208,7 +205,7 @@ function toggleTray(): void {
|
|||||||
ConfigUtil.setConfigItem('trayIcon', true);
|
ConfigUtil.setConfigItem('trayIcon', true);
|
||||||
}
|
}
|
||||||
const selector = 'webview:not([class*=disabled])';
|
const selector = 'webview:not([class*=disabled])';
|
||||||
const webview: WebviewTag = document.querySelector(selector);
|
const webview = document.querySelector(selector);
|
||||||
const webContents = webview.getWebContents();
|
const webContents = webview.getWebContents();
|
||||||
webContents.send('toggletray', state);
|
webContents.send('toggletray', state);
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { remote } from 'electron';
|
const { app, dialog } = require('electron').remote;
|
||||||
import JsonDB from 'node-json-db';
|
const fs = require('fs');
|
||||||
import { initSetUp } from './default-util';
|
const path = require('path');
|
||||||
|
const JsonDB = require('node-json-db');
|
||||||
import fs = require('fs');
|
const Logger = require('./logger-util');
|
||||||
import path = require('path');
|
const { initSetUp } = require('./default-util');
|
||||||
import Logger = require('./logger-util');
|
|
||||||
|
|
||||||
const { app, dialog } = remote;
|
|
||||||
|
|
||||||
initSetUp();
|
initSetUp();
|
||||||
|
|
||||||
@@ -17,12 +14,10 @@ const logger = new Logger({
|
|||||||
timestamp: true
|
timestamp: true
|
||||||
});
|
});
|
||||||
|
|
||||||
let instance: null | CertificateUtil = null;
|
let instance = null;
|
||||||
const certificatesDir = `${app.getPath('userData')}/certificates`;
|
const certificatesDir = `${app.getPath('userData')}/certificates`;
|
||||||
|
|
||||||
class CertificateUtil {
|
class CertificateUtil {
|
||||||
db: JsonDB;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return instance;
|
return instance;
|
||||||
@@ -33,8 +28,7 @@ class CertificateUtil {
|
|||||||
this.reloadDB();
|
this.reloadDB();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
getCertificate(server, defaultValue = null) {
|
||||||
getCertificate(server: string, defaultValue: any = null): any {
|
|
||||||
this.reloadDB();
|
this.reloadDB();
|
||||||
const value = this.db.getData('/')[server];
|
const value = this.db.getData('/')[server];
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
@@ -43,9 +37,8 @@ class CertificateUtil {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to copy the certificate to userData folder
|
// Function to copy the certificate to userData folder
|
||||||
copyCertificate(_server: string, location: string, fileName: string): boolean {
|
copyCertificate(server, location, fileName) {
|
||||||
let copied = false;
|
let copied = false;
|
||||||
const filePath = `${certificatesDir}/${fileName}`;
|
const filePath = `${certificatesDir}/${fileName}`;
|
||||||
try {
|
try {
|
||||||
@@ -61,19 +54,16 @@ class CertificateUtil {
|
|||||||
}
|
}
|
||||||
return copied;
|
return copied;
|
||||||
}
|
}
|
||||||
|
setCertificate(server, fileName) {
|
||||||
setCertificate(server: string, fileName: string): void {
|
|
||||||
const filePath = `${certificatesDir}/${fileName}`;
|
const filePath = `${certificatesDir}/${fileName}`;
|
||||||
this.db.push(`/${server}`, filePath, true);
|
this.db.push(`/${server}`, filePath, true);
|
||||||
this.reloadDB();
|
this.reloadDB();
|
||||||
}
|
}
|
||||||
|
removeCertificate(server) {
|
||||||
removeCertificate(server: string): void {
|
|
||||||
this.db.delete(`/${server}`);
|
this.db.delete(`/${server}`);
|
||||||
this.reloadDB();
|
this.reloadDB();
|
||||||
}
|
}
|
||||||
|
reloadDB() {
|
||||||
reloadDB(): void {
|
|
||||||
const settingsJsonPath = path.join(app.getPath('userData'), '/config/certificates.json');
|
const settingsJsonPath = path.join(app.getPath('userData'), '/config/certificates.json');
|
||||||
try {
|
try {
|
||||||
const file = fs.readFileSync(settingsJsonPath, 'utf8');
|
const file = fs.readFileSync(settingsJsonPath, 'utf8');
|
||||||
@@ -93,4 +83,4 @@ class CertificateUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new CertificateUtil();
|
module.exports = new CertificateUtil();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
let instance: null | CommonUtil = null;
|
let instance = null;
|
||||||
|
|
||||||
class CommonUtil {
|
class CommonUtil {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -13,13 +13,13 @@ class CommonUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unescape already encoded/escaped strings
|
// unescape already encoded/escaped strings
|
||||||
decodeString(stringInput: string): string {
|
decodeString(string) {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const dom = parser.parseFromString(
|
const dom = parser.parseFromString(
|
||||||
'<!doctype html><body>' + stringInput,
|
'<!doctype html><body>' + string,
|
||||||
'text/html');
|
'text/html');
|
||||||
return dom.body.textContent;
|
return dom.body.textContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new CommonUtil();
|
module.exports = new CommonUtil();
|
||||||
@@ -1,33 +1,32 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import JsonDB from 'node-json-db';
|
|
||||||
|
|
||||||
import fs = require('fs');
|
const fs = require('fs');
|
||||||
import path = require('path');
|
const path = require('path');
|
||||||
import electron = require('electron');
|
const process = require('process');
|
||||||
import Logger = require('./logger-util');
|
const JsonDB = require('node-json-db');
|
||||||
|
const Logger = require('./logger-util');
|
||||||
|
|
||||||
const logger = new Logger({
|
const logger = new Logger({
|
||||||
file: 'config-util.log',
|
file: 'config-util.log',
|
||||||
timestamp: true
|
timestamp: true
|
||||||
});
|
});
|
||||||
|
|
||||||
let instance: null | ConfigUtil = null;
|
let instance = null;
|
||||||
let dialog: Electron.Dialog = null;
|
let dialog = null;
|
||||||
let app: Electron.App = null;
|
let app = null;
|
||||||
|
|
||||||
/* To make the util runnable in both main and renderer process */
|
/* To make the util runnable in both main and renderer process */
|
||||||
if (process.type === 'renderer') {
|
if (process.type === 'renderer') {
|
||||||
const { remote } = electron;
|
const remote = require('electron').remote;
|
||||||
dialog = remote.dialog;
|
dialog = remote.dialog;
|
||||||
app = remote.app;
|
app = remote.app;
|
||||||
} else {
|
} else {
|
||||||
|
const electron = require('electron');
|
||||||
dialog = electron.dialog;
|
dialog = electron.dialog;
|
||||||
app = electron.app;
|
app = electron.app;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigUtil {
|
class ConfigUtil {
|
||||||
db: JsonDB;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return instance;
|
return instance;
|
||||||
@@ -39,7 +38,7 @@ class ConfigUtil {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfigItem(key: string, defaultValue: any = null): any {
|
getConfigItem(key, defaultValue = null) {
|
||||||
try {
|
try {
|
||||||
this.db.reload();
|
this.db.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -54,9 +53,8 @@ class ConfigUtil {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function returns whether a key exists in the configuration file (settings.json)
|
// This function returns whether a key exists in the configuration file (settings.json)
|
||||||
isConfigItemExists(key: string): boolean {
|
isConfigItemExists(key) {
|
||||||
try {
|
try {
|
||||||
this.db.reload();
|
this.db.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -67,17 +65,17 @@ class ConfigUtil {
|
|||||||
return (value !== undefined);
|
return (value !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfigItem(key: string, value: any): void {
|
setConfigItem(key, value) {
|
||||||
this.db.push(`/${key}`, value, true);
|
this.db.push(`/${key}`, value, true);
|
||||||
this.db.save();
|
this.db.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeConfigItem(key: string): void {
|
removeConfigItem(key) {
|
||||||
this.db.delete(`/${key}`);
|
this.db.delete(`/${key}`);
|
||||||
this.db.save();
|
this.db.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadDB(): void {
|
reloadDB() {
|
||||||
const settingsJsonPath = path.join(app.getPath('userData'), '/config/settings.json');
|
const settingsJsonPath = path.join(app.getPath('userData'), '/config/settings.json');
|
||||||
try {
|
try {
|
||||||
const file = fs.readFileSync(settingsJsonPath, 'utf8');
|
const file = fs.readFileSync(settingsJsonPath, 'utf8');
|
||||||
@@ -98,4 +96,4 @@ class ConfigUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new ConfigUtil();
|
module.exports = new ConfigUtil();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
let app: Electron.App = null;
|
let app = null;
|
||||||
let setupCompleted = false;
|
let setupCompleted = false;
|
||||||
if (process.type === 'renderer') {
|
if (process.type === 'renderer') {
|
||||||
app = require('electron').remote.app;
|
app = require('electron').remote.app;
|
||||||
@@ -12,7 +12,7 @@ const zulipDir = app.getPath('userData');
|
|||||||
const logDir = `${zulipDir}/Logs/`;
|
const logDir = `${zulipDir}/Logs/`;
|
||||||
const certificatesDir = `${zulipDir}/certificates/`;
|
const certificatesDir = `${zulipDir}/certificates/`;
|
||||||
const configDir = `${zulipDir}/config/`;
|
const configDir = `${zulipDir}/config/`;
|
||||||
export const initSetUp = (): void => {
|
const initSetUp = () => {
|
||||||
// if it is the first time the app is running
|
// if it is the first time the app is running
|
||||||
// create zulip dir in userData folder to
|
// create zulip dir in userData folder to
|
||||||
// avoid errors
|
// avoid errors
|
||||||
@@ -72,3 +72,7 @@ export const initSetUp = (): void => {
|
|||||||
setupCompleted = true;
|
setupCompleted = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
initSetUp
|
||||||
|
};
|
||||||
@@ -1,30 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import ConfigUtil = require('./config-util');
|
const ConfigUtil = require(__dirname + '/config-util.js');
|
||||||
|
|
||||||
// TODO: TypeScript - add to Setting interface
|
function toggle() {
|
||||||
// the list of settings since we have fixed amount of them
|
|
||||||
// We want to do this by creating a new module that exports
|
|
||||||
// this interface
|
|
||||||
interface Setting {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Toggle {
|
|
||||||
dnd: boolean;
|
|
||||||
newSettings: Setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggle(): Toggle {
|
|
||||||
const dnd = !ConfigUtil.getConfigItem('dnd', false);
|
const dnd = !ConfigUtil.getConfigItem('dnd', false);
|
||||||
const dndSettingList = ['showNotification', 'silent'];
|
const dndSettingList = ['showNotification', 'silent'];
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
dndSettingList.push('flashTaskbarOnMessage');
|
dndSettingList.push('flashTaskbarOnMessage');
|
||||||
}
|
}
|
||||||
|
|
||||||
let newSettings: Setting;
|
let newSettings;
|
||||||
if (dnd) {
|
if (dnd) {
|
||||||
const oldSettings: Setting = {};
|
const oldSettings = {};
|
||||||
newSettings = {};
|
newSettings = {};
|
||||||
|
|
||||||
// Iterate through the dndSettingList.
|
// Iterate through the dndSettingList.
|
||||||
@@ -48,3 +35,7 @@ export function toggle(): Toggle {
|
|||||||
ConfigUtil.setConfigItem('dnd', dnd);
|
ConfigUtil.setConfigItem('dnd', dnd);
|
||||||
return {dnd, newSettings};
|
return {dnd, newSettings};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
toggle
|
||||||
|
};
|
||||||
367
app/renderer/js/utils/domain-util.js
Normal file
367
app/renderer/js/utils/domain-util.js
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { app, dialog } = require('electron').remote;
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const JsonDB = require('node-json-db');
|
||||||
|
const request = require('request');
|
||||||
|
const escape = require('escape-html');
|
||||||
|
|
||||||
|
const Logger = require('./logger-util');
|
||||||
|
|
||||||
|
const CertificateUtil = require(__dirname + '/certificate-util.js');
|
||||||
|
const ProxyUtil = require(__dirname + '/proxy-util.js');
|
||||||
|
const ConfigUtil = require(__dirname + '/config-util.js');
|
||||||
|
const SystemUtil = require(__dirname + '/../utils/system-util.js');
|
||||||
|
|
||||||
|
const logger = new Logger({
|
||||||
|
file: `domain-util.log`,
|
||||||
|
timestamp: true
|
||||||
|
});
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
const defaultIconUrl = '../renderer/img/icon.png';
|
||||||
|
|
||||||
|
class DomainUtil {
|
||||||
|
constructor() {
|
||||||
|
if (instance) {
|
||||||
|
return instance;
|
||||||
|
} else {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadDB();
|
||||||
|
// Migrate from old schema
|
||||||
|
if (this.db.getData('/').domain) {
|
||||||
|
this.addDomain({
|
||||||
|
alias: 'Zulip',
|
||||||
|
url: this.db.getData('/domain')
|
||||||
|
});
|
||||||
|
this.db.delete('/domain');
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDomains() {
|
||||||
|
this.reloadDB();
|
||||||
|
if (this.db.getData('/').domains === undefined) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return this.db.getData('/domains');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDomain(index) {
|
||||||
|
this.reloadDB();
|
||||||
|
return this.db.getData(`/domains[${index}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDomain(index, server) {
|
||||||
|
this.reloadDB();
|
||||||
|
this.db.push(`/domains[${index}]`, server, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
addDomain(server) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (server.icon) {
|
||||||
|
this.saveServerIcon(server).then(localIconUrl => {
|
||||||
|
server.icon = localIconUrl;
|
||||||
|
this.db.push('/domains[]', server, true);
|
||||||
|
this.reloadDB();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
server.icon = defaultIconUrl;
|
||||||
|
this.db.push('/domains[]', server, true);
|
||||||
|
this.reloadDB();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeDomains() {
|
||||||
|
this.db.delete('/domains');
|
||||||
|
this.reloadDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeDomain(index) {
|
||||||
|
this.db.delete(`/domains[${index}]`);
|
||||||
|
this.reloadDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if domain is already added
|
||||||
|
duplicateDomain(domain) {
|
||||||
|
domain = this.formatUrl(domain);
|
||||||
|
const servers = this.getDomains();
|
||||||
|
for (const i in servers) {
|
||||||
|
if (servers[i].url === domain) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDomain(domain, silent = false) {
|
||||||
|
if (!silent && this.duplicateDomain(domain)) {
|
||||||
|
// Do not check duplicate in silent mode
|
||||||
|
return Promise.reject('This server has been added.');
|
||||||
|
}
|
||||||
|
|
||||||
|
domain = this.formatUrl(domain);
|
||||||
|
|
||||||
|
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
|
||||||
|
let certificateLocation = '';
|
||||||
|
|
||||||
|
if (certificate) {
|
||||||
|
// To handle case where certificate has been moved from the location in certificates.json
|
||||||
|
try {
|
||||||
|
certificateLocation = fs.readFileSync(certificate);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('Error while trying to get certificate: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
|
||||||
|
|
||||||
|
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
|
||||||
|
// Add proxy as a parameter if it sbeing used.
|
||||||
|
const checkDomain = {
|
||||||
|
url: domain + '/static/audio/zulip.ogg',
|
||||||
|
ca: (certificateLocation) ? certificateLocation : '',
|
||||||
|
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : '',
|
||||||
|
ecdhCurve: 'auto',
|
||||||
|
headers: { 'User-Agent': SystemUtil.getUserAgent() }
|
||||||
|
};
|
||||||
|
|
||||||
|
const serverConf = {
|
||||||
|
icon: defaultIconUrl,
|
||||||
|
url: domain,
|
||||||
|
alias: domain
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request(checkDomain, (error, response) => {
|
||||||
|
// If the domain contains following strings we just bypass the server
|
||||||
|
const whitelistDomains = [
|
||||||
|
'zulipdev.org'
|
||||||
|
];
|
||||||
|
|
||||||
|
// make sure that error is an error or string not undefined
|
||||||
|
// so validation does not throw error.
|
||||||
|
error = error || '';
|
||||||
|
|
||||||
|
const certsError = error.toString().includes('certificate');
|
||||||
|
if (!error && response.statusCode < 400) {
|
||||||
|
// Correct
|
||||||
|
this.getServerSettings(domain).then(serverSettings => {
|
||||||
|
resolve(serverSettings);
|
||||||
|
}, () => {
|
||||||
|
resolve(serverConf);
|
||||||
|
});
|
||||||
|
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError) {
|
||||||
|
if (silent) {
|
||||||
|
this.getServerSettings(domain).then(serverSettings => {
|
||||||
|
resolve(serverSettings);
|
||||||
|
}, () => {
|
||||||
|
resolve(serverConf);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Report error to sentry to get idea of possible certificate errors
|
||||||
|
// users get when adding the servers
|
||||||
|
logger.reportSentry(new Error(error));
|
||||||
|
const certErrorMessage = `Do you trust certificate from ${domain}? \n ${error}`;
|
||||||
|
const certErrorDetail = `The organization you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
|
||||||
|
\nIf you have a valid certificate please add it from Settings>Organizations and try to add the organization again.
|
||||||
|
\nUnless you have a good reason to believe otherwise, you should not proceed.
|
||||||
|
\nYou can click here if you'd like to proceed with the connection.`;
|
||||||
|
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
buttons: ['Yes', 'No'],
|
||||||
|
defaultId: 1,
|
||||||
|
message: certErrorMessage,
|
||||||
|
detail: certErrorDetail
|
||||||
|
}, response => {
|
||||||
|
if (response === 0) {
|
||||||
|
this.getServerSettings(domain).then(serverSettings => {
|
||||||
|
resolve(serverSettings);
|
||||||
|
}, () => {
|
||||||
|
resolve(serverConf);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject('Untrusted Certificate.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \
|
||||||
|
\n (1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server \
|
||||||
|
\n (4) the server has a valid certificate, you can add custom certificates in Settings>Organizations`;
|
||||||
|
reject(invalidZulipServerError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getServerSettings(domain) {
|
||||||
|
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
|
||||||
|
let certificateLocation = '';
|
||||||
|
|
||||||
|
if (certificate) {
|
||||||
|
// To handle case where certificate has been moved from the location in certificates.json
|
||||||
|
try {
|
||||||
|
certificateLocation = fs.readFileSync(certificate);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('Error while trying to get certificate: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
|
||||||
|
const serverSettingsOptions = {
|
||||||
|
url: domain + '/api/v1/server_settings',
|
||||||
|
ca: (certificateLocation) ? certificateLocation : '',
|
||||||
|
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : '',
|
||||||
|
ecdhCurve: 'auto',
|
||||||
|
headers: { 'User-Agent': SystemUtil.getUserAgent() }
|
||||||
|
};
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request(serverSettingsOptions, (error, response) => {
|
||||||
|
if (!error && response.statusCode === 200) {
|
||||||
|
const data = JSON.parse(response.body);
|
||||||
|
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
|
||||||
|
resolve({
|
||||||
|
// Some Zulip Servers use absolute URL for server icon whereas others use relative URL
|
||||||
|
// Following check handles both the cases
|
||||||
|
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
|
||||||
|
url: data.realm_uri,
|
||||||
|
alias: escape(data.realm_name)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject('Zulip server version < 1.6.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveServerIcon(server) {
|
||||||
|
const url = server.icon;
|
||||||
|
const domain = server.url;
|
||||||
|
|
||||||
|
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
|
||||||
|
let certificateLocation = '';
|
||||||
|
|
||||||
|
if (certificate) {
|
||||||
|
// To handle case where certificate has been moved from the location in certificates.json
|
||||||
|
try {
|
||||||
|
certificateLocation = fs.readFileSync(certificate);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('Error while trying to get certificate: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
|
||||||
|
|
||||||
|
// Add proxy and certificate as a parameter if its being used.
|
||||||
|
const serverIconOptions = {
|
||||||
|
url,
|
||||||
|
ca: (certificateLocation) ? certificateLocation : '',
|
||||||
|
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : '',
|
||||||
|
ecdhCurve: 'auto',
|
||||||
|
headers: { 'User-Agent': SystemUtil.getUserAgent() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// The save will always succeed. If url is invalid, downgrade to default icon.
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const filePath = this.generateFilePath(url);
|
||||||
|
const file = fs.createWriteStream(filePath);
|
||||||
|
try {
|
||||||
|
request(serverIconOptions).on('response', response => {
|
||||||
|
response.on('error', err => {
|
||||||
|
logger.log('Could not get server icon.');
|
||||||
|
logger.log(err);
|
||||||
|
logger.reportSentry(err);
|
||||||
|
resolve(defaultIconUrl);
|
||||||
|
});
|
||||||
|
response.pipe(file).on('finish', () => {
|
||||||
|
resolve(filePath);
|
||||||
|
});
|
||||||
|
}).on('error', err => {
|
||||||
|
logger.log('Could not get server icon.');
|
||||||
|
logger.log(err);
|
||||||
|
logger.reportSentry(err);
|
||||||
|
resolve(defaultIconUrl);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.log('Could not get server icon.');
|
||||||
|
logger.log(err);
|
||||||
|
logger.reportSentry(err);
|
||||||
|
resolve(defaultIconUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSavedServer(url, index) {
|
||||||
|
// Does not promise successful update
|
||||||
|
this.checkDomain(url, true).then(newServerConf => {
|
||||||
|
this.saveServerIcon(newServerConf).then(localIconUrl => {
|
||||||
|
newServerConf.icon = localIconUrl;
|
||||||
|
this.updateDomain(index, newServerConf);
|
||||||
|
this.reloadDB();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadDB() {
|
||||||
|
const domainJsonPath = path.join(app.getPath('userData'), 'config/domain.json');
|
||||||
|
try {
|
||||||
|
const file = fs.readFileSync(domainJsonPath, 'utf8');
|
||||||
|
JSON.parse(file);
|
||||||
|
} catch (err) {
|
||||||
|
if (fs.existsSync(domainJsonPath)) {
|
||||||
|
fs.unlinkSync(domainJsonPath);
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'Error saving new organization',
|
||||||
|
'There seems to be error while saving new organization, ' +
|
||||||
|
'you may have to re-add your previous organizations back.'
|
||||||
|
);
|
||||||
|
logger.error('Error while JSON parsing domain.json: ');
|
||||||
|
logger.error(err);
|
||||||
|
logger.reportSentry(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.db = new JsonDB(domainJsonPath, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateFilePath(url) {
|
||||||
|
const dir = `${app.getPath('userData')}/server-icons`;
|
||||||
|
const extension = path.extname(url).split('?')[0];
|
||||||
|
|
||||||
|
let hash = 5381;
|
||||||
|
let len = url.length;
|
||||||
|
|
||||||
|
while (len) {
|
||||||
|
hash = (hash * 33) ^ url.charCodeAt(--len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 'server-icons' directory if not existed
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${dir}/${hash >>> 0}${extension}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatUrl(domain) {
|
||||||
|
const hasPrefix = (domain.indexOf('http') === 0);
|
||||||
|
if (hasPrefix) {
|
||||||
|
return domain;
|
||||||
|
} else {
|
||||||
|
return (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new DomainUtil();
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
import JsonDB from 'node-json-db';
|
|
||||||
|
|
||||||
import escape = require('escape-html');
|
|
||||||
import request = require('request');
|
|
||||||
import fs = require('fs');
|
|
||||||
import path = require('path');
|
|
||||||
import Logger = require('./logger-util');
|
|
||||||
import electron = require('electron');
|
|
||||||
|
|
||||||
import RequestUtil = require('./request-util');
|
|
||||||
import Messages = require('../../../resources/messages');
|
|
||||||
|
|
||||||
const { app, dialog } = electron.remote;
|
|
||||||
|
|
||||||
const logger = new Logger({
|
|
||||||
file: `domain-util.log`,
|
|
||||||
timestamp: true
|
|
||||||
});
|
|
||||||
|
|
||||||
let instance: null | DomainUtil = null;
|
|
||||||
|
|
||||||
const defaultIconUrl = '../renderer/img/icon.png';
|
|
||||||
|
|
||||||
class DomainUtil {
|
|
||||||
db: JsonDB;
|
|
||||||
constructor() {
|
|
||||||
if (instance) {
|
|
||||||
return instance;
|
|
||||||
} else {
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reloadDB();
|
|
||||||
// Migrate from old schema
|
|
||||||
if (this.db.getData('/').domain) {
|
|
||||||
this.addDomain({
|
|
||||||
alias: 'Zulip',
|
|
||||||
url: this.db.getData('/domain')
|
|
||||||
});
|
|
||||||
this.db.delete('/domain');
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDomains(): any {
|
|
||||||
this.reloadDB();
|
|
||||||
if (this.db.getData('/').domains === undefined) {
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
return this.db.getData('/domains');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getDomain(index: number): any {
|
|
||||||
this.reloadDB();
|
|
||||||
return this.db.getData(`/domains[${index}]`);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDomain(index: number, server: object): void {
|
|
||||||
this.reloadDB();
|
|
||||||
this.db.push(`/domains[${index}]`, server, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
addDomain(server: any): Promise<void> {
|
|
||||||
const { ignoreCerts } = server;
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (server.icon) {
|
|
||||||
this.saveServerIcon(server, ignoreCerts).then(localIconUrl => {
|
|
||||||
server.icon = localIconUrl;
|
|
||||||
this.db.push('/domains[]', server, true);
|
|
||||||
this.reloadDB();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
server.icon = defaultIconUrl;
|
|
||||||
this.db.push('/domains[]', server, true);
|
|
||||||
this.reloadDB();
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeDomains(): void {
|
|
||||||
this.db.delete('/domains');
|
|
||||||
this.reloadDB();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeDomain(index: number): void {
|
|
||||||
this.db.delete(`/domains[${index}]`);
|
|
||||||
this.reloadDB();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if domain is already added
|
|
||||||
duplicateDomain(domain: any): boolean {
|
|
||||||
domain = this.formatUrl(domain);
|
|
||||||
const servers = this.getDomains();
|
|
||||||
for (const i in servers) {
|
|
||||||
if (servers[i].url === domain) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkCertError(domain: any, serverConf: any, error: string, silent: boolean): Promise<string | object> {
|
|
||||||
if (silent) {
|
|
||||||
// since getting server settings has already failed
|
|
||||||
return serverConf;
|
|
||||||
} else {
|
|
||||||
// Report error to sentry to get idea of possible certificate errors
|
|
||||||
// users get when adding the servers
|
|
||||||
logger.reportSentry(new Error(error).toString());
|
|
||||||
const certErrorMessage = Messages.certErrorMessage(domain, error);
|
|
||||||
const certErrorDetail = Messages.certErrorDetail();
|
|
||||||
|
|
||||||
const response = await (dialog.showMessageBox({
|
|
||||||
type: 'warning',
|
|
||||||
buttons: ['Yes', 'No'],
|
|
||||||
defaultId: 1,
|
|
||||||
message: certErrorMessage,
|
|
||||||
detail: certErrorDetail
|
|
||||||
}) as any); // TODO: TypeScript - Figure this out
|
|
||||||
if (response === 0) {
|
|
||||||
// set ignoreCerts parameter to true in case user responds with yes
|
|
||||||
serverConf.ignoreCerts = true;
|
|
||||||
try {
|
|
||||||
return await this.getServerSettings(domain, serverConf.ignoreCerts);
|
|
||||||
} catch (_) {
|
|
||||||
if (error === Messages.noOrgsError(domain)) {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
return serverConf;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Untrusted certificate.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignoreCerts parameter helps in fetching server icon and
|
|
||||||
// other server details when user chooses to ignore certificate warnings
|
|
||||||
async checkDomain(domain: any, ignoreCerts = false, silent = false): Promise<any> {
|
|
||||||
if (!silent && this.duplicateDomain(domain)) {
|
|
||||||
// Do not check duplicate in silent mode
|
|
||||||
throw new Error('This server has been added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
domain = this.formatUrl(domain);
|
|
||||||
|
|
||||||
const serverConf = {
|
|
||||||
icon: defaultIconUrl,
|
|
||||||
url: domain,
|
|
||||||
alias: domain,
|
|
||||||
ignoreCerts
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this.getServerSettings(domain, serverConf.ignoreCerts);
|
|
||||||
} catch (err) {
|
|
||||||
// If the domain contains following strings we just bypass the server
|
|
||||||
const whitelistDomains = [
|
|
||||||
'zulipdev.org'
|
|
||||||
];
|
|
||||||
|
|
||||||
// make sure that error is an error or string not undefined
|
|
||||||
// so validation does not throw error.
|
|
||||||
const error = err || '';
|
|
||||||
|
|
||||||
const certsError = error.toString().includes('certificate');
|
|
||||||
if (domain.indexOf(whitelistDomains) >= 0 || certsError) {
|
|
||||||
return this.checkCertError(domain, serverConf, error, silent);
|
|
||||||
} else {
|
|
||||||
throw Messages.invalidZulipServerError(domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getServerSettings(domain: any, ignoreCerts = false): Promise<object | string> {
|
|
||||||
const serverSettingsOptions = {
|
|
||||||
url: domain + '/api/v1/server_settings',
|
|
||||||
...RequestUtil.requestOptions(domain, ignoreCerts)
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
request(serverSettingsOptions, (error: string, response: any) => {
|
|
||||||
if (!error && response.statusCode === 200) {
|
|
||||||
const data = JSON.parse(response.body);
|
|
||||||
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
|
|
||||||
resolve({
|
|
||||||
// Some Zulip Servers use absolute URL for server icon whereas others use relative URL
|
|
||||||
// Following check handles both the cases
|
|
||||||
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
|
|
||||||
url: data.realm_uri,
|
|
||||||
alias: escape(data.realm_name),
|
|
||||||
ignoreCerts
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
reject(Messages.noOrgsError(domain));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reject(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
saveServerIcon(server: any, ignoreCerts = false): Promise<string> {
|
|
||||||
const url = server.icon;
|
|
||||||
const domain = server.url;
|
|
||||||
|
|
||||||
const serverIconOptions = {
|
|
||||||
url,
|
|
||||||
...RequestUtil.requestOptions(domain, ignoreCerts)
|
|
||||||
};
|
|
||||||
|
|
||||||
// The save will always succeed. If url is invalid, downgrade to default icon.
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const filePath = this.generateFilePath(url);
|
|
||||||
const file = fs.createWriteStream(filePath);
|
|
||||||
try {
|
|
||||||
request(serverIconOptions).on('response', (response: any) => {
|
|
||||||
response.on('error', (err: string) => {
|
|
||||||
logger.log('Could not get server icon.');
|
|
||||||
logger.log(err);
|
|
||||||
logger.reportSentry(err);
|
|
||||||
resolve(defaultIconUrl);
|
|
||||||
});
|
|
||||||
response.pipe(file).on('finish', () => {
|
|
||||||
resolve(filePath);
|
|
||||||
});
|
|
||||||
}).on('error', (err: string) => {
|
|
||||||
logger.log('Could not get server icon.');
|
|
||||||
logger.log(err);
|
|
||||||
logger.reportSentry(err);
|
|
||||||
resolve(defaultIconUrl);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
logger.log('Could not get server icon.');
|
|
||||||
logger.log(err);
|
|
||||||
logger.reportSentry(err);
|
|
||||||
resolve(defaultIconUrl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSavedServer(url: string, index: number): void {
|
|
||||||
// Does not promise successful update
|
|
||||||
const { ignoreCerts } = this.getDomain(index);
|
|
||||||
this.checkDomain(url, ignoreCerts, true).then(newServerConf => {
|
|
||||||
this.saveServerIcon(newServerConf, ignoreCerts).then(localIconUrl => {
|
|
||||||
newServerConf.icon = localIconUrl;
|
|
||||||
this.updateDomain(index, newServerConf);
|
|
||||||
this.reloadDB();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadDB(): void {
|
|
||||||
const domainJsonPath = path.join(app.getPath('userData'), 'config/domain.json');
|
|
||||||
try {
|
|
||||||
const file = fs.readFileSync(domainJsonPath, 'utf8');
|
|
||||||
JSON.parse(file);
|
|
||||||
} catch (err) {
|
|
||||||
if (fs.existsSync(domainJsonPath)) {
|
|
||||||
fs.unlinkSync(domainJsonPath);
|
|
||||||
dialog.showErrorBox(
|
|
||||||
'Error saving new organization',
|
|
||||||
'There seems to be error while saving new organization, ' +
|
|
||||||
'you may have to re-add your previous organizations back.'
|
|
||||||
);
|
|
||||||
logger.error('Error while JSON parsing domain.json: ');
|
|
||||||
logger.error(err);
|
|
||||||
logger.reportSentry(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.db = new JsonDB(domainJsonPath, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateFilePath(url: string): string {
|
|
||||||
const dir = `${app.getPath('userData')}/server-icons`;
|
|
||||||
const extension = path.extname(url).split('?')[0];
|
|
||||||
|
|
||||||
let hash = 5381;
|
|
||||||
let len = url.length;
|
|
||||||
|
|
||||||
while (len) {
|
|
||||||
hash = (hash * 33) ^ url.charCodeAt(--len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create 'server-icons' directory if not existed
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
fs.mkdirSync(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${dir}/${hash >>> 0}${extension}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
formatUrl(domain: any): string {
|
|
||||||
const hasPrefix = (domain.indexOf('http') === 0);
|
|
||||||
if (hasPrefix) {
|
|
||||||
return domain;
|
|
||||||
} else {
|
|
||||||
return (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export = new DomainUtil();
|
|
||||||
@@ -1,14 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// TODO: TypeScript - Add @types/
|
const wurl = require('wurl');
|
||||||
import wurl = require('wurl');
|
|
||||||
|
|
||||||
let instance: null | LinkUtil = null;
|
let instance = null;
|
||||||
|
|
||||||
interface IsInternalResponse {
|
|
||||||
isInternalUrl: boolean;
|
|
||||||
isUploadsUrl: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LinkUtil {
|
class LinkUtil {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -21,7 +15,7 @@ class LinkUtil {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
isInternal(currentUrl: string, newUrl: string): IsInternalResponse {
|
isInternal(currentUrl, newUrl) {
|
||||||
const currentDomain = wurl('hostname', currentUrl);
|
const currentDomain = wurl('hostname', currentUrl);
|
||||||
const newDomain = wurl('hostname', newUrl);
|
const newDomain = wurl('hostname', newUrl);
|
||||||
|
|
||||||
@@ -35,17 +29,17 @@ class LinkUtil {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isImage(url: string): boolean {
|
isImage(url) {
|
||||||
// test for images extension as well as urls like .png?s=100
|
// test for images extension as well as urls like .png?s=100
|
||||||
const isImageUrl = /\.(bmp|gif|jpg|jpeg|png|webp)\?*.*$/i;
|
const isImageUrl = /\.(bmp|gif|jpg|jpeg|png|webp)\?*.*$/i;
|
||||||
return isImageUrl.test(url);
|
return isImageUrl.test(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
isPDF(url: string): boolean {
|
isPDF(url) {
|
||||||
// test for pdf extension
|
// test for pdf extension
|
||||||
const isPDFUrl = /\.(pdf)\?*.*$/i;
|
const isPDFUrl = /\.(pdf)\?*.*$/i;
|
||||||
return isPDFUrl.test(url);
|
return isPDFUrl.test(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new LinkUtil();
|
module.exports = new LinkUtil();
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import JsonDB from 'node-json-db';
|
|
||||||
|
|
||||||
import fs = require('fs');
|
|
||||||
import path = require('path');
|
|
||||||
import electron = require('electron');
|
|
||||||
import Logger = require('./logger-util');
|
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const process = require('process');
|
||||||
const remote =
|
const remote =
|
||||||
process.type === 'renderer' ? electron.remote : electron;
|
process.type === 'renderer' ? require('electron').remote : require('electron');
|
||||||
|
const JsonDB = require('node-json-db');
|
||||||
|
const Logger = require('./logger-util');
|
||||||
|
|
||||||
const logger = new Logger({
|
const logger = new Logger({
|
||||||
file: 'linux-update-util.log',
|
file: 'linux-update-util.log',
|
||||||
@@ -16,11 +15,10 @@ const logger = new Logger({
|
|||||||
|
|
||||||
/* To make the util runnable in both main and renderer process */
|
/* To make the util runnable in both main and renderer process */
|
||||||
const { dialog, app } = remote;
|
const { dialog, app } = remote;
|
||||||
let instance: null | LinuxUpdateUtil = null;
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
class LinuxUpdateUtil {
|
class LinuxUpdateUtil {
|
||||||
db: JsonDB;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return instance;
|
return instance;
|
||||||
@@ -32,7 +30,7 @@ class LinuxUpdateUtil {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUpdateItem(key: string, defaultValue: any = null): any {
|
getUpdateItem(key, defaultValue = null) {
|
||||||
this.reloadDB();
|
this.reloadDB();
|
||||||
const value = this.db.getData('/')[key];
|
const value = this.db.getData('/')[key];
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
@@ -43,17 +41,17 @@ class LinuxUpdateUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setUpdateItem(key: string, value: any): void {
|
setUpdateItem(key, value) {
|
||||||
this.db.push(`/${key}`, value, true);
|
this.db.push(`/${key}`, value, true);
|
||||||
this.reloadDB();
|
this.reloadDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeUpdateItem(key: string): void {
|
removeUpdateItem(key) {
|
||||||
this.db.delete(`/${key}`);
|
this.db.delete(`/${key}`);
|
||||||
this.reloadDB();
|
this.reloadDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadDB(): void {
|
reloadDB() {
|
||||||
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/config/updates.json');
|
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/config/updates.json');
|
||||||
try {
|
try {
|
||||||
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
|
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
|
||||||
@@ -73,4 +71,4 @@ class LinuxUpdateUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new LinuxUpdateUtil();
|
module.exports = new LinuxUpdateUtil();
|
||||||
93
app/renderer/js/utils/logger-util.js
Normal file
93
app/renderer/js/utils/logger-util.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
const NodeConsole = require('console').Console;
|
||||||
|
const fs = require('fs');
|
||||||
|
const isDev = require('electron-is-dev');
|
||||||
|
const { initSetUp } = require('./default-util');
|
||||||
|
const { sentryInit, captureException } = require('./sentry-util');
|
||||||
|
|
||||||
|
initSetUp();
|
||||||
|
sentryInit();
|
||||||
|
let app = null;
|
||||||
|
if (process.type === 'renderer') {
|
||||||
|
app = require('electron').remote.app;
|
||||||
|
} else {
|
||||||
|
app = require('electron').app;
|
||||||
|
}
|
||||||
|
|
||||||
|
const browserConsole = console;
|
||||||
|
const logDir = `${app.getPath('userData')}/Logs`;
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
constructor(opts = {}) {
|
||||||
|
let {
|
||||||
|
timestamp = true,
|
||||||
|
file = 'console.log',
|
||||||
|
level = true,
|
||||||
|
logInDevMode = false
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
file = `${logDir}/${file}`;
|
||||||
|
if (timestamp === true) {
|
||||||
|
timestamp = this.getTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileStream = fs.createWriteStream(file, { flags: 'a' });
|
||||||
|
const nodeConsole = new NodeConsole(fileStream);
|
||||||
|
|
||||||
|
this.nodeConsole = nodeConsole;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.level = level;
|
||||||
|
this.logInDevMode = logInDevMode;
|
||||||
|
this.setUpConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
_log(type, ...args) {
|
||||||
|
const {
|
||||||
|
nodeConsole, timestamp, level, logInDevMode
|
||||||
|
} = this;
|
||||||
|
let nodeConsoleLog;
|
||||||
|
|
||||||
|
/* eslint-disable no-fallthrough */
|
||||||
|
switch (true) {
|
||||||
|
case typeof timestamp === 'function':
|
||||||
|
args.unshift(timestamp() + ' |\t');
|
||||||
|
|
||||||
|
case (level !== false):
|
||||||
|
args.unshift(type.toUpperCase() + ' |');
|
||||||
|
|
||||||
|
case isDev || logInDevMode:
|
||||||
|
nodeConsoleLog = nodeConsole[type] || nodeConsole.log;
|
||||||
|
nodeConsoleLog.apply(null, args);
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
/* eslint-enable no-fallthrough */
|
||||||
|
|
||||||
|
browserConsole[type].apply(null, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
setUpConsole() {
|
||||||
|
for (const type in browserConsole) {
|
||||||
|
this.setupConsoleMethod(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupConsoleMethod(type) {
|
||||||
|
this[type] = (...args) => {
|
||||||
|
this._log(type, ...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimestamp() {
|
||||||
|
const date = new Date();
|
||||||
|
const timestamp =
|
||||||
|
`${date.getMonth()}/${date.getDate()} ` +
|
||||||
|
`${date.getMinutes()}:${date.getSeconds()}`;
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
reportSentry(err) {
|
||||||
|
captureException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Logger;
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
import { Console as NodeConsole } from 'console'; // eslint-disable-line node/prefer-global/console
|
|
||||||
import { initSetUp } from './default-util';
|
|
||||||
import { sentryInit, captureException } from './sentry-util';
|
|
||||||
|
|
||||||
import fs = require('fs');
|
|
||||||
import os = require('os');
|
|
||||||
import isDev = require('electron-is-dev');
|
|
||||||
import electron = require('electron');
|
|
||||||
// this interface adds [key: string]: any so
|
|
||||||
// we can do console[type] later on in the code
|
|
||||||
interface PatchedConsole extends Console {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoggerOptions {
|
|
||||||
timestamp?: any;
|
|
||||||
file?: string;
|
|
||||||
level?: boolean;
|
|
||||||
logInDevMode?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
initSetUp();
|
|
||||||
|
|
||||||
let app: Electron.App = null;
|
|
||||||
let reportErrors = true;
|
|
||||||
if (process.type === 'renderer') {
|
|
||||||
app = electron.remote.app;
|
|
||||||
|
|
||||||
// Report Errors to Sentry only if it is enabled in settings
|
|
||||||
// Gets the value of reportErrors from config-util for renderer process
|
|
||||||
// For main process, sentryInit() is handled in index.js
|
|
||||||
const { ipcRenderer } = electron;
|
|
||||||
ipcRenderer.send('error-reporting');
|
|
||||||
ipcRenderer.on('error-reporting-val', (_event: any, errorReporting: boolean) => {
|
|
||||||
reportErrors = errorReporting;
|
|
||||||
if (reportErrors) {
|
|
||||||
sentryInit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
app = electron.app;
|
|
||||||
}
|
|
||||||
|
|
||||||
const browserConsole: PatchedConsole = console;
|
|
||||||
const logDir = `${app.getPath('userData')}/Logs`;
|
|
||||||
|
|
||||||
class Logger {
|
|
||||||
nodeConsole: PatchedConsole;
|
|
||||||
timestamp: any; // TODO: TypeScript - Figure out how to make this work with string | Function.
|
|
||||||
level: boolean;
|
|
||||||
logInDevMode: boolean;
|
|
||||||
[key: string]: any;
|
|
||||||
|
|
||||||
constructor(opts: LoggerOptions = {}) {
|
|
||||||
let {
|
|
||||||
timestamp = true,
|
|
||||||
file = 'console.log',
|
|
||||||
level = true,
|
|
||||||
logInDevMode = false
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
file = `${logDir}/${file}`;
|
|
||||||
if (timestamp === true) {
|
|
||||||
timestamp = this.getTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim log according to type of process
|
|
||||||
if (process.type === 'renderer') {
|
|
||||||
requestIdleCallback(() => this.trimLog(file));
|
|
||||||
} else {
|
|
||||||
process.nextTick(() => this.trimLog(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileStream = fs.createWriteStream(file, { flags: 'a' });
|
|
||||||
const nodeConsole = new NodeConsole(fileStream);
|
|
||||||
|
|
||||||
this.nodeConsole = nodeConsole;
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.level = level;
|
|
||||||
this.logInDevMode = logInDevMode;
|
|
||||||
this.setUpConsole();
|
|
||||||
}
|
|
||||||
|
|
||||||
_log(type: string, ...args: any[]): void {
|
|
||||||
const {
|
|
||||||
nodeConsole, timestamp, level, logInDevMode
|
|
||||||
} = this;
|
|
||||||
let nodeConsoleLog;
|
|
||||||
|
|
||||||
/* eslint-disable no-fallthrough */
|
|
||||||
switch (true) {
|
|
||||||
case typeof timestamp === 'function':
|
|
||||||
args.unshift(timestamp() + ' |\t');
|
|
||||||
|
|
||||||
case (level !== false):
|
|
||||||
args.unshift(type.toUpperCase() + ' |');
|
|
||||||
|
|
||||||
case isDev || logInDevMode:
|
|
||||||
nodeConsoleLog = nodeConsole[type] || nodeConsole.log;
|
|
||||||
nodeConsoleLog.apply(null, args); // eslint-disable-line prefer-spread
|
|
||||||
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
/* eslint-enable no-fallthrough */
|
|
||||||
|
|
||||||
browserConsole[type].apply(null, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
setUpConsole(): void {
|
|
||||||
for (const type in browserConsole) {
|
|
||||||
this.setupConsoleMethod(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setupConsoleMethod(type: string): void {
|
|
||||||
this[type] = (...args: any[]) => {
|
|
||||||
const log = this._log.bind(this, type, ...args);
|
|
||||||
log();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getTimestamp(): string {
|
|
||||||
const date = new Date();
|
|
||||||
const timestamp =
|
|
||||||
`${date.getMonth()}/${date.getDate()} ` +
|
|
||||||
`${date.getMinutes()}:${date.getSeconds()}`;
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
reportSentry(err: string): void {
|
|
||||||
if (reportErrors) {
|
|
||||||
captureException(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trimLog(file: string): void{
|
|
||||||
fs.readFile(file, 'utf8', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export = Logger;
|
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
// This util function returns the page params if they're present else returns null
|
// This util function returns the page params if they're present else returns null
|
||||||
export function isPageParams(): null | object {
|
function isPageParams() {
|
||||||
let webpageParams = null;
|
let webpageParams = null;
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-undef, @typescript-eslint/camelcase
|
// eslint-disable-next-line no-undef, camelcase
|
||||||
webpageParams = page_params;
|
webpageParams = page_params;
|
||||||
} catch (_) {
|
} catch (err) {
|
||||||
webpageParams = null;
|
webpageParams = null;
|
||||||
}
|
}
|
||||||
return webpageParams;
|
return webpageParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isPageParams
|
||||||
|
};
|
||||||
@@ -1,15 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import url = require('url');
|
const url = require('url');
|
||||||
|
const ConfigUtil = require('./config-util.js');
|
||||||
|
|
||||||
import ConfigUtil = require('./config-util');
|
let instance = null;
|
||||||
|
|
||||||
let instance: null | ProxyUtil = null;
|
|
||||||
|
|
||||||
interface ProxyRule {
|
|
||||||
hostname?: string;
|
|
||||||
port?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProxyUtil {
|
class ProxyUtil {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -23,13 +17,8 @@ class ProxyUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return proxy to be used for a particular uri, to be used for request
|
// Return proxy to be used for a particular uri, to be used for request
|
||||||
getProxy(_uri: string): ProxyRule | void {
|
getProxy(uri) {
|
||||||
const parsedUri = url.parse(_uri);
|
uri = url.parse(uri);
|
||||||
if (parsedUri === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uri = parsedUri;
|
|
||||||
const proxyRules = ConfigUtil.getConfigItem('proxyRules', '').split(';');
|
const proxyRules = ConfigUtil.getConfigItem('proxyRules', '').split(';');
|
||||||
// If SPS is on and system uses no proxy then request should not try to use proxy from
|
// If SPS is on and system uses no proxy then request should not try to use proxy from
|
||||||
// environment. NO_PROXY = '*' makes request ignore all environment proxy variables.
|
// environment. NO_PROXY = '*' makes request ignore all environment proxy variables.
|
||||||
@@ -38,9 +27,9 @@ class ProxyUtil {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyRule: any = {};
|
const proxyRule = {};
|
||||||
if (uri.protocol === 'http:') {
|
if (uri.protocol === 'http:') {
|
||||||
proxyRules.forEach((proxy: string) => {
|
proxyRules.forEach(proxy => {
|
||||||
if (proxy.includes('http=')) {
|
if (proxy.includes('http=')) {
|
||||||
proxyRule.hostname = proxy.split('http=')[1].trim().split(':')[0];
|
proxyRule.hostname = proxy.split('http=')[1].trim().split(':')[0];
|
||||||
proxyRule.port = proxy.split('http=')[1].trim().split(':')[1];
|
proxyRule.port = proxy.split('http=')[1].trim().split(':')[1];
|
||||||
@@ -50,7 +39,7 @@ class ProxyUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uri.protocol === 'https:') {
|
if (uri.protocol === 'https:') {
|
||||||
proxyRules.forEach((proxy: string) => {
|
proxyRules.forEach(proxy => {
|
||||||
if (proxy.includes('https=')) {
|
if (proxy.includes('https=')) {
|
||||||
proxyRule.hostname = proxy.split('https=')[1].trim().split(':')[0];
|
proxyRule.hostname = proxy.split('https=')[1].trim().split(':')[0];
|
||||||
proxyRule.port = proxy.split('https=')[1].trim().split(':')[1];
|
proxyRule.port = proxy.split('https=')[1].trim().split(':')[1];
|
||||||
@@ -60,15 +49,14 @@ class ProxyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor to async function
|
resolveSystemProxy(mainWindow) {
|
||||||
resolveSystemProxy(mainWindow: Electron.BrowserWindow): void {
|
|
||||||
const page = mainWindow.webContents;
|
const page = mainWindow.webContents;
|
||||||
const ses = page.session;
|
const ses = page.session;
|
||||||
const resolveProxyUrl = 'www.example.com';
|
const resolveProxyUrl = 'www.google.com';
|
||||||
|
|
||||||
// Check HTTP Proxy
|
// Check HTTP Proxy
|
||||||
const httpProxy = new Promise(resolve => {
|
const httpProxy = new Promise(resolve => {
|
||||||
ses.resolveProxy('http://' + resolveProxyUrl, (proxy: string) => {
|
ses.resolveProxy('http://' + resolveProxyUrl, proxy => {
|
||||||
let httpString = '';
|
let httpString = '';
|
||||||
if (proxy !== 'DIRECT') {
|
if (proxy !== 'DIRECT') {
|
||||||
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
|
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
|
||||||
@@ -82,12 +70,12 @@ class ProxyUtil {
|
|||||||
});
|
});
|
||||||
// Check HTTPS Proxy
|
// Check HTTPS Proxy
|
||||||
const httpsProxy = new Promise(resolve => {
|
const httpsProxy = new Promise(resolve => {
|
||||||
ses.resolveProxy('https://' + resolveProxyUrl, (proxy: string) => {
|
ses.resolveProxy('https://' + resolveProxyUrl, proxy => {
|
||||||
let httpsString = '';
|
let httpsString = '';
|
||||||
if (proxy !== 'DIRECT' || proxy.includes('HTTPS')) {
|
if (proxy !== 'DIRECT' || proxy.includes('HTTPS')) {
|
||||||
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
|
// 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
|
// for all other HTTP or direct url:port both uses PROXY
|
||||||
if (proxy.includes('PROXY') || proxy.includes('HTTPS')) {
|
if (proxy.includes('PROXY' || proxy.includes('HTTPS'))) {
|
||||||
httpsString += 'https=' + proxy.split('PROXY')[1] + ';';
|
httpsString += 'https=' + proxy.split('PROXY')[1] + ';';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +85,7 @@ class ProxyUtil {
|
|||||||
|
|
||||||
// Check FTP Proxy
|
// Check FTP Proxy
|
||||||
const ftpProxy = new Promise(resolve => {
|
const ftpProxy = new Promise(resolve => {
|
||||||
ses.resolveProxy('ftp://' + resolveProxyUrl, (proxy: string) => {
|
ses.resolveProxy('ftp://' + resolveProxyUrl, proxy => {
|
||||||
let ftpString = '';
|
let ftpString = '';
|
||||||
if (proxy !== 'DIRECT') {
|
if (proxy !== 'DIRECT') {
|
||||||
if (proxy.includes('PROXY')) {
|
if (proxy.includes('PROXY')) {
|
||||||
@@ -110,7 +98,7 @@ class ProxyUtil {
|
|||||||
|
|
||||||
// Check SOCKS Proxy
|
// Check SOCKS Proxy
|
||||||
const socksProxy = new Promise(resolve => {
|
const socksProxy = new Promise(resolve => {
|
||||||
ses.resolveProxy('socks4://' + resolveProxyUrl, (proxy: string) => {
|
ses.resolveProxy('socks4://' + resolveProxyUrl, proxy => {
|
||||||
let socksString = '';
|
let socksString = '';
|
||||||
if (proxy !== 'DIRECT') {
|
if (proxy !== 'DIRECT') {
|
||||||
if (proxy.includes('SOCKS5')) {
|
if (proxy.includes('SOCKS5')) {
|
||||||
@@ -139,4 +127,4 @@ class ProxyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new ProxyUtil();
|
module.exports = new ProxyUtil();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import isOnline = require('is-online');
|
const isOnline = require('is-online');
|
||||||
import Logger = require('./logger-util');
|
const Logger = require('./logger-util');
|
||||||
|
|
||||||
const logger = new Logger({
|
const logger = new Logger({
|
||||||
file: `domain-util.log`,
|
file: `domain-util.log`,
|
||||||
@@ -7,22 +7,16 @@ const logger = new Logger({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class ReconnectUtil {
|
class ReconnectUtil {
|
||||||
// TODO: TypeScript - Figure out how to annotate serverManagerView
|
constructor(serverManagerView) {
|
||||||
// it should be ServerManagerView; maybe make it a generic so we can
|
|
||||||
// pass the class from main.js
|
|
||||||
serverManagerView: any;
|
|
||||||
alreadyReloaded: boolean;
|
|
||||||
|
|
||||||
constructor(serverManagerView: any) {
|
|
||||||
this.serverManagerView = serverManagerView;
|
this.serverManagerView = serverManagerView;
|
||||||
this.alreadyReloaded = false;
|
this.alreadyReloaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearState(): void {
|
clearState() {
|
||||||
this.alreadyReloaded = false;
|
this.alreadyReloaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pollInternetAndReload(): void {
|
pollInternetAndReload() {
|
||||||
const pollInterval = setInterval(() => {
|
const pollInterval = setInterval(() => {
|
||||||
this._checkAndReload()
|
this._checkAndReload()
|
||||||
.then(status => {
|
.then(status => {
|
||||||
@@ -34,15 +28,14 @@ class ReconnectUtil {
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this a async function
|
_checkAndReload() {
|
||||||
_checkAndReload(): Promise<boolean> {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
|
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
|
||||||
isOnline()
|
isOnline()
|
||||||
.then((online: boolean) => {
|
.then(online => {
|
||||||
if (online) {
|
if (online) {
|
||||||
if (!this.alreadyReloaded) {
|
if (!this.alreadyReloaded) {
|
||||||
this.serverManagerView.reloadCurrentView();
|
this.serverManagerView.reloadView();
|
||||||
}
|
}
|
||||||
logger.log('You\'re back online.');
|
logger.log('You\'re back online.');
|
||||||
return resolve(true);
|
return resolve(true);
|
||||||
@@ -64,4 +57,4 @@ class ReconnectUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = ReconnectUtil;
|
module.exports = ReconnectUtil;
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import fs = require('fs');
|
|
||||||
import ConfigUtil = require('./config-util');
|
|
||||||
import Logger = require('./logger-util');
|
|
||||||
import ProxyUtil = require('./proxy-util');
|
|
||||||
import CertificateUtil = require('./certificate-util');
|
|
||||||
import SystemUtil = require('./system-util');
|
|
||||||
|
|
||||||
const logger = new Logger({
|
|
||||||
file: `request-util.log`,
|
|
||||||
timestamp: true
|
|
||||||
});
|
|
||||||
|
|
||||||
let instance: null | RequestUtil = null;
|
|
||||||
|
|
||||||
// TODO: TypeScript - Use ProxyRule for the proxy property
|
|
||||||
// we can do this now since we use export = ProxyUtil syntax
|
|
||||||
interface RequestUtilResponse {
|
|
||||||
ca: string;
|
|
||||||
proxy: string | void | object;
|
|
||||||
ecdhCurve: 'auto';
|
|
||||||
headers: { 'User-Agent': string };
|
|
||||||
rejectUnauthorized: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RequestUtil {
|
|
||||||
constructor() {
|
|
||||||
if (!instance) {
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignoreCerts parameter helps in fetching server icon and
|
|
||||||
// other server details when user chooses to ignore certificate warnings
|
|
||||||
requestOptions(domain: string, ignoreCerts: boolean): RequestUtilResponse {
|
|
||||||
domain = this.formatUrl(domain);
|
|
||||||
const certificate = CertificateUtil.getCertificate(
|
|
||||||
encodeURIComponent(domain)
|
|
||||||
);
|
|
||||||
let certificateLocation = '';
|
|
||||||
if (certificate) {
|
|
||||||
// To handle case where certificate has been moved from the location in certificates.json
|
|
||||||
try {
|
|
||||||
certificateLocation = fs.readFileSync(certificate, 'utf8');
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(`Error while trying to get certificate: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
|
|
||||||
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
|
|
||||||
// Add proxy as a parameter if it is being used.
|
|
||||||
return {
|
|
||||||
ca: certificateLocation ? certificateLocation : '',
|
|
||||||
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : '',
|
|
||||||
ecdhCurve: 'auto',
|
|
||||||
headers: { 'User-Agent': SystemUtil.getUserAgent() },
|
|
||||||
rejectUnauthorized: !ignoreCerts
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
formatUrl(domain: string): string {
|
|
||||||
const hasPrefix = domain.startsWith('http', 0);
|
|
||||||
if (hasPrefix) {
|
|
||||||
return domain;
|
|
||||||
} else {
|
|
||||||
return domain.includes('localhost:') ? `http://${domain}` : `https://${domain}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestUtil = new RequestUtil();
|
|
||||||
export = requestUtil;
|
|
||||||
20
app/renderer/js/utils/sentry-util.js
Normal file
20
app/renderer/js/utils/sentry-util.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const { init, captureException } = require('@sentry/electron');
|
||||||
|
const isDev = require('electron-is-dev');
|
||||||
|
|
||||||
|
const sentryInit = () => {
|
||||||
|
if (!isDev) {
|
||||||
|
init({
|
||||||
|
dsn: 'SENTRY_DSN',
|
||||||
|
// We should ignore this error since it's harmless and we know the reason behind this
|
||||||
|
// This error mainly comes from the console logs.
|
||||||
|
// This is a temp solution until Sentry supports disabling the console logs
|
||||||
|
ignoreErrors: ['does not appear to be a valid Zulip server'],
|
||||||
|
sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sentryInit,
|
||||||
|
captureException
|
||||||
|
};
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { init } from '@sentry/electron';
|
|
||||||
|
|
||||||
import isDev = require('electron-is-dev');
|
|
||||||
import path = require('path');
|
|
||||||
import dotenv = require('dotenv');
|
|
||||||
dotenv.config({ path: path.resolve(__dirname, '/../../../../.env') });
|
|
||||||
|
|
||||||
export const sentryInit = (): void => {
|
|
||||||
if (!isDev) {
|
|
||||||
init({
|
|
||||||
dsn: process.env.SENTRY_DSN,
|
|
||||||
// We should ignore this error since it's harmless and we know the reason behind this
|
|
||||||
// This error mainly comes from the console logs.
|
|
||||||
// This is a temp solution until Sentry supports disabling the console logs
|
|
||||||
ignoreErrors: ['does not appear to be a valid Zulip server']
|
|
||||||
// sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { captureException } from '@sentry/electron';
|
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { remote } from 'electron';
|
|
||||||
|
|
||||||
import os = require('os');
|
const {app} = require('electron').remote;
|
||||||
|
|
||||||
const { app } = remote;
|
const os = require('os');
|
||||||
let instance: null | SystemUtil = null;
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
class SystemUtil {
|
class SystemUtil {
|
||||||
connectivityERR: string[];
|
|
||||||
|
|
||||||
userAgent: string | null;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return instance;
|
return instance;
|
||||||
@@ -31,30 +27,29 @@ class SystemUtil {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOS(): string {
|
getOS() {
|
||||||
const platform = os.platform();
|
if (os.platform() === 'darwin') {
|
||||||
if (platform === 'darwin') {
|
|
||||||
return 'Mac';
|
return 'Mac';
|
||||||
} else if (platform === 'linux') {
|
}
|
||||||
|
if (os.platform() === 'linux') {
|
||||||
return 'Linux';
|
return 'Linux';
|
||||||
} else if (platform === 'win32') {
|
}
|
||||||
|
if (os.platform() === 'win32' || os.platform() === 'win64') {
|
||||||
if (parseFloat(os.release()) < 6.2) {
|
if (parseFloat(os.release()) < 6.2) {
|
||||||
return 'Windows 7';
|
return 'Windows 7';
|
||||||
} else {
|
} else {
|
||||||
return 'Windows 10';
|
return 'Windows 10';
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setUserAgent(webViewUserAgent: string): void {
|
setUserAgent(webViewUserAgent) {
|
||||||
this.userAgent = `ZulipElectron/${app.getVersion()} ${webViewUserAgent}`;
|
this.userAgent = 'ZulipElectron/' + app.getVersion() + ' ' + webViewUserAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserAgent(): string | null {
|
getUserAgent() {
|
||||||
return this.userAgent;
|
return this.userAgent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = new SystemUtil();
|
module.exports = new SystemUtil();
|
||||||
@@ -32,10 +32,6 @@
|
|||||||
<i class="material-icons md-48">refresh</i>
|
<i class="material-icons md-48">refresh</i>
|
||||||
<span id="reload-tooltip" style="display:none">Reload</span>
|
<span id="reload-tooltip" style="display:none">Reload</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-button" id="loading-action">
|
|
||||||
<i class="refresh material-icons md-48">loop</i>
|
|
||||||
<span id="loading-tooltip" style="display:none">Loading</span>
|
|
||||||
</div>
|
|
||||||
<div class="action-button disable" id="back-action">
|
<div class="action-button disable" id="back-action">
|
||||||
<i class="material-icons md-48">arrow_back</i>
|
<i class="material-icons md-48">arrow_back</i>
|
||||||
<span id="back-tooltip" style="display:none">Go Back</span>
|
<span id="back-tooltip" style="display:none">Go Back</span>
|
||||||
@@ -55,11 +51,6 @@
|
|||||||
<send-feedback></send-feedback>
|
<send-feedback></send-feedback>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
<script>
|
|
||||||
// we don't use src='./js/main' in the script tag because
|
|
||||||
// it messes up require module path resolution
|
|
||||||
require('./js/main');
|
|
||||||
</script>
|
|
||||||
<script>require('./js/shared/preventdrag.js')</script>
|
<script>require('./js/shared/preventdrag.js')</script>
|
||||||
</html>
|
</html>
|
||||||
@@ -12,8 +12,6 @@
|
|||||||
<div id="settings-container"></div>
|
<div id="settings-container"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script src="js/pages/preference/preference.js"></script>
|
||||||
require('./js/pages/preference/preference.js');
|
<script>require('./js/shared/preventdrag.js')</script>
|
||||||
require('./js/shared/preventdrag.js')
|
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
class Messages {
|
|
||||||
invalidZulipServerError(domain: string): string {
|
|
||||||
return `${domain} does not appear to be a valid Zulip server. Make sure that
|
|
||||||
\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).`;
|
|
||||||
}
|
|
||||||
|
|
||||||
noOrgsError(domain: string): string {
|
|
||||||
return `${domain} does not have any organizations added.\
|
|
||||||
\nPlease contact your server administrator.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
certErrorMessage(domain: string, error: string): string {
|
|
||||||
return `Do you trust certificate from ${domain}? \n ${error}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
\nUnless you have a good reason to believe otherwise, you should not proceed.
|
|
||||||
\nYou can click here if you'd like to proceed with the connection.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export = new Messages();
|
|
||||||
BIN
build/icon.png
Normal file
BIN
build/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
119
changelog.md
119
changelog.md
@@ -2,111 +2,6 @@
|
|||||||
|
|
||||||
All notable changes to the Zulip desktop app are documented in this file.
|
All notable changes to the Zulip desktop app are documented in this file.
|
||||||
|
|
||||||
### v3.1.0-beta --2019-07-19
|
|
||||||
|
|
||||||
**New features**:
|
|
||||||
* Add option to find accounts by email.
|
|
||||||
* Add option to hide Menu bar to View menu.
|
|
||||||
* Show a loading indicator in the sidebar.
|
|
||||||
* Update Help Center to open help page of the currently active server.
|
|
||||||
* Improve auto-detection of spellchecker language.
|
|
||||||
* Disable menu items on non-server pages.
|
|
||||||
* Support dark mode on macOS.
|
|
||||||
|
|
||||||
**Fixes**:
|
|
||||||
* Updated, more robust server validation logic.
|
|
||||||
* Fix JSON DB errors observed when switching tabs.
|
|
||||||
* Remove unused `isLoading` function from `Tab`.
|
|
||||||
* Remove unused `defaultId` parameter.
|
|
||||||
* Fix syntax error in `proxy-util.js`.
|
|
||||||
* Fix issue with creation of large `.node` files in the `Temp` folder on Windows machines.
|
|
||||||
* Fix issue where drafts were not saved properly.
|
|
||||||
|
|
||||||
**Development**:
|
|
||||||
* Migrate codebase to TypeScript.
|
|
||||||
* Set the indent_size in `.editconfig` to 4.
|
|
||||||
* Use `.env` file for reading Sentry DSN.
|
|
||||||
|
|
||||||
**Documentation**:
|
|
||||||
* Improve development guide.
|
|
||||||
|
|
||||||
**Module updates**:
|
|
||||||
* Upgrade xo to v0.24.0.
|
|
||||||
* Upgrade node-json-db to v0.9.2.
|
|
||||||
* Upgrade electron to v3.1.10.
|
|
||||||
* Add missing transitive dependencies.
|
|
||||||
|
|
||||||
### v3.0.0 --2019-05-20
|
|
||||||
|
|
||||||
**New features**:
|
|
||||||
* Add context menu in left sidebar.
|
|
||||||
* Enable per-user installation on Windows.
|
|
||||||
* Switch to next server on Ctrl+Tab.
|
|
||||||
* Add option to copy zulip URL.
|
|
||||||
* Allow zoom options from numpad.
|
|
||||||
* Use server language for spellchecker for all platforms.
|
|
||||||
* Allow installing app without admin privileges.
|
|
||||||
* Allow insecure requests on user request.
|
|
||||||
* Unify case across menus and settings.
|
|
||||||
|
|
||||||
**Enhancements**:
|
|
||||||
* Remove Found bug button.
|
|
||||||
* Set custom css to false by default.
|
|
||||||
* Disable beta updates if auto updates disabled
|
|
||||||
* Update menu items on setting page.
|
|
||||||
* Include certificates in all requests for icon.
|
|
||||||
* Document show sidebar shortcut properly.
|
|
||||||
* Improve organization page.
|
|
||||||
* Improve wording of adding a new org button.
|
|
||||||
* Increase width of add a new org button.
|
|
||||||
* Add eol for linebreaks on windows.
|
|
||||||
* Teach git to ignore unnecessary binary files.
|
|
||||||
* Send user-agent with request.
|
|
||||||
* Minimize to tray on startup.
|
|
||||||
* Update test config files.
|
|
||||||
* Ensure backward compatibility when using narrow.by_topic.
|
|
||||||
* Use path.sep for path separator to support Windows.
|
|
||||||
* Change the window title to contain active Realm's name.
|
|
||||||
* Use path.basename to get certificate file name.
|
|
||||||
* Disable pdf-viewer window.
|
|
||||||
* Default to starting app on login.
|
|
||||||
* Modify reset app data button.
|
|
||||||
* Add requestOptions to replace request instances.
|
|
||||||
* Workaround buggy focus switching in Electron 3.0.10.
|
|
||||||
* Reorder file menu and add option to Add Organization.
|
|
||||||
* Improve development guide.
|
|
||||||
* Implement CSS linting with stylelint.
|
|
||||||
* Add "role" key to webview property.
|
|
||||||
* Implement HTML Linting with htmlhint and fix indent.
|
|
||||||
* Limit the number of lines in log files.
|
|
||||||
* Fix focus after clicking back button.
|
|
||||||
* Remove minimize and close from File menu.
|
|
||||||
* Add config for installer name.
|
|
||||||
|
|
||||||
**Fixes**:
|
|
||||||
* Fix `request` ecdhCurve mismatch errors
|
|
||||||
* Fix typo in network error message.
|
|
||||||
* Fix context menu not working on adding new org.
|
|
||||||
* Fix reply from notification.
|
|
||||||
* Fix shorcut section horizontal alignment.
|
|
||||||
* Fix broken link in docs.
|
|
||||||
* Fix grammatical errors.
|
|
||||||
* Fix typo error in issue template.
|
|
||||||
* Fix text for Toggle DND in sidebar on hover.
|
|
||||||
* Fix focus after soft reload.
|
|
||||||
* Fix tip's place for Windows & Linux.
|
|
||||||
|
|
||||||
|
|
||||||
**Module updates**:
|
|
||||||
* Update node-json-db to v0.9.1.
|
|
||||||
* Update sentry to v0.12.1.
|
|
||||||
* Update electron-window-state to v5.0.3.
|
|
||||||
* Update electron to v3.0.10.
|
|
||||||
* Update electron-builder to v20.40.2.
|
|
||||||
* Update electron-sentry to v0.14.0.
|
|
||||||
* Update dependencies to fix minor dev security alerts.
|
|
||||||
* Update snap config.
|
|
||||||
|
|
||||||
### v2.3.82 --2018-09-25
|
### v2.3.82 --2018-09-25
|
||||||
|
|
||||||
**New features**:
|
**New features**:
|
||||||
@@ -397,7 +292,7 @@ electron-updater - `v2.21.8`
|
|||||||
|
|
||||||
* Setting page and left-sidebar UI improvements
|
* Setting page and left-sidebar UI improvements
|
||||||
|
|
||||||
* Other minor improvements
|
* Other minor improvments
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -524,7 +419,7 @@ electron-updater - `v2.21.8`
|
|||||||
|
|
||||||
* Fixed a bug which was caused by app's shortcuts. From now on our shortcuts won't hijack other apps shortcuts
|
* Fixed a bug which was caused by app's shortcuts. From now on our shortcuts won't hijack other apps shortcuts
|
||||||
|
|
||||||
* Removed [electron-localshortcut](https://github.com/parro-it/electron-localshortcut) completely. Now we only depend on menu accelerators for keyboard shortcuts
|
* Removed [electron-localshortcut](https://github.com/parro-it/electron-localshortcut) completely. Now we only depends on menu accelerators for keyboard shortcuts
|
||||||
|
|
||||||
* Handle certificate issue properly
|
* Handle certificate issue properly
|
||||||
|
|
||||||
@@ -725,7 +620,7 @@ electron-updater - `v2.21.8`
|
|||||||
|
|
||||||
* Properly signed app for macOS
|
* Properly signed app for macOS
|
||||||
* Toggle tray icon
|
* Toggle tray icon
|
||||||
* Better error handling when no internet connection
|
* Better error handling when no internet connenction
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -739,7 +634,7 @@ electron-updater - `v2.21.8`
|
|||||||
|
|
||||||
### v0.5.8 --2017-02-13
|
### v0.5.8 --2017-02-13
|
||||||
|
|
||||||
**Enhancements**:
|
**Enhacements**:
|
||||||
Smaller Windows installer size
|
Smaller Windows installer size
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
@@ -755,7 +650,7 @@ Minor improvements
|
|||||||
### v0.5.6 --2017-02-07
|
### v0.5.6 --2017-02-07
|
||||||
|
|
||||||
|
|
||||||
**Enhancement**:
|
**Enhacements**:
|
||||||
|
|
||||||
- Using NSIS instead of [Squirrel.Windows](https://github.com/Squirrel/Squirrel.Windows) on Windows
|
- Using NSIS instead of [Squirrel.Windows](https://github.com/Squirrel/Squirrel.Windows) on Windows
|
||||||
|
|
||||||
@@ -823,7 +718,7 @@ Minor improvements
|
|||||||
|
|
||||||
### v0.5.1 --2016-11-23
|
### v0.5.1 --2016-11-23
|
||||||
|
|
||||||
**Enhancement**:
|
**Enhacements**:
|
||||||
|
|
||||||
- Added Spellchecker support with correct spell suggestions
|
- Added Spellchecker support with correct spell suggestions
|
||||||
|
|
||||||
@@ -864,6 +759,6 @@ Minor improvements
|
|||||||
|
|
||||||
### v0.0.1-alpha -- 2016-08-31
|
### v0.0.1-alpha -- 2016-08-31
|
||||||
|
|
||||||
**Enhancement**:
|
**Enhacements**:
|
||||||
|
|
||||||
* Added DMG installer for macOS
|
* Added DMG installer for macOS
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Development Setup
|
# Development setup
|
||||||
|
|
||||||
This is a guide to running the Zulip desktop app from source,
|
This is a guide to running the Zulip desktop app from a source tree,
|
||||||
in order to contribute to developing it.
|
in order to contribute to developing it.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
@@ -8,7 +8,6 @@ in order to contribute to developing it.
|
|||||||
To build and run the app from source, you'll need the following:
|
To build and run the app from source, you'll need the following:
|
||||||
|
|
||||||
* [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
* [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||||
* Use our [Git Guide](https://zulip.readthedocs.io/en/latest/git/setup.html) to get started with Git and GitHub.
|
|
||||||
* [Node.js](https://nodejs.org) >= v6.9.0
|
* [Node.js](https://nodejs.org) >= v6.9.0
|
||||||
* [NPM](https://www.npmjs.com/get-npm) and
|
* [NPM](https://www.npmjs.com/get-npm) and
|
||||||
[node-gyp](https://github.com/nodejs/node-gyp#installation),
|
[node-gyp](https://github.com/nodejs/node-gyp#installation),
|
||||||
@@ -16,50 +15,35 @@ To build and run the app from source, you'll need the following:
|
|||||||
* [Python](https://www.python.org/downloads/release/python-2713/)
|
* [Python](https://www.python.org/downloads/release/python-2713/)
|
||||||
(v2.7.x recommended)
|
(v2.7.x recommended)
|
||||||
* A C++ compiler compatible with C++11
|
* A C++ compiler compatible with C++11
|
||||||
* Development headers for the libXext, libXtst, and libxkbfile libraries, which can be installed using apt on Ubuntu using
|
* Linux users also need [Snapcraft](https://snapcraft.io/)
|
||||||
```sh
|
* Development headers for the libXext, libXtst, and libxkbfile libraries
|
||||||
$ sudo apt install libxext-dev libxtst-dev libxkbfile-dev libgconf-2-4
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ubuntu/Linux and other Debian-based distributions
|
### Debian/Ubuntu and friends
|
||||||
|
|
||||||
On a system running Debian, Ubuntu, or another Debian-based Linux
|
On a system running Debian, Ubuntu, or another Debian-based Linux
|
||||||
distribution, you can install all dependencies through the package
|
distribution, you can install all dependencies through the package
|
||||||
manager (see [here][node-debian] for more on the first command):
|
manager (see [here][nodesource-install] for more on the first command):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
|
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
|
||||||
$ sudo apt install git nodejs python build-essential snapcraft libxext-dev libxtst-dev libxkbfile-dev libgconf-2-4
|
$ sudo apt install git nodejs python build-essential snapcraft libxext-dev libxtst-dev libxkbfile-dev libgconf-2-4
|
||||||
```
|
```
|
||||||
|
|
||||||
[node-debian]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
|
[nodesource-install]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
|
||||||
|
|
||||||
### MacOS
|
### Other OSes
|
||||||
|
|
||||||
On a system running MacOS, you can refer to [official nodejs docs][node-mac] to
|
Other developers run the app on Windows, macOS, and possibly other OSes.
|
||||||
install nodejs. To ensure Node.js has been installed, run ```node -v``` in terminal to know your node version.
|
PRs to add specific instructions to this doc are welcome!
|
||||||
|
|
||||||
[node-mac]: https://nodejs.org/en/download/package-manager/#macos
|
On Windows, your C++ compiler should be Visual Studio 2015 or later.
|
||||||
|
|
||||||
If [NPM](https://www.npmjs.com/get-npm) and [node-gyp](https://github.com/nodejs/node-gyp#installation) don't come bundled with your Node.js installation, you will need to install them manually.
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
- Download Node.js for Windows and install it. You can refer to the official docs [here][node-windows] to do so. To ensure Node.js has been installed, run ```node -v``` in Git Bash to know your node version.
|
|
||||||
|
|
||||||
[node-windows]: https://nodejs.org/en/download/package-manager/#windows
|
|
||||||
|
|
||||||
- Also, install install Windows-Build-Tools to compile native node modules by using
|
|
||||||
```sh
|
|
||||||
$ npm install --global windows-build-tools
|
|
||||||
```
|
|
||||||
|
|
||||||
## Download, build, and run
|
## Download, build, and run
|
||||||
|
|
||||||
Clone the source locally:
|
Clone the source locally:
|
||||||
```sh
|
```sh
|
||||||
$ git clone https://github.com/zulip/zulip-desktop
|
$ git clone https://github.com/zulip/zulip-electron
|
||||||
$ cd zulip-desktop
|
$ cd zulip-electron
|
||||||
```
|
```
|
||||||
|
|
||||||
Install project dependencies:
|
Install project dependencies:
|
||||||
@@ -77,18 +61,6 @@ Start and watch changes:
|
|||||||
$ npm run dev
|
$ npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tests:
|
|
||||||
```sh
|
|
||||||
$ npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
## How to contribute?
|
|
||||||
|
|
||||||
Feel free to fork this repository, test it locally and then report any bugs
|
|
||||||
you find in the [issue tracker](https://github.com/zulip/zulip-desktop/issues).
|
|
||||||
|
|
||||||
You can read more about making contributions in our [Contributing Guide](./CONTRIBUTING.md).
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
If you have any problems running the app, see the [most common
|
If you have any problems running the app, see the [most common
|
||||||
@@ -103,12 +75,11 @@ npm run dist
|
|||||||
|
|
||||||
This command will produce distributable packages or installers for the
|
This command will produce distributable packages or installers for the
|
||||||
operating system you're running on:
|
operating system you're running on:
|
||||||
* on Windows, a .7z nsis file and a .exe WebSetup file
|
* on Windows, a Windows installer file
|
||||||
* on macOS, a `.dmg` file
|
* on macOS, a `.dmg` file
|
||||||
* on Linux, a plain `.zip` file as well as a `.deb` file, `.snap` file and an
|
* on Linux, a plain `.zip` file as well as a `.deb` file, `.snap` file and an
|
||||||
`AppImage` file.
|
`AppImage` file.
|
||||||
|
To generate all three types, you will need all three operating
|
||||||
To generate all three types of files, you will need all three operating
|
|
||||||
systems.
|
systems.
|
||||||
|
|
||||||
The output distributable packages appear in the `dist/` directory.
|
The output files appear in the `dist/` directory.
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ C:\Windows\system32> npm install --global --production windows-build-tools
|
|||||||
Clone the source locally:
|
Clone the source locally:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ git clone https://github.com/zulip/zulip-desktop
|
$ git clone https://github.com/zulip/zulip-electron
|
||||||
$ cd zulip-desktop
|
$ cd zulip-electron
|
||||||
```
|
```
|
||||||
|
|
||||||
Install project dependencies:
|
Install project dependencies:
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
### Want to contribute to this Wiki?
|
### 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-electron/tree/master/docs)
|
||||||
|
|||||||
25
gulpfile.js
25
gulpfile.js
@@ -5,37 +5,16 @@ const electron = require('electron-connect').server.create({
|
|||||||
});
|
});
|
||||||
const tape = require('gulp-tape');
|
const tape = require('gulp-tape');
|
||||||
const tapColorize = require('tap-colorize');
|
const tapColorize = require('tap-colorize');
|
||||||
const ts = require("gulp-typescript");
|
|
||||||
const tsProject = ts.createProject("tsconfig.json");
|
|
||||||
|
|
||||||
const glob = require('glob');
|
|
||||||
const { execSync } = require('child_process');
|
|
||||||
|
|
||||||
const baseFilePattern = 'app/+(main|renderer)/**/*';
|
|
||||||
const globOptions = { cwd: __dirname };
|
|
||||||
const jsFiles = glob.sync(baseFilePattern + '.js', globOptions);
|
|
||||||
const tsFiles = glob.sync(baseFilePattern + '.ts', globOptions);
|
|
||||||
if (jsFiles.length !== tsFiles.length) {
|
|
||||||
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
||||||
console.log('Compiling typescript files...');
|
|
||||||
execSync(`${npx} tsc`);
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task("compile", function () {
|
|
||||||
return tsProject.src()
|
|
||||||
.pipe(tsProject())
|
|
||||||
.js.pipe(gulp.dest("app"));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('dev', () => {
|
gulp.task('dev', () => {
|
||||||
// Start browser process
|
// Start browser process
|
||||||
electron.start();
|
electron.start();
|
||||||
// Restart browser process
|
// Restart browser process
|
||||||
gulp.watch('app/main/*.ts', gulp.series('compile', 'restart:browser'));
|
gulp.watch('app/main/*.js', gulp.series('restart:browser'));
|
||||||
// Reload renderer process
|
// Reload renderer process
|
||||||
gulp.watch('app/renderer/css/*.css', gulp.series('reload:renderer'));
|
gulp.watch('app/renderer/css/*.css', gulp.series('reload:renderer'));
|
||||||
gulp.watch('app/renderer/*.html', gulp.series('reload:renderer'));
|
gulp.watch('app/renderer/*.html', gulp.series('reload:renderer'));
|
||||||
gulp.watch('app/renderer/js/**/*.ts', gulp.series('compile', 'reload:renderer'));
|
gulp.watch('app/renderer/js/**/*.js', gulp.series('reload:renderer'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('restart:browser', done => {
|
gulp.task('restart:browser', done => {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# How to install
|
# How to install
|
||||||
|
|
||||||
**Note:** If you download from the [releases page](https://github.com/zulip/zulip-desktop/releases), be careful what version you pick. Releases that end with `-beta` are beta releases and the rest are stable.
|
**Note:** If you download from the [releases page](https://github.com/zulip/zulip-electron/releases), be careful what version you pick. Releases that end with `-beta` are beta releases and the rest are stable.
|
||||||
- **beta:** these releases are the right balance between getting new features early while staying away from nasty bugs.
|
- **beta:** these releases are the right balance between getting new features early while staying away from nasty bugs.
|
||||||
- **stable:** these releases are more thoroughly tested; they receive new features later, but there's a lower chance that things will go wrong.
|
- **stable:** these releases are more thoroughly tested; they receive new features later, but there's a lower chance that things will go wrong.
|
||||||
|
|
||||||
[LR]: https://github.com/zulip/zulip-desktop/releases
|
[LR]: https://github.com/zulip/zulip-electron/releases
|
||||||
|
|
||||||
## OS X
|
## OS X
|
||||||
|
|
||||||
|
|||||||
5116
package-lock.json
generated
5116
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
98
package.json
98
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "zulip",
|
"name": "zulip",
|
||||||
"productName": "Zulip",
|
"productName": "Zulip",
|
||||||
"version": "3.1.0-beta",
|
"version": "2.5.0",
|
||||||
"main": "./app/main",
|
"main": "./app/main",
|
||||||
"description": "Zulip Desktop App",
|
"description": "Zulip Desktop App",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@@ -12,30 +12,24 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zulip/zulip-desktop.git"
|
"url": "https://github.com/zulip/zulip-electron.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/zulip/zulip-desktop/issues"
|
"url": "https://github.com/zulip/zulip-electron/issues"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node tools/run-dev",
|
"start": "electron app --disable-http-cache --no-electron-connect",
|
||||||
"clean-ts-files": "git clean app/*.js -xf",
|
|
||||||
"watch-ts": "tsc -w",
|
|
||||||
"reinstall": "node ./tools/reinstall-node-modules.js",
|
"reinstall": "node ./tools/reinstall-node-modules.js",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"lint-css": "stylelint app/renderer/css/*.css",
|
"test": "xo",
|
||||||
"lint-html": "./node_modules/.bin/htmlhint \"app/renderer/*.html\" ",
|
|
||||||
"lint-js": "xo",
|
|
||||||
"test": "npm run lint-html && npm run lint-css && npm run lint-js",
|
|
||||||
"test-e2e": "gulp test-e2e",
|
"test-e2e": "gulp test-e2e",
|
||||||
"compile": "gulp compile",
|
"dev": "gulp dev & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js",
|
||||||
"dev": "gulp dev && npm test",
|
"pack": "electron-builder --dir",
|
||||||
"pack": "tsc && electron-builder --dir",
|
"dist": "electron-builder",
|
||||||
"dist": "tsc && electron-builder",
|
"mas": "electron-builder --mac mas",
|
||||||
"mas": "tsc && electron-builder --mac mas",
|
|
||||||
"travis": "cd ./scripts && ./travis-build-test.sh",
|
"travis": "cd ./scripts && ./travis-build-test.sh",
|
||||||
"build-locales": "node tools/locale-helper"
|
"build-locales": "node tools/locale-helper"
|
||||||
},
|
},
|
||||||
@@ -45,18 +39,14 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"appId": "org.zulip.zulip-electron",
|
"appId": "org.zulip.zulip-electron",
|
||||||
"asar": true,
|
"asar": true,
|
||||||
"asarUnpack": [
|
|
||||||
"**/*.node"
|
|
||||||
],
|
|
||||||
"files": [
|
"files": [
|
||||||
"**/*",
|
"**/*",
|
||||||
"!docs${/*}",
|
"!docs${/*}",
|
||||||
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
|
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
|
||||||
],
|
],
|
||||||
"copyright": "©2019 Kandra Labs, Inc.",
|
"copyright": "©2017 Kandra Labs, Inc.",
|
||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.productivity",
|
"category": "public.app-category.productivity",
|
||||||
"darkModeSupport": true,
|
|
||||||
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
@@ -69,7 +59,7 @@
|
|||||||
"AppImage",
|
"AppImage",
|
||||||
"snap"
|
"snap"
|
||||||
],
|
],
|
||||||
"maintainer": "Akash Nimare <akash@zulipchat.com>",
|
"maintainer": "Akash Nimare <svnitakash@gmail.com>",
|
||||||
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
||||||
},
|
},
|
||||||
"deb": {
|
"deb": {
|
||||||
@@ -113,13 +103,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "build/icon.ico",
|
"icon": "build/icon.ico",
|
||||||
"artifactName": "${productName}-Web-Setup-${version}.${ext}",
|
|
||||||
"publisherName": "Kandra Labs, Inc."
|
"publisherName": "Kandra Labs, Inc."
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"allowToChangeInstallationDirectory": true,
|
"perMachine": true,
|
||||||
"oneClick": false,
|
"oneClick": false,
|
||||||
"perMachine": false
|
"allowToChangeInstallationDirectory": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -131,56 +120,40 @@
|
|||||||
"InstantMessaging"
|
"InstantMessaging"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/adm-zip": "^0.4.32",
|
|
||||||
"@types/dotenv": "6.1.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "1.10.2",
|
|
||||||
"@typescript-eslint/parser": "1.10.2",
|
|
||||||
"assert": "1.4.1",
|
"assert": "1.4.1",
|
||||||
"cp-file": "5.0.0",
|
"cp-file": "^5.0.0",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron": "3.1.10",
|
"electron": "3.0.10",
|
||||||
"electron-builder": "20.40.2",
|
"electron-builder": "20.28.3",
|
||||||
"electron-connect": "0.6.2",
|
"electron-connect": "0.6.2",
|
||||||
"electron-debug": "1.4.0",
|
"electron-debug": "1.4.0",
|
||||||
"eslint-config-xo-typescript": "0.14.0",
|
|
||||||
"fs-extra": "8.1.0",
|
|
||||||
"google-translate-api": "2.3.0",
|
"google-translate-api": "2.3.0",
|
||||||
"gulp": "4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-tape": "0.0.9",
|
"gulp-tape": "0.0.9",
|
||||||
"gulp-typescript": "5.0.1",
|
"is-ci": "^1.0.10",
|
||||||
"htmlhint": "0.11.0",
|
"nodemon": "^1.14.11",
|
||||||
"is-ci": "1.0.10",
|
|
||||||
"nodemon": "1.14.11",
|
|
||||||
"pre-commit": "1.2.2",
|
"pre-commit": "1.2.2",
|
||||||
"spectron": "5.0.0",
|
"spectron": "^5.0.0",
|
||||||
"stylelint": "9.10.1",
|
"tap-colorize": "^1.2.0",
|
||||||
"tap-colorize": "1.2.0",
|
"tape": "^4.8.0",
|
||||||
"tape": "4.8.0",
|
"xo": "0.18.2"
|
||||||
"typescript": "3.5.2",
|
|
||||||
"xo": "0.24.0"
|
|
||||||
},
|
},
|
||||||
"xo": {
|
"xo": {
|
||||||
"extends": "xo-typescript",
|
|
||||||
"extensions": [
|
|
||||||
"ts"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 6,
|
"sourceType": "script",
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"globalReturn": true,
|
"globalReturn": true
|
||||||
"modules": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"esnext": true,
|
"esnext": true,
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": "app/**/*.ts",
|
"files": "app*/**/*.js",
|
||||||
"rules": {
|
"rules": {
|
||||||
"max-lines": [
|
"max-lines": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
"max": 800,
|
"max": 600,
|
||||||
"skipBlankLines": true,
|
"skipBlankLines": true,
|
||||||
"skipComments": true
|
"skipComments": true
|
||||||
}
|
}
|
||||||
@@ -195,26 +168,13 @@
|
|||||||
"prefer-promise-reject-errors": 0,
|
"prefer-promise-reject-errors": 0,
|
||||||
"import/no-unresolved": 0,
|
"import/no-unresolved": 0,
|
||||||
"import/no-extraneous-dependencies": 0,
|
"import/no-extraneous-dependencies": 0,
|
||||||
"no-prototype-builtins": 0,
|
"no-prototype-builtins": 0
|
||||||
"lines-between-class-members": 0,
|
|
||||||
"padding-line-between-statements": 0,
|
|
||||||
"quotes": 0,
|
|
||||||
"unicorn/catch-error-name": 0,
|
|
||||||
"unicorn/no-console-spaces": 0,
|
|
||||||
"node/no-deprecated-api": 0,
|
|
||||||
"@typescript-eslint/member-ordering": "never",
|
|
||||||
"@typescript-eslint/restrict-plus-operands": "never",
|
|
||||||
"import/default": 0,
|
|
||||||
"@typescript-eslint/no-unused-vars": 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"tests/*.js",
|
"tests/*.js",
|
||||||
"tools/locale-helper/*.js",
|
"tools/locale-helper/*.js"
|
||||||
"*/**/*.js",
|
|
||||||
"*.js",
|
|
||||||
"typings.d.ts"
|
|
||||||
],
|
],
|
||||||
"envs": [
|
"envs": [
|
||||||
"node",
|
"node",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: zulip
|
name: zulip
|
||||||
version: 2.3.82
|
version: 2.0.0
|
||||||
summary: Zulip Desktop Client for Linux
|
summary: Zulip Desktop Client for Linux
|
||||||
description: Zulip combines the immediacy of Slack with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones.
|
description: Zulip combines the immediacy of Slack with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones.
|
||||||
confinement: strict
|
confinement: strict
|
||||||
@@ -34,4 +34,4 @@ parts:
|
|||||||
- libxtst6
|
- libxtst6
|
||||||
source: dist/linux-unpacked
|
source: dist/linux-unpacked
|
||||||
after:
|
after:
|
||||||
- desktop-gtk3
|
- desktop-gtk2
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const glob = require('glob');
|
|
||||||
const chalk = require('chalk');
|
|
||||||
const { spawn } = require('child_process');
|
|
||||||
|
|
||||||
async function run(task, commandToRun, opts = {}) {
|
|
||||||
const args = commandToRun.split(' ');
|
|
||||||
let cmd = args[0];
|
|
||||||
args.splice(0, 1);
|
|
||||||
|
|
||||||
if (process.platform === 'win32' && /np(m|x)/.test(cmd)) {
|
|
||||||
cmd = cmd + '.cmd';
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaults = {
|
|
||||||
cwd: path.resolve(__dirname, '..'),
|
|
||||||
env: process.env,
|
|
||||||
stdio: 'pipe'
|
|
||||||
};
|
|
||||||
|
|
||||||
opts = { ...defaults, ...opts };
|
|
||||||
task = ' ' + task + ' ';
|
|
||||||
|
|
||||||
const colors = [
|
|
||||||
{ bg: 'bold.bgGreen', color: 'bold.green' },
|
|
||||||
{ bg: 'bold.bgMagenta', color: 'bold.magenta' },
|
|
||||||
{ bg: 'bold.bgBlue', color: 'bold.blue' },
|
|
||||||
{ bg: 'bold.bgYellow', color: 'bold.yellow' },
|
|
||||||
{ bg: 'bold.bgCyan', color: 'bold.cyan' },
|
|
||||||
{ bg: 'bold.bgKeyword("rebeccapurple")', color: 'bold.keyword("rebeccapurple")' },
|
|
||||||
{ bg: 'bold.bgKeyword("darkslategray")', color: 'bold.keyword("darkslategray")' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const randomColorIndex = Math.floor((Math.random() * (colors.length * 1000)) / 1000);
|
|
||||||
const randomColor = colors[randomColorIndex];
|
|
||||||
console.log(chalk`{${randomColor.bg} ${ task }} {${randomColor.color} ${commandToRun}}`);
|
|
||||||
|
|
||||||
const proc = spawn(cmd, args, opts);
|
|
||||||
proc.stderr.on('data', data => console.log(data.toString()));
|
|
||||||
proc.stdout.on('data', data => console.log(data.toString()));
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
function check(code) {
|
|
||||||
if (code !== 0) {
|
|
||||||
reject(chalk`{bgRed ERROR!} Running {red ${commandToRun}} failed with exit code: ${code}`);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
// we don't want to exit after compiling typescript files
|
|
||||||
// but instead want to do it if the tsc -w or electron app died.
|
|
||||||
if (commandToRun !== 'npx tsc') {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proc.on('exit', check);
|
|
||||||
proc.on('close', check);
|
|
||||||
proc.on('error', () => {
|
|
||||||
reject();
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const baseFilePattern = 'app/+(main|renderer)/**/*';
|
|
||||||
const globOptions = { cwd: path.resolve(__dirname, '..') };
|
|
||||||
const jsFiles = glob.sync(baseFilePattern + '.js', globOptions);
|
|
||||||
const tsFiles = glob.sync(baseFilePattern + '.ts', globOptions);
|
|
||||||
|
|
||||||
|
|
||||||
// if the are missing compiled js file for typescript files
|
|
||||||
// run the typescript compiler before starting the app
|
|
||||||
if (jsFiles.length !== tsFiles.length) {
|
|
||||||
console.log('Compiling typescript files...');
|
|
||||||
await run('TypeScript', 'npx tsc');
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
run('Electron app', 'npx electron app --disable-http-cache --no-electron-connect'),
|
|
||||||
run('TypeScript watch mode', 'npx tsc --watch --pretty')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
.catch(err => console.error(err));
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "esnext",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"module": "commonjs",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Strict type-checking */
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"strictNullChecks": false,
|
|
||||||
"noImplicitThis": true,
|
|
||||||
"alwaysStrict": true,
|
|
||||||
|
|
||||||
/* Additional checks */
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noImplicitReturns": true
|
|
||||||
},
|
|
||||||
"include":[
|
|
||||||
"app/**/*",
|
|
||||||
"typings.d.ts"
|
|
||||||
],
|
|
||||||
"exclude":[
|
|
||||||
"node_modules",
|
|
||||||
"app/node_modules"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
54
typings.d.ts
vendored
54
typings.d.ts
vendored
@@ -1,54 +0,0 @@
|
|||||||
declare module 'adm-zip';
|
|
||||||
declare module 'auto-launch';
|
|
||||||
declare module 'is-online';
|
|
||||||
declare module 'request';
|
|
||||||
declare module 'semver';
|
|
||||||
declare module '@electron-elements/send-feedback';
|
|
||||||
declare module 'node-mac-notifier';
|
|
||||||
declare module 'electron-connect';
|
|
||||||
declare module 'electron-is-dev';
|
|
||||||
declare module 'electron-spellchecker';
|
|
||||||
declare module 'escape-html';
|
|
||||||
declare module 'fs-extra';
|
|
||||||
declare module 'wurl';
|
|
||||||
|
|
||||||
interface PageParamsObject {
|
|
||||||
realm_uri: string;
|
|
||||||
default_language: string;
|
|
||||||
}
|
|
||||||
declare var page_params: PageParamsObject;
|
|
||||||
|
|
||||||
// since requestIdleCallback didn't make it into lib.dom.d.ts yet
|
|
||||||
declare function requestIdleCallback(callback: Function, options?: object): void;
|
|
||||||
|
|
||||||
// Patch Notification object so we can implement our side
|
|
||||||
// of Notification classes which we export into zulip side through
|
|
||||||
// preload.js; if we don't do his extending Notification will throw error.
|
|
||||||
// Relevant code is in app/renderer/js/notification/default-notification.ts
|
|
||||||
// and the relevant function is requestPermission.
|
|
||||||
declare var PatchedNotification: {
|
|
||||||
prototype: Notification;
|
|
||||||
new(title: string, options?: NotificationOptions): Notification;
|
|
||||||
readonly maxActions: number;
|
|
||||||
readonly permission: NotificationPermission;
|
|
||||||
requestPermission(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is mostly zulip side of code we access from window
|
|
||||||
interface Window {
|
|
||||||
$: any;
|
|
||||||
narrow: any
|
|
||||||
Notification: typeof PatchedNotification;
|
|
||||||
}
|
|
||||||
|
|
||||||
// typescript doesn't have up to date NotificationOptions yet
|
|
||||||
interface NotificationOptions {
|
|
||||||
silent?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ZulipWebWindow extends Window {
|
|
||||||
electron_bridge: any;
|
|
||||||
tray: any;
|
|
||||||
$: any;
|
|
||||||
lightbox: any;
|
|
||||||
}
|
|
||||||
@@ -48,10 +48,10 @@ envSetup()
|
|||||||
|
|
||||||
# Set name of upstreamRemote
|
# Set name of upstreamRemote
|
||||||
cd $workingDir
|
cd $workingDir
|
||||||
git remote -v | grep "github\.com.zulip.zulip-desktop.git (fetch)" > /dev/null 2>&1
|
git remote -v | grep "github\.com.zulip.zulip-electron.git (fetch)" > /dev/null 2>&1
|
||||||
if [ $? -eq 0 ]
|
if [ $? -eq 0 ]
|
||||||
then
|
then
|
||||||
upstreamRemote=`git remote -v | grep "github\.com.zulip.zulip-desktop.git (fetch)" | awk '{ print $1 }'`
|
upstreamRemote=`git remote -v | grep "github\.com.zulip.zulip-electron.git (fetch)" | awk '{ print $1 }'`
|
||||||
else
|
else
|
||||||
upstreamRemote="origin"
|
upstreamRemote="origin"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user