Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4d3805be8 | ||
|
|
e853af40c4 | ||
|
|
941200cf3b | ||
|
|
cf1f659ebf | ||
|
|
eb381a87bc | ||
|
|
68bc0ae4a0 | ||
|
|
178bc7f401 | ||
|
|
0f1245b975 | ||
|
|
960312a932 | ||
|
|
0e00f3bbce | ||
|
|
ec205f68a6 | ||
|
|
5fe5989710 | ||
|
|
69141b5395 | ||
|
|
8d66f05924 | ||
|
|
e7330dbff8 | ||
|
|
67fa9cca8c | ||
|
|
a90bf1af08 | ||
|
|
cb145acc73 | ||
|
|
099e10673c | ||
|
|
4b3608fc1e | ||
|
|
6128c0e12a | ||
|
|
14a1f5d3e1 | ||
|
|
9cf26f4890 | ||
|
|
397a7381b8 | ||
|
|
24b28f9ded | ||
|
|
9ceabe02d5 | ||
|
|
b207ee57de | ||
|
|
cf9d0c8aa2 | ||
|
|
e97ab2e6dd | ||
|
|
6a7f26d7e8 | ||
|
|
b6e11f623a | ||
|
|
1c60c335fd | ||
|
|
c9249b1724 | ||
|
|
9e957ba704 | ||
|
|
6c37e30233 | ||
|
|
addfe2e414 | ||
|
|
bda0dd29df | ||
|
|
01926e1234 | ||
|
|
9138bbfaf2 |
@@ -6,7 +6,8 @@
|
||||
|
||||
Desktop client for Zulip. Available for Mac, Linux, and Windows.
|
||||
|
||||
<img src="http://i.imgur.com/ChzTq4F.png"/>
|
||||
<img src="https://i.imgur.com/s1o6TRA.png"/>
|
||||
<img src="https://i.imgur.com/vekKnW4.png"/>
|
||||
|
||||
# Download
|
||||
Please see the [installation guide](https://zulip.com/help/desktop-app-install-guide).
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
import {app, dialog} from 'electron';
|
||||
import {UpdateDownloadedEvent, UpdateInfo, autoUpdater} from 'electron-updater';
|
||||
import {linuxUpdateNotification} from './linuxupdater'; // Required only in case of linux
|
||||
import {app, dialog, session} from 'electron';
|
||||
import util from 'util';
|
||||
|
||||
import log from 'electron-log';
|
||||
import isDev from 'electron-is-dev';
|
||||
import log from 'electron-log';
|
||||
import {UpdateDownloadedEvent, UpdateInfo, autoUpdater} from 'electron-updater';
|
||||
|
||||
import * as ConfigUtil from '../renderer/js/utils/config-util';
|
||||
import * as LinkUtil from '../renderer/js/utils/link-util';
|
||||
|
||||
export function appUpdater(updateFromMenu = false): void {
|
||||
import {linuxUpdateNotification} from './linuxupdater'; // Required only in case of linux
|
||||
|
||||
const sleep = util.promisify(setTimeout);
|
||||
|
||||
export async function appUpdater(updateFromMenu = false): Promise<void> {
|
||||
// Don't initiate auto-updates in development
|
||||
if (isDev) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform === 'linux' && !process.env.APPIMAGE) {
|
||||
linuxUpdateNotification();
|
||||
const ses = session.fromPartition('persist:webviewsession');
|
||||
await linuxUpdateNotification(ses);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,13 +100,12 @@ export function appUpdater(updateFromMenu = false): void {
|
||||
detail: 'It will be installed the next time you restart the application'
|
||||
});
|
||||
if (response === 0) {
|
||||
setTimeout(() => {
|
||||
autoUpdater.quitAndInstall();
|
||||
// Force app to quit. This is just a workaround, ideally autoUpdater.quitAndInstall() should relaunch the app.
|
||||
app.quit();
|
||||
}, 1000);
|
||||
await sleep(1000);
|
||||
autoUpdater.quitAndInstall();
|
||||
// Force app to quit. This is just a workaround, ideally autoUpdater.quitAndInstall() should relaunch the app.
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
// Init for updates
|
||||
(async () => autoUpdater.checkForUpdates())();
|
||||
await autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import {sentryInit} from '../renderer/js/utils/sentry-util';
|
||||
import {appUpdater} from './autoupdater';
|
||||
import {setAutoLaunch} from './startup';
|
||||
|
||||
import electron, {app, dialog, ipcMain, session} from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import electron, {app, dialog, ipcMain, session} from 'electron';
|
||||
|
||||
import * as AppMenu from './menu';
|
||||
import * as BadgeSettings from '../renderer/js/pages/preference/badge-settings';
|
||||
import * as CertificateUtil from '../renderer/js/utils/certificate-util';
|
||||
import * as ConfigUtil from '../renderer/js/utils/config-util';
|
||||
import * as ProxyUtil from '../renderer/js/utils/proxy-util';
|
||||
import {sentryInit} from '../renderer/js/utils/sentry-util';
|
||||
|
||||
interface PatchedGlobal extends NodeJS.Global {
|
||||
mainWindowState: windowStateKeeper.State;
|
||||
}
|
||||
import {appUpdater} from './autoupdater';
|
||||
import * as AppMenu from './menu';
|
||||
import {_getServerSettings, _saveServerIcon, _isOnline} from './request';
|
||||
import {setAutoLaunch} from './startup';
|
||||
|
||||
const globalPatched = global as PatchedGlobal;
|
||||
let mainWindowState: windowStateKeeper.State;
|
||||
|
||||
// Prevent window being garbage collected
|
||||
let mainWindow: Electron.BrowserWindow;
|
||||
@@ -77,9 +76,6 @@ function createMainWindow(): Electron.BrowserWindow {
|
||||
path: `${app.getPath('userData')}/config`
|
||||
});
|
||||
|
||||
// Let's keep the window position global so that we can access it in other process
|
||||
globalPatched.mainWindowState = mainWindowState;
|
||||
|
||||
const win = new electron.BrowserWindow({
|
||||
// This settings needs to be saved in config
|
||||
title: 'Zulip',
|
||||
@@ -167,6 +163,7 @@ app.on('activate', () => {
|
||||
app.on('ready', () => {
|
||||
const ses = session.fromPartition('persist:webviewsession');
|
||||
ses.setUserAgent(`ZulipElectron/${app.getVersion()} ${ses.getUserAgent()}`);
|
||||
|
||||
ipcMain.on('set-spellcheck-langs', () => {
|
||||
ses.setSpellCheckerLanguages(ConfigUtil.getConfigItem('spellcheckerLanguages'));
|
||||
});
|
||||
@@ -208,17 +205,23 @@ app.on('ready', () => {
|
||||
event.returnValue = session.fromPartition('persist:webviewsession').getUserAgent();
|
||||
});
|
||||
|
||||
page.once('did-frame-finish-load', () => {
|
||||
ipcMain.handle('get-server-settings', async (event, domain: string) => _getServerSettings(domain, ses));
|
||||
|
||||
ipcMain.handle('save-server-icon', async (event, url: string) => _saveServerIcon(url, ses));
|
||||
|
||||
ipcMain.handle('is-online', async (event, url: string) => _isOnline(url, ses));
|
||||
|
||||
page.once('did-frame-finish-load', async () => {
|
||||
// Initiate auto-updates on MacOS and Windows
|
||||
if (ConfigUtil.getConfigItem('autoUpdate')) {
|
||||
appUpdater();
|
||||
await appUpdater();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('certificate-error', (
|
||||
event: Event,
|
||||
webContents: Electron.WebContents,
|
||||
url: string,
|
||||
urlString: string,
|
||||
error: string,
|
||||
certificate: Electron.Certificate,
|
||||
callback: (isTrusted: boolean) => void
|
||||
@@ -226,8 +229,12 @@ app.on('ready', () => {
|
||||
// TODO: The entire concept of selectively ignoring certificate errors
|
||||
// is ill-conceived, and this handler needs to be deleted.
|
||||
|
||||
const {origin} = new URL(url);
|
||||
const filename = CertificateUtil.getCertificate(encodeURIComponent(origin));
|
||||
const url = new URL(urlString);
|
||||
if (url.protocol === 'wss:') {
|
||||
url.protocol = 'https:';
|
||||
}
|
||||
|
||||
const filename = CertificateUtil.getCertificate(encodeURIComponent(url.origin));
|
||||
if (filename !== undefined) {
|
||||
try {
|
||||
const savedCertificate = fs.readFileSync(
|
||||
@@ -247,7 +254,7 @@ app.on('ready', () => {
|
||||
|
||||
dialog.showErrorBox(
|
||||
'Certificate error',
|
||||
`The server presented an invalid certificate for ${origin}:
|
||||
`The server presented an invalid certificate for ${url.origin}:
|
||||
|
||||
${error}`
|
||||
);
|
||||
@@ -311,7 +318,7 @@ ${error}`
|
||||
});
|
||||
|
||||
ipcMain.on('clear-app-settings', () => {
|
||||
globalPatched.mainWindowState.unmanage();
|
||||
mainWindowState.unmanage();
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
});
|
||||
@@ -348,7 +355,7 @@ ${error}`
|
||||
AppMenu.setMenu(props);
|
||||
const activeTab = props.tabs[props.activeTabIndex];
|
||||
if (activeTab) {
|
||||
mainWindow.setTitle(`Zulip - ${activeTab.webview.props.name}`);
|
||||
mainWindow.setTitle(`Zulip - ${activeTab.webviewName}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -360,7 +367,7 @@ ${error}`
|
||||
page.downloadURL(url);
|
||||
page.session.once('will-download', async (_event: Event, item) => {
|
||||
if (ConfigUtil.getConfigItem('promptDownload', false)) {
|
||||
const showDialogOptions: object = {
|
||||
const showDialogOptions: electron.SaveDialogOptions = {
|
||||
defaultPath: path.join(downloadPath, item.getFilename())
|
||||
};
|
||||
item.setSaveDialogOptions(showDialogOptions);
|
||||
|
||||
@@ -1,52 +1,45 @@
|
||||
import {app, Notification} from 'electron';
|
||||
import {app, Notification, net} from 'electron';
|
||||
|
||||
import request from 'request';
|
||||
import getStream from 'get-stream';
|
||||
import semver from 'semver';
|
||||
|
||||
import * as ConfigUtil from '../renderer/js/utils/config-util';
|
||||
import * as ProxyUtil from '../renderer/js/utils/proxy-util';
|
||||
import * as LinuxUpdateUtil from '../renderer/js/utils/linux-update-util';
|
||||
import Logger from '../renderer/js/utils/logger-util';
|
||||
|
||||
import {fetchResponse} from './request';
|
||||
|
||||
const logger = new Logger({
|
||||
file: 'linux-update-util.log',
|
||||
timestamp: true
|
||||
});
|
||||
|
||||
export function linuxUpdateNotification(): void {
|
||||
export async function linuxUpdateNotification(session: Electron.session): Promise<void> {
|
||||
let url = 'https://api.github.com/repos/zulip/zulip-desktop/releases';
|
||||
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
|
||||
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
|
||||
|
||||
const options = {
|
||||
url,
|
||||
headers: {'User-Agent': 'request'},
|
||||
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : '',
|
||||
ecdhCurve: 'auto'
|
||||
};
|
||||
|
||||
request(options, (error, response: request.Response, body: string) => {
|
||||
if (error) {
|
||||
logger.error('Linux update error.');
|
||||
logger.error(error);
|
||||
try {
|
||||
const response = await fetchResponse(net.request({url, session}));
|
||||
if (response.statusCode !== 200) {
|
||||
logger.log('Linux update response status: ', response.statusCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.statusCode < 400) {
|
||||
const data = JSON.parse(body);
|
||||
const latestVersion = ConfigUtil.getConfigItem('betaUpdate') ? data[0].tag_name : data.tag_name;
|
||||
if (typeof latestVersion !== 'string') {
|
||||
throw new TypeError('Expected string for tag_name');
|
||||
}
|
||||
|
||||
if (semver.gt(latestVersion, app.getVersion())) {
|
||||
const notified = LinuxUpdateUtil.getUpdateItem(latestVersion);
|
||||
if (notified === null) {
|
||||
new Notification({title: 'Zulip Update', body: `A new version ${latestVersion} is available. Please update using your package manager.`}).show();
|
||||
LinuxUpdateUtil.setUpdateItem(latestVersion, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.log('Linux update response status: ', response.statusCode);
|
||||
const data = JSON.parse(await getStream(response));
|
||||
const latestVersion = ConfigUtil.getConfigItem('betaUpdate') ? data[0].tag_name : data.tag_name;
|
||||
if (typeof latestVersion !== 'string') {
|
||||
throw new TypeError('Expected string for tag_name');
|
||||
}
|
||||
});
|
||||
|
||||
if (semver.gt(latestVersion, app.getVersion())) {
|
||||
const notified = LinuxUpdateUtil.getUpdateItem(latestVersion);
|
||||
if (notified === null) {
|
||||
new Notification({title: 'Zulip Update', body: `A new version ${latestVersion} is available. Please update using your package manager.`}).show();
|
||||
LinuxUpdateUtil.setUpdateItem(latestVersion, true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Linux update error.');
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import {app, shell, BrowserWindow, Menu} from 'electron';
|
||||
import {appUpdater} from './autoupdater';
|
||||
|
||||
import AdmZip from 'adm-zip';
|
||||
import * as DNDUtil from '../renderer/js/utils/dnd-util';
|
||||
|
||||
import type {TabData} from '../renderer/js/main';
|
||||
import * as ConfigUtil from '../renderer/js/utils/config-util';
|
||||
import * as DNDUtil from '../renderer/js/utils/dnd-util';
|
||||
import * as LinkUtil from '../renderer/js/utils/link-util';
|
||||
import * as t from '../renderer/js/utils/translation-util';
|
||||
import type {ServerOrFunctionalTab} from '../renderer/js/main';
|
||||
|
||||
import {appUpdater} from './autoupdater';
|
||||
|
||||
export interface MenuProps {
|
||||
tabs: ServerOrFunctionalTab[];
|
||||
tabs: TabData[];
|
||||
activeTabIndex?: number;
|
||||
enableMenu?: boolean;
|
||||
}
|
||||
@@ -41,8 +43,8 @@ function getHistorySubmenu(enableMenu: boolean): Electron.MenuItemConstructorOpt
|
||||
function getToolsSubmenu(): Electron.MenuItemConstructorOptions[] {
|
||||
return [{
|
||||
label: t.__('Check for Updates'),
|
||||
click() {
|
||||
checkForUpdate();
|
||||
async click() {
|
||||
await checkForUpdate();
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -213,7 +215,7 @@ function getHelpSubmenu(): Electron.MenuItemConstructorOptions[] {
|
||||
];
|
||||
}
|
||||
|
||||
function getWindowSubmenu(tabs: ServerOrFunctionalTab[], activeTabIndex: number): Electron.MenuItemConstructorOptions[] {
|
||||
function getWindowSubmenu(tabs: TabData[], activeTabIndex: number): Electron.MenuItemConstructorOptions[] {
|
||||
const initialSubmenu: Electron.MenuItemConstructorOptions[] = [{
|
||||
label: t.__('Minimize'),
|
||||
role: 'minimize'
|
||||
@@ -229,17 +231,17 @@ function getWindowSubmenu(tabs: ServerOrFunctionalTab[], activeTabIndex: number)
|
||||
});
|
||||
tabs.forEach(tab => {
|
||||
// Do not add functional tab settings to list of windows in menu bar
|
||||
if (tab.props.role === 'function' && tab.props.name === 'Settings') {
|
||||
if (tab.role === 'function' && tab.name === 'Settings') {
|
||||
return;
|
||||
}
|
||||
|
||||
initialSubmenu.push({
|
||||
label: tab.props.name,
|
||||
accelerator: tab.props.role === 'function' ? '' : `${ShortcutKey} + ${tab.props.index + 1}`,
|
||||
checked: tab.props.index === activeTabIndex,
|
||||
label: tab.name,
|
||||
accelerator: tab.role === 'function' ? '' : `${ShortcutKey} + ${tab.index + 1}`,
|
||||
checked: tab.index === activeTabIndex,
|
||||
click(_item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
sendAction('switch-server-tab', tab.props.index);
|
||||
sendAction('switch-server-tab', tab.index);
|
||||
}
|
||||
},
|
||||
type: 'checkbox'
|
||||
@@ -541,24 +543,24 @@ function sendAction(action: string, ...parameters: unknown[]): void {
|
||||
win.webContents.send(action, ...parameters);
|
||||
}
|
||||
|
||||
function checkForUpdate(): void {
|
||||
appUpdater(true);
|
||||
async function checkForUpdate(): Promise<void> {
|
||||
await appUpdater(true);
|
||||
}
|
||||
|
||||
function getNextServer(tabs: ServerOrFunctionalTab[], activeTabIndex: number): number {
|
||||
function getNextServer(tabs: TabData[], activeTabIndex: number): number {
|
||||
do {
|
||||
activeTabIndex = (activeTabIndex + 1) % tabs.length;
|
||||
}
|
||||
while (tabs[activeTabIndex].props.role !== 'server');
|
||||
while (tabs[activeTabIndex].role !== 'server');
|
||||
|
||||
return activeTabIndex;
|
||||
}
|
||||
|
||||
function getPreviousServer(tabs: ServerOrFunctionalTab[], activeTabIndex: number): number {
|
||||
function getPreviousServer(tabs: TabData[], activeTabIndex: number): number {
|
||||
do {
|
||||
activeTabIndex = (activeTabIndex - 1 + tabs.length) % tabs.length;
|
||||
}
|
||||
while (tabs[activeTabIndex].props.role !== 'server');
|
||||
while (tabs[activeTabIndex].role !== 'server');
|
||||
|
||||
return activeTabIndex;
|
||||
}
|
||||
|
||||
113
app/main/request.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import {ClientRequest, IncomingMessage, app, net} from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import stream from 'stream';
|
||||
import util from 'util';
|
||||
|
||||
import escape from 'escape-html';
|
||||
import getStream from 'get-stream';
|
||||
|
||||
import {ServerConf} from '../renderer/js/utils/domain-util';
|
||||
import Logger from '../renderer/js/utils/logger-util';
|
||||
import * as Messages from '../resources/messages';
|
||||
|
||||
export async function fetchResponse(request: ClientRequest): Promise<IncomingMessage> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.on('response', resolve);
|
||||
request.on('abort', () => reject(new Error('Request aborted')));
|
||||
request.on('error', reject);
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
const pipeline = util.promisify(stream.pipeline);
|
||||
|
||||
/* Request: domain-util */
|
||||
|
||||
const defaultIconUrl = '../renderer/img/icon.png';
|
||||
|
||||
const logger = new Logger({
|
||||
file: 'domain-util.log',
|
||||
timestamp: true
|
||||
});
|
||||
|
||||
const generateFilePath = (url: string): string => {
|
||||
const dir = `${app.getPath('userData')}/server-icons`;
|
||||
const extension = path.extname(url).split('?')[0];
|
||||
|
||||
let hash = 5381;
|
||||
let {length} = url;
|
||||
|
||||
while (length) {
|
||||
hash = (hash * 33) ^ url.charCodeAt(--length);
|
||||
}
|
||||
|
||||
// Create 'server-icons' directory if not existed
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
|
||||
return `${dir}/${hash >>> 0}${extension}`;
|
||||
};
|
||||
|
||||
export const _getServerSettings = async (domain: string, session: Electron.session): Promise<ServerConf> => {
|
||||
const response = await fetchResponse(net.request({
|
||||
url: domain + '/api/v1/server_settings',
|
||||
session
|
||||
}));
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(Messages.invalidZulipServerError(domain));
|
||||
}
|
||||
|
||||
const {realm_name, realm_uri, realm_icon} = JSON.parse(await getStream(response));
|
||||
if (
|
||||
typeof realm_name !== 'string' ||
|
||||
typeof realm_uri !== 'string' ||
|
||||
typeof realm_icon !== 'string'
|
||||
) {
|
||||
throw new TypeError(Messages.noOrgsError(domain));
|
||||
}
|
||||
|
||||
return {
|
||||
// Some Zulip Servers use absolute URL for server icon whereas others use relative URL
|
||||
// Following check handles both the cases
|
||||
icon: realm_icon.startsWith('/') ? realm_uri + realm_icon : realm_icon,
|
||||
url: realm_uri,
|
||||
alias: escape(realm_name)
|
||||
};
|
||||
};
|
||||
|
||||
export const _saveServerIcon = async (url: string, session: Electron.session): Promise<string> => {
|
||||
try {
|
||||
const response = await fetchResponse(net.request({url, session}));
|
||||
if (response.statusCode !== 200) {
|
||||
logger.log('Could not get server icon.');
|
||||
return defaultIconUrl;
|
||||
}
|
||||
|
||||
const filePath = generateFilePath(url);
|
||||
await pipeline(response, fs.createWriteStream(filePath));
|
||||
return filePath;
|
||||
} catch (error) {
|
||||
logger.log('Could not get server icon.');
|
||||
logger.log(error);
|
||||
logger.reportSentry(error);
|
||||
return defaultIconUrl;
|
||||
}
|
||||
};
|
||||
|
||||
/* Request: reconnect-util */
|
||||
|
||||
export const _isOnline = async (url: string, session: Electron.session): Promise<boolean> => {
|
||||
try {
|
||||
const response = await fetchResponse(net.request({
|
||||
url: `${url}/static/favicon.ico`,
|
||||
session
|
||||
}));
|
||||
const isValidResponse = response.statusCode >= 200 && response.statusCode < 400;
|
||||
return isValidResponse;
|
||||
} catch (error) {
|
||||
logger.log(error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import {app} from 'electron';
|
||||
|
||||
import AutoLaunch from 'auto-launch';
|
||||
import isDev from 'electron-is-dev';
|
||||
|
||||
import * as ConfigUtil from '../renderer/js/utils/config-util';
|
||||
|
||||
export const setAutoLaunch = async (AutoLaunchValue: boolean): Promise<void> => {
|
||||
|
||||
@@ -389,6 +389,10 @@ img.server-info-icon {
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
#note {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: Courier New, Courier, monospace;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,5 +1,5 @@
|
||||
import {clipboard} from 'electron';
|
||||
import crypto from 'crypto';
|
||||
import {clipboard} from 'electron';
|
||||
|
||||
// This helper is exposed via electron_bridge for use in the social
|
||||
// login flow.
|
||||
@@ -59,7 +59,7 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
|
||||
plaintext =
|
||||
decipher.update(ciphertext, undefined, 'utf8') +
|
||||
decipher.final('utf8');
|
||||
} catch (_) {
|
||||
} catch {
|
||||
// If the parsing or decryption failed in any way,
|
||||
// the correct token hasn’t been copied yet; try
|
||||
// again next time.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import {remote, ContextMenuParams} from 'electron';
|
||||
|
||||
import * as t from '../utils/translation-util';
|
||||
|
||||
const {clipboard, Menu} = remote;
|
||||
|
||||
export const contextMenu = (webContents: Electron.WebContents, event: Event, props: ContextMenuParams) => {
|
||||
const isText = Boolean(props.selectionText.length);
|
||||
const isLink = Boolean(props.linkURL);
|
||||
const isText = props.selectionText !== '';
|
||||
const isLink = props.linkURL !== '';
|
||||
const linkURL = isLink ? new URL(props.linkURL) : undefined;
|
||||
const isEmailAddress = isLink ? linkURL.protocol === 'mailto:' : undefined;
|
||||
|
||||
const makeSuggestion = (suggestion: string) => ({
|
||||
label: suggestion,
|
||||
@@ -21,7 +25,8 @@ export const contextMenu = (webContents: Electron.WebContents, event: Event, pro
|
||||
webContents.session.addWordToSpellCheckerDictionary(props.misspelledWord);
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
type: 'separator',
|
||||
visible: props.isEditable && isText && props.misspelledWord.length !== 0
|
||||
}, {
|
||||
label: `${t.__('Look Up')} "${props.selectionText}"`,
|
||||
visible: process.platform === 'darwin' && isText,
|
||||
@@ -29,7 +34,8 @@ export const contextMenu = (webContents: Electron.WebContents, event: Event, pro
|
||||
webContents.showDefinitionForSelection();
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
type: 'separator',
|
||||
visible: process.platform === 'darwin' && isText
|
||||
}, {
|
||||
label: t.__('Cut'),
|
||||
visible: isText,
|
||||
@@ -41,6 +47,7 @@ export const contextMenu = (webContents: Electron.WebContents, event: Event, pro
|
||||
}, {
|
||||
label: t.__('Copy'),
|
||||
accelerator: 'CommandOrControl+C',
|
||||
enabled: props.editFlags.canCopy,
|
||||
click(_item) {
|
||||
webContents.copy();
|
||||
}
|
||||
@@ -54,12 +61,12 @@ export const contextMenu = (webContents: Electron.WebContents, event: Event, pro
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: t.__('Copy Link'),
|
||||
visible: isText && isLink,
|
||||
label: isEmailAddress ? t.__('Copy Email Address') : t.__('Copy Link'),
|
||||
visible: isLink,
|
||||
click(_item) {
|
||||
clipboard.write({
|
||||
bookmark: props.linkText,
|
||||
text: props.linkURL
|
||||
text: isEmailAddress ? linkURL.pathname : props.linkURL
|
||||
});
|
||||
}
|
||||
}, {
|
||||
@@ -78,7 +85,8 @@ export const contextMenu = (webContents: Electron.WebContents, event: Event, pro
|
||||
});
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
type: 'separator',
|
||||
visible: isLink || props.mediaType === 'image'
|
||||
}, {
|
||||
label: t.__('Services'),
|
||||
visible: process.platform === 'darwin',
|
||||
@@ -96,7 +104,12 @@ export const contextMenu = (webContents: Electron.WebContents, event: Event, pro
|
||||
});
|
||||
}
|
||||
}
|
||||
// Hide the invisible separators on Linux and Windows
|
||||
// Electron has a bug which ignores visible: false parameter for separator menuitems. So we remove them here.
|
||||
// https://github.com/electron/electron/issues/5869
|
||||
// https://github.com/electron/electron/issues/6906
|
||||
|
||||
const menu = Menu.buildFromTemplate(menuTemplate);
|
||||
const filteredMenuTemplate = menuTemplate.filter(menuItem => menuItem.visible ?? true);
|
||||
const menu = Menu.buildFromTemplate(filteredMenuTemplate);
|
||||
menu.popup();
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {ipcRenderer, remote} from 'electron';
|
||||
|
||||
import * as LinkUtil from '../utils/link-util';
|
||||
import * as ConfigUtil from '../utils/config-util';
|
||||
import * as LinkUtil from '../utils/link-util';
|
||||
|
||||
import type WebView from './webview';
|
||||
|
||||
const {shell, app} = remote;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import Tab, {TabProps} from './tab';
|
||||
import * as SystemUtil from '../utils/system-util';
|
||||
|
||||
import Tab, {TabProps} from './tab';
|
||||
|
||||
export default class ServerTab extends Tab {
|
||||
$badge: Element;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import WebView from './webview';
|
||||
import BaseComponent from './base';
|
||||
import WebView from './webview';
|
||||
|
||||
export interface TabProps {
|
||||
role: string;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {ipcRenderer, remote} from 'electron';
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import * as ConfigUtil from '../utils/config-util';
|
||||
import * as SystemUtil from '../utils/system-util';
|
||||
|
||||
import BaseComponent from './base';
|
||||
import handleExternalLink from './handle-external-link';
|
||||
import {contextMenu} from './context-menu';
|
||||
import handleExternalLink from './handle-external-link';
|
||||
|
||||
const {app, dialog} = remote;
|
||||
|
||||
@@ -216,7 +217,7 @@ export default class WebView extends BaseComponent {
|
||||
|
||||
focus(): void {
|
||||
// Focus Webview and it's contents when Window regain focus.
|
||||
const webContents = this.$el.getWebContents();
|
||||
const webContents = remote.webContents.fromId(this.$el.getWebContentsId());
|
||||
// HACK: webContents.isFocused() seems to be true even without the element
|
||||
// being in focus. So, we check against `document.activeElement`.
|
||||
if (webContents && this.$el !== document.activeElement) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import {ClipboardDecrypterImpl} from './clipboard-decrypter';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {remote} from 'electron';
|
||||
import SendFeedback from '@electron-elements/send-feedback';
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import SendFeedback from '@electron-elements/send-feedback';
|
||||
|
||||
const {app} = remote;
|
||||
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import {ipcRenderer, remote, clipboard} from 'electron';
|
||||
import {feedbackHolder} from './feedback';
|
||||
|
||||
import path from 'path';
|
||||
import escape from 'escape-html';
|
||||
|
||||
import isDev from 'electron-is-dev';
|
||||
const {session, app, Menu, dialog} = remote;
|
||||
import escape from 'escape-html';
|
||||
|
||||
import * as Messages from '../../resources/messages';
|
||||
|
||||
import FunctionalTab from './components/functional-tab';
|
||||
import ServerTab from './components/server-tab';
|
||||
import WebView from './components/webview';
|
||||
import {feedbackHolder} from './feedback';
|
||||
import * as CommonUtil from './utils/common-util';
|
||||
import * as ConfigUtil from './utils/config-util';
|
||||
import * as DNDUtil from './utils/dnd-util';
|
||||
import type {DNDSettings} from './utils/dnd-util';
|
||||
import * as DomainUtil from './utils/domain-util';
|
||||
import * as EnterpriseUtil from './utils/enterprise-util';
|
||||
import * as LinkUtil from './utils/link-util';
|
||||
import Logger from './utils/logger-util';
|
||||
import ReconnectUtil from './utils/reconnect-util';
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import './tray';
|
||||
|
||||
import * as DomainUtil from './utils/domain-util';
|
||||
import WebView from './components/webview';
|
||||
import ServerTab from './components/server-tab';
|
||||
import FunctionalTab from './components/functional-tab';
|
||||
import * as ConfigUtil from './utils/config-util';
|
||||
import * as DNDUtil from './utils/dnd-util';
|
||||
import ReconnectUtil from './utils/reconnect-util';
|
||||
import Logger from './utils/logger-util';
|
||||
import * as CommonUtil from './utils/common-util';
|
||||
import * as EnterpriseUtil from './utils/enterprise-util';
|
||||
import * as LinkUtil from './utils/link-util';
|
||||
import * as Messages from '../../resources/messages';
|
||||
import type {DNDSettings} from './utils/dnd-util';
|
||||
const {session, app, Menu, dialog} = remote;
|
||||
|
||||
interface FunctionalTabProps {
|
||||
name: string;
|
||||
@@ -59,7 +61,14 @@ const logger = new Logger({
|
||||
});
|
||||
|
||||
const rendererDirectory = path.resolve(__dirname, '..');
|
||||
export type ServerOrFunctionalTab = ServerTab | FunctionalTab;
|
||||
type ServerOrFunctionalTab = ServerTab | FunctionalTab;
|
||||
|
||||
export interface TabData {
|
||||
role: string;
|
||||
name: string;
|
||||
index: number;
|
||||
webviewName: string;
|
||||
}
|
||||
|
||||
class ServerManagerView {
|
||||
$addServerButton: HTMLButtonElement;
|
||||
@@ -590,19 +599,13 @@ class ServerManagerView {
|
||||
// not crash app when this.tabs is passed into
|
||||
// ipcRenderer. Something about webview, and props.webview
|
||||
// properties in ServerTab causes the app to crash.
|
||||
get tabsForIpc(): ServerOrFunctionalTab[] {
|
||||
const tabs: ServerOrFunctionalTab[] = [];
|
||||
this.tabs.forEach((tab: ServerOrFunctionalTab) => {
|
||||
const proto = Object.create(Object.getPrototypeOf(tab));
|
||||
const tabClone = Object.assign(proto, tab);
|
||||
|
||||
tabClone.webview = {props: {}};
|
||||
tabClone.webview.props.name = tab.webview.props.name;
|
||||
delete tabClone.props.webview;
|
||||
tabs.push(tabClone);
|
||||
});
|
||||
|
||||
return tabs;
|
||||
get tabsForIpc(): TabData[] {
|
||||
return this.tabs.map(tab => ({
|
||||
role: tab.props.role,
|
||||
name: tab.props.name,
|
||||
index: tab.props.index,
|
||||
webviewName: tab.webview.props.name
|
||||
}));
|
||||
}
|
||||
|
||||
activateTab(index: number, hideOldTab = true): void {
|
||||
@@ -627,7 +630,7 @@ class ServerManagerView {
|
||||
|
||||
try {
|
||||
this.tabs[index].webview.canGoBackButton();
|
||||
} catch (_) {
|
||||
} catch {
|
||||
}
|
||||
|
||||
this.activeTabIndex = index;
|
||||
@@ -717,7 +720,7 @@ class ServerManagerView {
|
||||
|
||||
updateGeneralSettings(setting: string, value: unknown): void {
|
||||
if (this.getActiveWebview()) {
|
||||
const webContents = this.getActiveWebview().getWebContents();
|
||||
const webContents = remote.webContents.fromId(this.getActiveWebview().getWebContentsId());
|
||||
webContents.send(setting, value);
|
||||
}
|
||||
}
|
||||
@@ -893,7 +896,7 @@ class ServerManagerView {
|
||||
webviews.forEach(webview => {
|
||||
try {
|
||||
webview.setAudioMuted(state);
|
||||
} catch (_) {
|
||||
} catch {
|
||||
// Webview is not ready yet
|
||||
webview.addEventListener('dom-ready', () => {
|
||||
webview.setAudioMuted(state);
|
||||
@@ -917,7 +920,7 @@ class ServerManagerView {
|
||||
ipcRenderer.on('toggle-dnd', (event: Event, state: boolean, newSettings: DNDSettings) => {
|
||||
this.toggleDNDButton(state);
|
||||
ipcRenderer.send('forward-message', 'toggle-silent', newSettings.silent);
|
||||
const webContents = this.getActiveWebview().getWebContents();
|
||||
const webContents = remote.webContents.fromId(this.getActiveWebview().getWebContentsId());
|
||||
webContents.send('toggle-dnd', state, newSettings);
|
||||
});
|
||||
|
||||
@@ -969,7 +972,7 @@ class ServerManagerView {
|
||||
ipcRenderer.on('focus-webview-with-id', (event: Event, webviewId: number) => {
|
||||
const webviews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webview');
|
||||
webviews.forEach(webview => {
|
||||
const currentId = webview.getWebContents().id;
|
||||
const currentId = webview.getWebContentsId();
|
||||
const tabId = webview.getAttribute('data-tab-id');
|
||||
const concurrentTab: HTMLButtonElement = document.querySelector(`div[data-tab-id="${tabId}"]`);
|
||||
if (currentId === webviewId) {
|
||||
@@ -1046,7 +1049,8 @@ window.addEventListener('load', async () => {
|
||||
// Only start electron-connect (auto reload on change) when its ran
|
||||
// from `npm run dev` or `gulp dev` and not from `npm start`
|
||||
if (isDev && remote.getGlobal('process').argv.includes('--electron-connect')) {
|
||||
require('electron-connect').client.create();
|
||||
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
||||
(await import('electron-connect')).client.create();
|
||||
}
|
||||
|
||||
const serverManagerView = new ServerManagerView();
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import MacNotifier from 'node-mac-notifier';
|
||||
|
||||
import electron_bridge from '../electron-bridge';
|
||||
import * as ConfigUtil from '../utils/config-util';
|
||||
|
||||
import {
|
||||
appId, customReply, focusCurrentServer, parseReply
|
||||
} from './helpers';
|
||||
|
||||
import MacNotifier from 'node-mac-notifier';
|
||||
import * as ConfigUtil from '../utils/config-util';
|
||||
import electron_bridge from '../electron-bridge';
|
||||
|
||||
type ReplyHandler = (response: string) => void;
|
||||
type ClickHandler = () => void;
|
||||
let replyHandler: ReplyHandler;
|
||||
@@ -47,7 +49,7 @@ class DarwinNotification {
|
||||
}
|
||||
|
||||
static requestPermission(): void {
|
||||
return; // eslint-disable-line no-useless-return
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// Override default Notification permission
|
||||
@@ -104,7 +106,7 @@ class DarwinNotification {
|
||||
// Method specific to notification api
|
||||
// used by zulip
|
||||
close(): void {
|
||||
return; // eslint-disable-line no-useless-return
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
import {focusCurrentServer} from './helpers';
|
||||
|
||||
import * as ConfigUtil from '../utils/config-util';
|
||||
|
||||
import {focusCurrentServer} from './helpers';
|
||||
|
||||
const NativeNotification = window.Notification;
|
||||
export default class BaseNotification extends NativeNotification {
|
||||
constructor(title: string, options: NotificationOptions) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {remote} from 'electron';
|
||||
import electron_bridge from '../electron-bridge';
|
||||
import {appId, loadBots} from './helpers';
|
||||
|
||||
import DefaultNotification from './default-notification';
|
||||
import {appId} from './helpers';
|
||||
|
||||
const {app} = remote;
|
||||
|
||||
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
|
||||
@@ -67,7 +67,3 @@ export function newNotification(
|
||||
actions: notification.actions
|
||||
};
|
||||
}
|
||||
|
||||
electron_bridge.once('zulip-loaded', async () => {
|
||||
await loadBots();
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
'use-strict';
|
||||
|
||||
import {remote, OpenDialogOptions} from 'electron';
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import BaseComponent from '../../components/base';
|
||||
import * as CertificateUtil from '../../utils/certificate-util';
|
||||
import * as DomainUtil from '../../utils/domain-util';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import escape from 'escape-html';
|
||||
|
||||
import BaseComponent from '../../components/base';
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import BaseSection from './base-section';
|
||||
import * as DomainUtil from '../../utils/domain-util';
|
||||
import ServerInfoForm from './server-info-form';
|
||||
import AddCertificate from './add-certificate';
|
||||
import FindAccounts from './find-accounts';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
import AddCertificate from './add-certificate';
|
||||
import BaseSection from './base-section';
|
||||
import FindAccounts from './find-accounts';
|
||||
import ServerInfoForm from './server-info-form';
|
||||
|
||||
interface ConnectedOrgSectionProps {
|
||||
$root: Element;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use-strict';
|
||||
|
||||
import BaseComponent from '../../components/base';
|
||||
import * as LinkUtil from '../../utils/link-util';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import {ipcRenderer, remote, OpenDialogOptions} from 'electron';
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import Tagify from '@yaireo/tagify';
|
||||
import fs from 'fs-extra';
|
||||
import ISO6391 from 'iso-639-1';
|
||||
|
||||
const {app, dialog, session} = remote;
|
||||
const currentBrowserWindow = remote.getCurrentWindow();
|
||||
|
||||
import BaseSection from './base-section';
|
||||
import supportedLocales from '../../../../translations/supported-locales.json';
|
||||
import * as ConfigUtil from '../../utils/config-util';
|
||||
import * as EnterpriseUtil from '../../utils/enterprise-util';
|
||||
import * as t from '../../utils/translation-util';
|
||||
import supportedLocales from '../../../../translations/supported-locales.json';
|
||||
import Tagify from '@yaireo/tagify';
|
||||
import ISO6391 from 'iso-639-1';
|
||||
|
||||
import BaseSection from './base-section';
|
||||
|
||||
const {app, dialog, session} = remote;
|
||||
const currentBrowserWindow = remote.getCurrentWindow();
|
||||
|
||||
interface GeneralSectionProps {
|
||||
$root: Element;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import BaseSection from './base-section';
|
||||
import * as ConfigUtil from '../../utils/config-util';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
import BaseSection from './base-section';
|
||||
|
||||
interface NetworkSectionProps {
|
||||
$root: Element;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import BaseComponent from '../../components/base';
|
||||
import Nav from './nav';
|
||||
import ServersSection from './servers-section';
|
||||
import GeneralSection from './general-section';
|
||||
import NetworkSection from './network-section';
|
||||
import ConnectedOrgSection from './connected-org-section';
|
||||
import ShortcutsSection from './shortcuts-section';
|
||||
import type {DNDSettings} from '../../utils/dnd-util';
|
||||
|
||||
import ConnectedOrgSection from './connected-org-section';
|
||||
import GeneralSection from './general-section';
|
||||
import Nav from './nav';
|
||||
import NetworkSection from './network-section';
|
||||
import ServersSection from './servers-section';
|
||||
import ShortcutsSection from './shortcuts-section';
|
||||
|
||||
type Section = ServersSection | GeneralSection | NetworkSection | ConnectedOrgSection | ShortcutsSection;
|
||||
|
||||
export default class PreferenceView extends BaseComponent {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {remote, ipcRenderer} from 'electron';
|
||||
|
||||
import * as Messages from '../../../../resources/messages';
|
||||
import BaseComponent from '../../components/base';
|
||||
import * as DomainUtil from '../../utils/domain-util';
|
||||
import * as Messages from '../../../../resources/messages';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
const {dialog} = remote;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
import BaseSection from './base-section';
|
||||
import NewServerForm from './new-server-form';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
interface ServersSectionProps {
|
||||
$root: Element;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import BaseSection from './base-section';
|
||||
import * as LinkUtil from '../../utils/link-util';
|
||||
import * as t from '../../utils/translation-util';
|
||||
|
||||
import BaseSection from './base-section';
|
||||
|
||||
interface ShortcutsSectionProps {
|
||||
$root: Element;
|
||||
}
|
||||
|
||||
@@ -3,18 +3,20 @@ import fs from 'fs';
|
||||
|
||||
import isDev from 'electron-is-dev';
|
||||
|
||||
import electron_bridge from './electron-bridge';
|
||||
import {loadBots} from './notification/helpers';
|
||||
import * as NetworkError from './pages/network';
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import './notification';
|
||||
|
||||
// Prevent drag and drop event in main process which prevents remote code executaion
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import './shared/preventdrag';
|
||||
|
||||
import electron_bridge from './electron-bridge';
|
||||
contextBridge.exposeInMainWorld('raw_electron_bridge', electron_bridge);
|
||||
|
||||
electron_bridge.once('zulip-loaded', async () => {
|
||||
await loadBots();
|
||||
});
|
||||
|
||||
ipcRenderer.on('logout', () => {
|
||||
// Create the menu for the below
|
||||
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ipcRenderer, remote, WebviewTag, NativeImage} from 'electron';
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import * as ConfigUtil from './utils/config-util';
|
||||
|
||||
const {Tray, Menu, nativeImage, BrowserWindow} = remote;
|
||||
@@ -207,7 +207,7 @@ function toggleTray(): void {
|
||||
|
||||
const selector = 'webview:not([class*=disabled])';
|
||||
const webview: WebviewTag = document.querySelector(selector);
|
||||
const webContents = webview.getWebContents();
|
||||
const webContents = remote.webContents.fromId(webview.getWebContentsId());
|
||||
webContents.send('toggletray', state);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import electron from 'electron';
|
||||
import {JsonDB} from 'node-json-db';
|
||||
import {initSetUp} from './default-util';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {JsonDB} from 'node-json-db';
|
||||
|
||||
import {initSetUp} from './default-util';
|
||||
import Logger from './logger-util';
|
||||
|
||||
const {app, dialog} =
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {JsonDB} from 'node-json-db';
|
||||
|
||||
import electron from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import electron from 'electron';
|
||||
import Logger from './logger-util';
|
||||
|
||||
import {JsonDB} from 'node-json-db';
|
||||
|
||||
import * as EnterpriseUtil from './enterprise-util';
|
||||
import Logger from './logger-util';
|
||||
|
||||
const logger = new Logger({
|
||||
file: 'config-util.log',
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import {JsonDB} from 'node-json-db';
|
||||
|
||||
import escape from 'escape-html';
|
||||
import request from 'request';
|
||||
import {remote, ipcRenderer} from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Logger from './logger-util';
|
||||
import {remote} from 'electron';
|
||||
|
||||
import * as RequestUtil from './request-util';
|
||||
import * as EnterpriseUtil from './enterprise-util';
|
||||
import {JsonDB} from 'node-json-db';
|
||||
|
||||
import * as Messages from '../../../resources/messages';
|
||||
|
||||
import * as EnterpriseUtil from './enterprise-util';
|
||||
import Logger from './logger-util';
|
||||
|
||||
const {app, dialog} = remote;
|
||||
|
||||
export interface ServerConf {
|
||||
@@ -146,74 +144,11 @@ export async function checkDomain(domain: string, silent = false): Promise<Serve
|
||||
}
|
||||
|
||||
async function getServerSettings(domain: string): Promise<ServerConf> {
|
||||
const serverSettingsOptions = {
|
||||
url: domain + '/api/v1/server_settings',
|
||||
...RequestUtil.requestOptions(domain)
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(serverSettingsOptions, (error: Error, response: request.Response) => {
|
||||
if (!error && response.statusCode === 200) {
|
||||
const {realm_name, realm_uri, realm_icon} = JSON.parse(response.body);
|
||||
if (
|
||||
typeof realm_name === 'string' &&
|
||||
typeof realm_uri === 'string' &&
|
||||
typeof realm_icon === 'string'
|
||||
) {
|
||||
resolve({
|
||||
// Some Zulip Servers use absolute URL for server icon whereas others use relative URL
|
||||
// Following check handles both the cases
|
||||
icon: realm_icon.startsWith('/') ? realm_uri + realm_icon : realm_icon,
|
||||
url: realm_uri,
|
||||
alias: escape(realm_name)
|
||||
});
|
||||
} else {
|
||||
reject(Messages.noOrgsError(domain));
|
||||
}
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
return ipcRenderer.invoke('get-server-settings', domain);
|
||||
}
|
||||
|
||||
export async function saveServerIcon(server: ServerConf): Promise<string> {
|
||||
const url = server.icon;
|
||||
const domain = server.url;
|
||||
|
||||
const serverIconOptions = {
|
||||
url,
|
||||
...RequestUtil.requestOptions(domain)
|
||||
};
|
||||
|
||||
// The save will always succeed. If url is invalid, downgrade to default icon.
|
||||
return new Promise(resolve => {
|
||||
const filePath = generateFilePath(url);
|
||||
const file = fs.createWriteStream(filePath);
|
||||
try {
|
||||
request(serverIconOptions).on('response', (response: request.Response) => {
|
||||
response.on('error', (err: Error) => {
|
||||
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: Error) => {
|
||||
logger.log('Could not get server icon.');
|
||||
logger.log(err);
|
||||
logger.reportSentry(err);
|
||||
resolve(defaultIconUrl);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.log('Could not get server icon.');
|
||||
logger.log(error);
|
||||
logger.reportSentry(error);
|
||||
resolve(defaultIconUrl);
|
||||
}
|
||||
});
|
||||
return ipcRenderer.invoke('save-server-icon', server.icon);
|
||||
}
|
||||
|
||||
export async function updateSavedServer(url: string, index: number): Promise<void> {
|
||||
@@ -256,25 +191,6 @@ function reloadDB(): void {
|
||||
db = new JsonDB(domainJsonPath, true, true);
|
||||
}
|
||||
|
||||
function generateFilePath(url: string): string {
|
||||
const dir = `${app.getPath('userData')}/server-icons`;
|
||||
const extension = path.extname(url).split('?')[0];
|
||||
|
||||
let hash = 5381;
|
||||
let {length} = url;
|
||||
|
||||
while (length) {
|
||||
hash = (hash * 33) ^ url.charCodeAt(--length);
|
||||
}
|
||||
|
||||
// Create 'server-icons' directory if not existed
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
|
||||
return `${dir}/${hash >>> 0}${extension}`;
|
||||
}
|
||||
|
||||
export function formatUrl(domain: string): string {
|
||||
if (domain.startsWith('http://') || domain.startsWith('https://')) {
|
||||
return domain;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {shell} from 'electron';
|
||||
import escape from 'escape-html';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import escape from 'escape-html';
|
||||
|
||||
export function isUploadsUrl(server: string, url: URL): boolean {
|
||||
return url.origin === server && url.pathname.startsWith('/user_uploads/');
|
||||
}
|
||||
@@ -36,7 +37,7 @@ export async function openBrowser(url: URL): Promise<void> {
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
shell.openItem(file);
|
||||
await shell.openPath(file);
|
||||
setTimeout(() => {
|
||||
fs.unlinkSync(file);
|
||||
fs.rmdirSync(dir);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {JsonDB} from 'node-json-db';
|
||||
|
||||
import electron from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import electron from 'electron';
|
||||
|
||||
import {JsonDB} from 'node-json-db';
|
||||
|
||||
import Logger from './logger-util';
|
||||
|
||||
const remote =
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import {Console} from 'console'; // eslint-disable-line node/prefer-global/console
|
||||
import {initSetUp} from './default-util';
|
||||
import {sentryInit, captureException} from './sentry-util';
|
||||
|
||||
import electron from 'electron';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
|
||||
import isDev from 'electron-is-dev';
|
||||
import electron from 'electron';
|
||||
|
||||
import {initSetUp} from './default-util';
|
||||
import {sentryInit, captureException} from './sentry-util';
|
||||
|
||||
interface LoggerOptions {
|
||||
timestamp?: true | (() => string);
|
||||
@@ -89,20 +90,21 @@ export default class Logger {
|
||||
nodeConsole, timestamp, level, logInDevMode
|
||||
} = this;
|
||||
|
||||
/* eslint-disable no-fallthrough */
|
||||
switch (true) {
|
||||
case typeof timestamp === 'function':
|
||||
args.unshift(timestamp() + ' |\t');
|
||||
// Fall through
|
||||
|
||||
case (level):
|
||||
args.unshift(type.toUpperCase() + ' |');
|
||||
// Fall through
|
||||
|
||||
case isDev || logInDevMode:
|
||||
nodeConsole[type](...args);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
default:
|
||||
}
|
||||
/* eslint-enable no-fallthrough */
|
||||
|
||||
console[type](...args);
|
||||
}
|
||||
|
||||
@@ -5,45 +5,6 @@ export interface ProxyRule {
|
||||
port?: number;
|
||||
}
|
||||
|
||||
// Return proxy to be used for a particular uri, to be used for request
|
||||
export function getProxy(_uri: string): ProxyRule | void {
|
||||
let uri;
|
||||
try {
|
||||
uri = new URL(_uri);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const proxyRules = ConfigUtil.getConfigItem('proxyRules', '').split(';');
|
||||
// If SPS is on and system uses no proxy then request should not try to use proxy from
|
||||
// environment. NO_PROXY = '*' makes request ignore all environment proxy variables.
|
||||
if (proxyRules[0] === '') {
|
||||
process.env.NO_PROXY = '*';
|
||||
return;
|
||||
}
|
||||
|
||||
const proxyRule: any = {};
|
||||
if (uri.protocol === 'http:') {
|
||||
proxyRules.forEach((proxy: string) => {
|
||||
if (proxy.includes('http=')) {
|
||||
proxyRule.hostname = proxy.split('http=')[1].trim().split(':')[0];
|
||||
proxyRule.port = proxy.split('http=')[1].trim().split(':')[1];
|
||||
}
|
||||
});
|
||||
return proxyRule;
|
||||
}
|
||||
|
||||
if (uri.protocol === 'https:') {
|
||||
proxyRules.forEach((proxy: string) => {
|
||||
if (proxy.includes('https=')) {
|
||||
proxyRule.hostname = proxy.split('https=')[1].trim().split(':')[0];
|
||||
proxyRule.port = proxy.split('https=')[1].trim().split(':')[1];
|
||||
}
|
||||
});
|
||||
return proxyRule;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Refactor to async function
|
||||
export async function resolveSystemProxy(mainWindow: Electron.BrowserWindow): Promise<void> {
|
||||
const page = mainWindow.webContents;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import type WebView from '../components/webview';
|
||||
import backoff from 'backoff';
|
||||
import request from 'request';
|
||||
|
||||
import type WebView from '../components/webview';
|
||||
|
||||
import Logger from './logger-util';
|
||||
import * as RequestUtil from './request-util';
|
||||
|
||||
const logger = new Logger({
|
||||
file: 'domain-util.log',
|
||||
@@ -32,23 +32,7 @@ export default class ReconnectUtil {
|
||||
}
|
||||
|
||||
async isOnline(): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
try {
|
||||
request(
|
||||
{
|
||||
url: `${this.url}/static/favicon.ico`,
|
||||
...RequestUtil.requestOptions(this.url)
|
||||
},
|
||||
(error: Error, response: request.Response) => {
|
||||
const isValidResponse =
|
||||
!error && response.statusCode >= 200 && response.statusCode < 400;
|
||||
resolve(isValidResponse);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logger.log(error);
|
||||
}
|
||||
});
|
||||
return ipcRenderer.invoke('is-online', this.url);
|
||||
}
|
||||
|
||||
pollInternetAndReload(): void {
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import {remote} from 'electron';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import * as ConfigUtil from './config-util';
|
||||
import Logger from './logger-util';
|
||||
import * as ProxyUtil from './proxy-util';
|
||||
import * as CertificateUtil from './certificate-util';
|
||||
import * as SystemUtil from './system-util';
|
||||
|
||||
const {app} = remote;
|
||||
|
||||
const logger = new Logger({
|
||||
file: 'request-util.log',
|
||||
timestamp: true
|
||||
});
|
||||
|
||||
interface RequestUtilResponse {
|
||||
ca: string;
|
||||
proxy: string | void | ProxyUtil.ProxyRule;
|
||||
ecdhCurve: 'auto';
|
||||
headers: { 'User-Agent': string };
|
||||
}
|
||||
|
||||
export function requestOptions(domain: string): RequestUtilResponse {
|
||||
domain = formatUrl(domain);
|
||||
const certificate = CertificateUtil.getCertificate(
|
||||
encodeURIComponent(domain)
|
||||
);
|
||||
|
||||
let certificateFile = null;
|
||||
if (certificate?.includes('/')) {
|
||||
// Certificate saved using old app version
|
||||
certificateFile = certificate;
|
||||
} else if (certificate) {
|
||||
certificateFile = path.join(`${app.getPath('userData')}/certificates`, certificate);
|
||||
}
|
||||
|
||||
let certificateLocation = '';
|
||||
if (certificate) {
|
||||
// To handle case where certificate has been moved from the location in certificates.json
|
||||
try {
|
||||
certificateLocation = fs.readFileSync(certificateFile, 'utf8');
|
||||
} catch (error) {
|
||||
logger.warn('Error while trying to get certificate:', error);
|
||||
}
|
||||
}
|
||||
|
||||
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()}
|
||||
};
|
||||
}
|
||||
|
||||
function formatUrl(domain: string): string {
|
||||
const hasPrefix = domain.startsWith('http', 0);
|
||||
if (hasPrefix) {
|
||||
return domain;
|
||||
}
|
||||
|
||||
return domain.includes('localhost:') ? `http://${domain}` : `https://${domain}`;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import {init} from '@sentry/electron';
|
||||
|
||||
import isDev from 'electron-is-dev';
|
||||
|
||||
export const sentryInit = (): void => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import os from 'os';
|
||||
|
||||
export const connectivityERR: string[] = [
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import path from 'path';
|
||||
|
||||
import i18n from 'i18n';
|
||||
|
||||
import * as ConfigUtil from './config-util';
|
||||
|
||||
i18n.configure({
|
||||
|
||||
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 436 B After Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 900 B After Width: | Height: | Size: 631 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 932 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 13 KiB |
@@ -81,7 +81,7 @@
|
||||
"Shortcuts": "Shortcuts",
|
||||
"Show App Logs": "Show App Logs",
|
||||
"Show app icon in system tray": "Show app icon in system tray",
|
||||
"Show app unread badge": "Mostrar una marca en la icona si hi ha missatges sense llegir",
|
||||
"Show app unread badge": "Mostrar una marca en la icona si hi ha missatges no llegits",
|
||||
"Show desktop notifications": "Show desktop notifications",
|
||||
"Show downloaded files in file manager": "Show downloaded files in file manager",
|
||||
"Show sidebar": "Show sidebar",
|
||||
|
||||
@@ -20,57 +20,57 @@
|
||||
"Change": "Skift",
|
||||
"Check for Updates": "Tjek for opdateringer",
|
||||
"Close": "Luk",
|
||||
"Connect": "Forbind",
|
||||
"Connect": "Tilslut",
|
||||
"Connect to another organization": "Forbind til en anden organisation",
|
||||
"Connected organizations": "Tilsluttede organisationer",
|
||||
"Copy": "Kopiér",
|
||||
"Copy Zulip URL": "Kopiér Zulip URL",
|
||||
"Create a new organization": "Opret ny organisation",
|
||||
"Cut": "Klip",
|
||||
"Default download location": "Default download location",
|
||||
"Default download location": "Default download placering",
|
||||
"Delete": "Slet",
|
||||
"Desktop App Settings": "Desktop App Settings",
|
||||
"Desktop App Settings": "Desktop app-indstillinger",
|
||||
"Desktop Notifications": "Desktop-notifikationer",
|
||||
"Desktop Settings": "Desktop-indstillinger",
|
||||
"Disconnect": "Frakobl",
|
||||
"Download App Logs": "Download App-logfiler",
|
||||
"Download App Logs": "Download app-logfiler",
|
||||
"Edit": "Redigér",
|
||||
"Edit Shortcuts": "Edit Shortcuts",
|
||||
"Edit Shortcuts": "Redigér genveje",
|
||||
"Enable auto updates": "Aktivér auto-opdateringer",
|
||||
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
|
||||
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
|
||||
"Factory Reset": "Factory Reset",
|
||||
"Enable error reporting (requires restart)": "Aktivér fejlrapportering (kræver genstart)",
|
||||
"Enable spellchecker (requires restart)": "Aktivér stavekontrol (kræver genstart)",
|
||||
"Factory Reset": "Nulstil til fabriksindstillinger",
|
||||
"File": "Fil",
|
||||
"Find accounts": "Find accounts",
|
||||
"Find accounts": "Find konti",
|
||||
"Find accounts by email": "Find accounts by email",
|
||||
"Flash taskbar on new message": "Flash taskbar on new message",
|
||||
"Forward": "Forward",
|
||||
"Functionality": "Functionality",
|
||||
"General": "General",
|
||||
"Get beta updates": "Get beta updates",
|
||||
"Hard Reload": "Hard Reload",
|
||||
"Help": "Help",
|
||||
"Help Center": "Help Center",
|
||||
"History": "History",
|
||||
"History Shortcuts": "History Shortcuts",
|
||||
"Keyboard Shortcuts": "Keyboard Shortcuts",
|
||||
"Log Out": "Log Out",
|
||||
"Log Out of Organization": "Log Out of Organization",
|
||||
"Manual proxy configuration": "Manual proxy configuration",
|
||||
"Minimize": "Minimize",
|
||||
"Functionality": "Funktionalitet",
|
||||
"General": "Generelt",
|
||||
"Get beta updates": "Få beta opdateringer",
|
||||
"Hard Reload": "Hård reload",
|
||||
"Help": "Hjælp",
|
||||
"Help Center": "Hjælpecenter",
|
||||
"History": "Historik",
|
||||
"History Shortcuts": "Historik genveje",
|
||||
"Keyboard Shortcuts": "Tastatur genveje",
|
||||
"Log Out": "Log ud",
|
||||
"Log Out of Organization": "Log ud af organisation",
|
||||
"Manual proxy configuration": "Manuel proxy opsætning",
|
||||
"Minimize": "Minimér",
|
||||
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
|
||||
"NO": "NO",
|
||||
"Network": "Network",
|
||||
"OR": "OR",
|
||||
"Organization URL": "Organization URL",
|
||||
"Organizations": "Organizations",
|
||||
"Paste": "Paste",
|
||||
"Paste and Match Style": "Paste and Match Style",
|
||||
"NO": "NEJ",
|
||||
"Network": "Netværk",
|
||||
"OR": "ELLER",
|
||||
"Organization URL": "Organisation URL",
|
||||
"Organizations": "Organisationer",
|
||||
"Paste": "Indsæt",
|
||||
"Paste and Match Style": "Indsæt med samme formattering",
|
||||
"Proxy": "Proxy",
|
||||
"Proxy bypass rules": "Proxy bypass rules",
|
||||
"Proxy rules": "Proxy rules",
|
||||
"Quit": "Quit",
|
||||
"Quit Zulip": "Quit Zulip",
|
||||
"Proxy bypass rules": "Proxy bypass regler",
|
||||
"Proxy rules": "Proxy regler",
|
||||
"Quit": "Luk",
|
||||
"Quit Zulip": "Luk Zulip",
|
||||
"Redo": "Redo",
|
||||
"Release Notes": "Release Notes",
|
||||
"Reload": "Reload",
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
"All the connected organizations will appear here": "Alle verbundenen Organisationen erscheinen hier",
|
||||
"Always start minimized": "Immer minimiert öffnen",
|
||||
"App Updates": "App Updates",
|
||||
"Appearance": "Ansicht",
|
||||
"Appearance": "Erscheinungsbild",
|
||||
"Application Shortcuts": "App-Verknüpfungen",
|
||||
"Are you sure you want to disconnect this organization?": "Bist du dir sicher, dass du die Verbindung zur Organisation trennen möchtest?",
|
||||
"Auto hide Menu bar": "Menü automatisch verstecken",
|
||||
"Auto hide menu bar (Press Alt key to display)": "Menü automatisch verstecken (Zum anzeigen die Alt-Taste drücken)",
|
||||
"Auto hide menu bar (Press Alt key to display)": "Menü automatisch verstecken (zum Anzeigen die Alt-Taste drücken)",
|
||||
"Back": "Zurück",
|
||||
"Bounce dock on new private message": "Im Dock hüpfen, wenn neue private Nachrichten eingehen",
|
||||
"Certificate file": "Zertifikatsdatei",
|
||||
@@ -42,7 +42,7 @@
|
||||
"Factory Reset": "Alle Einstellungen auf Standardwerte zurücksetzen",
|
||||
"File": "Datei",
|
||||
"Find accounts": "Accounts finden",
|
||||
"Find accounts by email": "Accounts anhand E-Mail finden",
|
||||
"Find accounts by email": "Accounts anhand E-Mail-Adresse finden",
|
||||
"Flash taskbar on new message": "Farbliche Hervorhebung in Taskbar bei neuen Nachrichten",
|
||||
"Forward": "Weiter",
|
||||
"Functionality": "Funktionalität",
|
||||
@@ -93,7 +93,7 @@
|
||||
"Tip": "Tipp",
|
||||
"Toggle DevTools for Active Tab": "Entwickler-Tools für aktiven Tab umschalten",
|
||||
"Toggle DevTools for Zulip App": "Entwickler-Tools für Zulip-App umschalten",
|
||||
"Toggle Do Not Disturb": "Nicht-stören-Modus umschalten",
|
||||
"Toggle Do Not Disturb": "Nicht-Stören-Modus umschalten",
|
||||
"Toggle Full Screen": "Vollbildschirm umschalten",
|
||||
"Toggle Sidebar": "Seitenleiste umschalten",
|
||||
"Toggle Tray Icon": "Tray-Icon umschalten",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"About Zulip": "About Zulip",
|
||||
"Actual Size": "Actual Size",
|
||||
"Add Custom Certificates": "Add Custom Certificates",
|
||||
"Add Organization": "Add Organization",
|
||||
"Add Organization": "Add Organisation",
|
||||
"Add a Zulip organization": "Add a Zulip organisation",
|
||||
"Add custom CSS": "Add custom CSS",
|
||||
"Advanced": "Advanced",
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"About Zulip": "Tietoa Zulipista",
|
||||
"Actual Size": "Actual Size",
|
||||
"Add Custom Certificates": "Add Custom Certificates",
|
||||
"Add Organization": "Add Organization",
|
||||
"Add a Zulip organization": "Add a Zulip organization",
|
||||
"Add custom CSS": "Add custom CSS",
|
||||
"Advanced": "Advanced",
|
||||
"Actual Size": "Varsinainen koko",
|
||||
"Add Custom Certificates": "Lisää omia sertifikaatteja",
|
||||
"Add Organization": "Lisää organisaatio",
|
||||
"Add a Zulip organization": "Lisää Zulip-organisaatio",
|
||||
"Add custom CSS": "Lisää oma CSS",
|
||||
"Advanced": "Edistynyt",
|
||||
"All the connected organizations will appear here": "All the connected organizations will appear here",
|
||||
"Always start minimized": "Always start minimized",
|
||||
"App Updates": "App Updates",
|
||||
"Appearance": "Appearance",
|
||||
"Application Shortcuts": "Application Shortcuts",
|
||||
"Always start minimized": "Aloita aina pienennettynä",
|
||||
"App Updates": "Sovellspäivitykset",
|
||||
"Appearance": "Ulkonäkö",
|
||||
"Application Shortcuts": "Sovellusoikotiet",
|
||||
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
|
||||
"Auto hide Menu bar": "Auto hide Menu bar",
|
||||
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
|
||||
@@ -20,19 +20,19 @@
|
||||
"Change": "Muuta",
|
||||
"Check for Updates": "Check for Updates",
|
||||
"Close": "Sulje",
|
||||
"Connect": "Connect",
|
||||
"Connect": "Yhdistä",
|
||||
"Connect to another organization": "Connect to another organization",
|
||||
"Connected organizations": "Connected organizations",
|
||||
"Copy": "Copy",
|
||||
"Copy Zulip URL": "Copy Zulip URL",
|
||||
"Create a new organization": "Create a new organization",
|
||||
"Cut": "Cut",
|
||||
"Copy": "Kopioi",
|
||||
"Copy Zulip URL": "Kopioi Zulip-URL",
|
||||
"Create a new organization": "Luo uusi organisaatio",
|
||||
"Cut": "Leikkaa",
|
||||
"Default download location": "Default download location",
|
||||
"Delete": "Poista",
|
||||
"Desktop App Settings": "Desktop App Settings",
|
||||
"Desktop Notifications": "Desktop Notifications",
|
||||
"Desktop Settings": "Desktop Settings",
|
||||
"Disconnect": "Disconnect",
|
||||
"Disconnect": "Katkaise",
|
||||
"Download App Logs": "Download App Logs",
|
||||
"Edit": "Muokkaa",
|
||||
"Edit Shortcuts": "Edit Shortcuts",
|
||||
@@ -49,8 +49,8 @@
|
||||
"General": "General",
|
||||
"Get beta updates": "Get beta updates",
|
||||
"Hard Reload": "Hard Reload",
|
||||
"Help": "Help",
|
||||
"Help Center": "Help Center",
|
||||
"Help": "Ohje",
|
||||
"Help Center": "Tukikeskus",
|
||||
"History": "History",
|
||||
"History Shortcuts": "History Shortcuts",
|
||||
"Keyboard Shortcuts": "Keyboard Shortcuts",
|
||||
@@ -59,26 +59,26 @@
|
||||
"Manual proxy configuration": "Manual proxy configuration",
|
||||
"Minimize": "Minimize",
|
||||
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
|
||||
"NO": "NO",
|
||||
"NO": "EI",
|
||||
"Network": "Network",
|
||||
"OR": "TAI",
|
||||
"Organization URL": "Organisaation URL",
|
||||
"Organizations": "Organizations",
|
||||
"Organizations": "Organisaatiot",
|
||||
"Paste": "Liitä",
|
||||
"Paste and Match Style": "Paste and Match Style",
|
||||
"Proxy": "Proxy",
|
||||
"Paste and Match Style": "Liitä ja täsmää tyylit",
|
||||
"Proxy": "Välipalvelin",
|
||||
"Proxy bypass rules": "Proxy bypass rules",
|
||||
"Proxy rules": "Proxy rules",
|
||||
"Quit": "Lopeta",
|
||||
"Quit Zulip": "Quit Zulip",
|
||||
"Redo": "Redo",
|
||||
"Quit Zulip": "Lopeta Zulip",
|
||||
"Redo": "Toista",
|
||||
"Release Notes": "Release Notes",
|
||||
"Reload": "Reload",
|
||||
"Reload": "Lataa uudelleen",
|
||||
"Report an Issue": "Report an Issue",
|
||||
"Save": "Talleta",
|
||||
"Save": "Tallenna",
|
||||
"Select All": "Valitse kaikki",
|
||||
"Settings": "Asetukset",
|
||||
"Shortcuts": "Shortcuts",
|
||||
"Shortcuts": "Oikopolut",
|
||||
"Show App Logs": "Show App Logs",
|
||||
"Show app icon in system tray": "Show app icon in system tray",
|
||||
"Show app unread badge": "Show app unread badge",
|
||||
@@ -90,22 +90,22 @@
|
||||
"Switch to Previous Organization": "Switch to Previous Organization",
|
||||
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
|
||||
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
|
||||
"Tip": "Tip",
|
||||
"Tip": "Vinkki",
|
||||
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
|
||||
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
|
||||
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
|
||||
"Toggle Full Screen": "Toggle Full Screen",
|
||||
"Toggle Sidebar": "Toggle Sidebar",
|
||||
"Toggle Tray Icon": "Toggle Tray Icon",
|
||||
"Tools": "Tools",
|
||||
"Undo": "Undo",
|
||||
"Tools": "Työkalut",
|
||||
"Undo": "Peru",
|
||||
"Upload": "Upload",
|
||||
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
|
||||
"View": "View",
|
||||
"View": "Näytä",
|
||||
"View Shortcuts": "View Shortcuts",
|
||||
"Window": "Window",
|
||||
"Window": "Ikkuna",
|
||||
"Window Shortcuts": "Window Shortcuts",
|
||||
"YES": "YES",
|
||||
"YES": "KYLLÄ",
|
||||
"Zoom In": "Zoom In",
|
||||
"Zoom Out": "Zoom Out",
|
||||
"Zulip Help": "Zulip Help",
|
||||
|
||||
@@ -112,23 +112,23 @@
|
||||
"keyboard shortcuts": "gyorsbillentyűk",
|
||||
"script": "parancsfájl",
|
||||
"Quit when the window is closed": "Kilépés az ablak bezárásakor",
|
||||
"Ask where to save files before downloading": "Ask where to save files before downloading",
|
||||
"Services": "Services",
|
||||
"Hide": "Hide",
|
||||
"Hide Others": "Hide Others",
|
||||
"Unhide": "Unhide",
|
||||
"AddServer": "AddServer",
|
||||
"App language (requires restart)": "App language (requires restart)",
|
||||
"Factory Reset Data": "Factory Reset Data",
|
||||
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
|
||||
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
|
||||
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
|
||||
"Copy Link": "Copy Link",
|
||||
"Copy Image": "Copy Image",
|
||||
"Copy Image URL": "Copy Image URL",
|
||||
"No Suggestion Found": "No Suggestion Found",
|
||||
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
|
||||
"Spellchecker Languages": "Spellchecker Languages",
|
||||
"Add to Dictionary": "Add to Dictionary",
|
||||
"Look Up": "Look Up"
|
||||
"Ask where to save files before downloading": "Letöltés előtt kérdezze meg a mentés helyét",
|
||||
"Services": "Szolgáltatások",
|
||||
"Hide": "Elrejtés",
|
||||
"Hide Others": "A többi elrejtése",
|
||||
"Unhide": "Felfedés",
|
||||
"AddServer": "Szerver hozzáadás",
|
||||
"App language (requires restart)": "Alkalmazás nyelve (újraindítást igényel)",
|
||||
"Factory Reset Data": "Gyári adatok visszaállítása",
|
||||
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Alkalmazásadatok törlése - törlődnek a csatlakoztatott szervezetek, fiókok és tanúsítványok is.",
|
||||
"On macOS, the OS spellchecker is used.": "MacOS rendszereken az operációs rendszer helyesírásellenőrzőjét használjuk.",
|
||||
"Change the language from System Preferences → Keyboard → Text → Spelling.": "A nyelv a Rendszerbeállítások → Billentyűzet → Szöveg → Helyesírás pontban változtatható meg.",
|
||||
"Copy Link": "Hivatkozás másolása",
|
||||
"Copy Image": "Kép másolása",
|
||||
"Copy Image URL": "Kép címének másolása",
|
||||
"No Suggestion Found": "Nem találtunk javaslatot",
|
||||
"You can select a maximum of 3 languages for spellchecking.": "Maximum 3 nyelv választható ki helyesírásellenőrzéshez.",
|
||||
"Spellchecker Languages": "Helyesírásellenőrzés nyelvei",
|
||||
"Add to Dictionary": "Hozzáadás a szótárhoz",
|
||||
"Look Up": "Keresés"
|
||||
}
|
||||
|
||||
@@ -105,23 +105,23 @@
|
||||
"View Shortcuts": "Visualizza Scorciatoie",
|
||||
"Window": "Finestra",
|
||||
"Window Shortcuts": "Scorciatoie Finestra",
|
||||
"YES": "YES",
|
||||
"Zoom In": "Zoom Più",
|
||||
"Zoom Out": "Zoom Meno",
|
||||
"Zulip Help": "Aiuto Zulip",
|
||||
"YES": "SI",
|
||||
"Zoom In": "Ingrandisci",
|
||||
"Zoom Out": "Riduci",
|
||||
"Zulip Help": "Guida di Zulip",
|
||||
"keyboard shortcuts": "scorciatoie tastiera",
|
||||
"script": "script",
|
||||
"Quit when the window is closed": "Esci quando la finestra è chiusa",
|
||||
"Ask where to save files before downloading": "Chiedi dove salvare i file prima di scaricarli",
|
||||
"Services": "Servizi",
|
||||
"Hide": "Nascondi",
|
||||
"Hide Others": "Hide Others",
|
||||
"Unhide": "Unhide",
|
||||
"Hide Others": "Nascondi gli altri",
|
||||
"Unhide": "Mostra",
|
||||
"AddServer": "AddServer",
|
||||
"App language (requires restart)": "App language (requires restart)",
|
||||
"App language (requires restart)": "Lingua applicazione (richiede riavvio)",
|
||||
"Factory Reset Data": "Factory Reset Data",
|
||||
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
|
||||
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
|
||||
"On macOS, the OS spellchecker is used.": "Su macOS, è usato il correttore ortografico di sistema.",
|
||||
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
|
||||
"Copy Link": "Copia link",
|
||||
"Copy Image": "Copia immagine",
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
"Connect to another organization": "Подключиться к другой организации",
|
||||
"Connected organizations": "Подключенные организации",
|
||||
"Copy": "Копировать",
|
||||
"Copy Zulip URL": "Скопировать URL Zulip",
|
||||
"Copy Zulip URL": "Скопировать ссылку на сервер Zulip",
|
||||
"Create a new organization": "Создать новую организацию",
|
||||
"Cut": "Вырезать",
|
||||
"Default download location": "Папка для загрузки",
|
||||
"Delete": "Удалить",
|
||||
"Desktop App Settings": "Настройки приложения",
|
||||
"Desktop Notifications": "Всплывающие оповещения",
|
||||
"Desktop Notifications": "Оповещения на рабочем столе",
|
||||
"Desktop Settings": "Настройки рабочего стола",
|
||||
"Disconnect": "Отключиться",
|
||||
"Download App Logs": "Скачать логи приложения",
|
||||
@@ -111,7 +111,7 @@
|
||||
"Zulip Help": "Помощь по Zulip",
|
||||
"keyboard shortcuts": "Горячие клавиши",
|
||||
"script": "скрипт",
|
||||
"Quit when the window is closed": "Выходить при закрытии окна",
|
||||
"Quit when the window is closed": "Выйти, когда окно будет закрыто",
|
||||
"Ask where to save files before downloading": "Спрашивать, где сохранять файлы перед скачиванием",
|
||||
"Services": "Сервисы",
|
||||
"Hide": "Скрыть",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"cs": "česky",
|
||||
"da": "Dansk",
|
||||
"de": "Deutsch",
|
||||
"el_GR": "Greek (Greece)",
|
||||
"el": "Ελληνικά",
|
||||
"el-GR": "Greek (Greece)",
|
||||
"en_GB": "English (UK)",
|
||||
"en": "English (US)",
|
||||
"es": "Español",
|
||||
@@ -29,6 +29,7 @@
|
||||
"pt": "Português",
|
||||
"ro": "Română",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovak",
|
||||
"sr": "српски",
|
||||
"sv": "svenska",
|
||||
"ta": "தமிழ்",
|
||||
@@ -37,6 +38,6 @@
|
||||
"uz": "O'zbek",
|
||||
"vi": "Tiếng Việt",
|
||||
"zh_TW": "中文 (傳統的)",
|
||||
"zh-hans": "简体中文",
|
||||
"zh-hant": "繁體中文"
|
||||
"zh-Hans": "简体中文",
|
||||
"zh-Hant": "繁體中文"
|
||||
}
|
||||
@@ -1,134 +1,134 @@
|
||||
{
|
||||
"About Zulip": "About Zulip",
|
||||
"Actual Size": "Actual Size",
|
||||
"Add Custom Certificates": "Add Custom Certificates",
|
||||
"Add Organization": "Add Organization",
|
||||
"Add a Zulip organization": "Add a Zulip organization",
|
||||
"Add custom CSS": "Add custom CSS",
|
||||
"Advanced": "Advanced",
|
||||
"All the connected organizations will appear here": "All the connected organizations will appear here",
|
||||
"Always start minimized": "Always start minimized",
|
||||
"App Updates": "App Updates",
|
||||
"Appearance": "Appearance",
|
||||
"Application Shortcuts": "Application Shortcuts",
|
||||
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
|
||||
"Auto hide Menu bar": "Auto hide Menu bar",
|
||||
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
|
||||
"Back": "Back",
|
||||
"Bounce dock on new private message": "Bounce dock on new private message",
|
||||
"Certificate file": "Certificate file",
|
||||
"Change": "Change",
|
||||
"Check for Updates": "Check for Updates",
|
||||
"Close": "Close",
|
||||
"Connect": "Connect",
|
||||
"Connect to another organization": "Connect to another organization",
|
||||
"Connected organizations": "Connected organizations",
|
||||
"Copy": "Copy",
|
||||
"Copy Zulip URL": "Copy Zulip URL",
|
||||
"Create a new organization": "Create a new organization",
|
||||
"Cut": "Cut",
|
||||
"Default download location": "Default download location",
|
||||
"Delete": "Delete",
|
||||
"Desktop App Settings": "Desktop App Settings",
|
||||
"Desktop Notifications": "Desktop Notifications",
|
||||
"Desktop Settings": "Desktop Settings",
|
||||
"Disconnect": "Disconnect",
|
||||
"Download App Logs": "Download App Logs",
|
||||
"Edit": "Edit",
|
||||
"Edit Shortcuts": "Edit Shortcuts",
|
||||
"Enable auto updates": "Enable auto updates",
|
||||
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
|
||||
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
|
||||
"Factory Reset": "Factory Reset",
|
||||
"File": "File",
|
||||
"Find accounts": "Find accounts",
|
||||
"Find accounts by email": "Find accounts by email",
|
||||
"Flash taskbar on new message": "Flash taskbar on new message",
|
||||
"Forward": "Forward",
|
||||
"Functionality": "Functionality",
|
||||
"General": "General",
|
||||
"Get beta updates": "Get beta updates",
|
||||
"Hard Reload": "Hard Reload",
|
||||
"Help": "Help",
|
||||
"Help Center": "Help Center",
|
||||
"History": "History",
|
||||
"History Shortcuts": "History Shortcuts",
|
||||
"Keyboard Shortcuts": "Keyboard Shortcuts",
|
||||
"Log Out": "Log Out",
|
||||
"Log Out of Organization": "Log Out of Organization",
|
||||
"Manual proxy configuration": "Manual proxy configuration",
|
||||
"Minimize": "Minimize",
|
||||
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
|
||||
"NO": "NO",
|
||||
"Network": "Network",
|
||||
"About Zulip": "關於 Zulip",
|
||||
"Actual Size": "實際大小",
|
||||
"Add Custom Certificates": "新增自定義證書",
|
||||
"Add Organization": "新增組織",
|
||||
"Add a Zulip organization": "新增 Zulip 組織",
|
||||
"Add custom CSS": "新增自定義 CSS",
|
||||
"Advanced": "\b\b進階",
|
||||
"All the connected organizations will appear here": "所有已連結的組織將會顯示於此",
|
||||
"Always start minimized": "始終最小化開啟",
|
||||
"App Updates": "應用程式更新",
|
||||
"Appearance": "外觀",
|
||||
"Application Shortcuts": "應用程式快捷鍵",
|
||||
"Are you sure you want to disconnect this organization?": "您確定要斷開與此組織之連結?",
|
||||
"Auto hide Menu bar": "自動隱藏功能選單",
|
||||
"Auto hide menu bar (Press Alt key to display)": "自動隱藏功能選單(按 Alt 鍵顯示)",
|
||||
"Back": "返回",
|
||||
"Bounce dock on new private message": "Bounce dock on 新的私人訊息",
|
||||
"Certificate file": "證書檔案",
|
||||
"Change": "修改",
|
||||
"Check for Updates": "檢查更新",
|
||||
"Close": "關閉",
|
||||
"Connect": "連結",
|
||||
"Connect to another organization": "連結至其他組織",
|
||||
"Connected organizations": "已連結的組織",
|
||||
"Copy": "複製",
|
||||
"Copy Zulip URL": "複製 Zulip 網址",
|
||||
"Create a new organization": "新增組織",
|
||||
"Cut": "剪下",
|
||||
"Default download location": "預設下載位置",
|
||||
"Delete": "刪除",
|
||||
"Desktop App Settings": "桌面版應用程式設定",
|
||||
"Desktop Notifications": "桌面版通知",
|
||||
"Desktop Settings": "桌面版設定",
|
||||
"Disconnect": "斷開連結",
|
||||
"Download App Logs": "下載應用程式紀錄",
|
||||
"Edit": "編輯",
|
||||
"Edit Shortcuts": "編輯快捷鍵",
|
||||
"Enable auto updates": "啟用自動更新",
|
||||
"Enable error reporting (requires restart)": "啟用錯誤回報(需要重新啟動)",
|
||||
"Enable spellchecker (requires restart)": "啟用拼字檢查(需要重新啟動)",
|
||||
"Factory Reset": "初始化",
|
||||
"File": "檔案",
|
||||
"Find accounts": "查詢帳號",
|
||||
"Find accounts by email": "用 email 查詢帳號",
|
||||
"Flash taskbar on new message": "有新訊息時閃爍任務欄",
|
||||
"Forward": "轉發",
|
||||
"Functionality": "功能性",
|
||||
"General": "通用",
|
||||
"Get beta updates": "取得 beta 更新",
|
||||
"Hard Reload": "強制重新載入",
|
||||
"Help": "幫助",
|
||||
"Help Center": "幫助中心",
|
||||
"History": "歷史",
|
||||
"History Shortcuts": "歷史快捷鍵",
|
||||
"Keyboard Shortcuts": "Keyboard 快捷鍵",
|
||||
"Log Out": "登出",
|
||||
"Log Out of Organization": "登出此組織",
|
||||
"Manual proxy configuration": "proxy 設定",
|
||||
"Minimize": "最小化",
|
||||
"Mute all sounds from Zulip": "將所有 Zulip 音效靜音",
|
||||
"NO": "否",
|
||||
"Network": "網路",
|
||||
"OR": "或",
|
||||
"Organization URL": "Organization URL",
|
||||
"Organizations": "Organizations",
|
||||
"Paste": "Paste",
|
||||
"Paste and Match Style": "Paste and Match Style",
|
||||
"Organization URL": "組織網址",
|
||||
"Organizations": "組織",
|
||||
"Paste": "貼上",
|
||||
"Paste and Match Style": "貼上並套用樣式",
|
||||
"Proxy": "Proxy",
|
||||
"Proxy bypass rules": "Proxy bypass rules",
|
||||
"Proxy rules": "Proxy rules",
|
||||
"Quit": "Quit",
|
||||
"Quit Zulip": "Quit Zulip",
|
||||
"Redo": "Redo",
|
||||
"Release Notes": "Release Notes",
|
||||
"Reload": "Reload",
|
||||
"Report an Issue": "Report an Issue",
|
||||
"Save": "Save",
|
||||
"Select All": "Select All",
|
||||
"Settings": "Settings",
|
||||
"Shortcuts": "Shortcuts",
|
||||
"Show App Logs": "Show App Logs",
|
||||
"Show app icon in system tray": "Show app icon in system tray",
|
||||
"Show app unread badge": "Show app unread badge",
|
||||
"Show desktop notifications": "Show desktop notifications",
|
||||
"Show downloaded files in file manager": "Show downloaded files in file manager",
|
||||
"Show sidebar": "Show sidebar",
|
||||
"Start app at login": "Start app at login",
|
||||
"Switch to Next Organization": "Switch to Next Organization",
|
||||
"Switch to Previous Organization": "Switch to Previous Organization",
|
||||
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
|
||||
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
|
||||
"Tip": "Tip",
|
||||
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
|
||||
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
|
||||
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
|
||||
"Toggle Full Screen": "Toggle Full Screen",
|
||||
"Toggle Sidebar": "Toggle Sidebar",
|
||||
"Toggle Tray Icon": "Toggle Tray Icon",
|
||||
"Tools": "Tools",
|
||||
"Undo": "Undo",
|
||||
"Upload": "Upload",
|
||||
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
|
||||
"View": "View",
|
||||
"View Shortcuts": "View Shortcuts",
|
||||
"Window": "Window",
|
||||
"Window Shortcuts": "Window Shortcuts",
|
||||
"YES": "YES",
|
||||
"Zoom In": "Zoom In",
|
||||
"Zoom Out": "Zoom Out",
|
||||
"Zulip Help": "Zulip Help",
|
||||
"keyboard shortcuts": "keyboard shortcuts",
|
||||
"Proxy bypass rules": "Proxy 白名單規則",
|
||||
"Proxy rules": "Proxy 規則",
|
||||
"Quit": "退出",
|
||||
"Quit Zulip": "退出 Zulip",
|
||||
"Redo": "重做",
|
||||
"Release Notes": "發行公告",
|
||||
"Reload": "重新載入",
|
||||
"Report an Issue": "回報問題",
|
||||
"Save": "儲存",
|
||||
"Select All": "選擇全部",
|
||||
"Settings": "設定",
|
||||
"Shortcuts": "快捷鍵",
|
||||
"Show App Logs": "顯示應用程式記錄",
|
||||
"Show app icon in system tray": "顯示應用程式圖示在系統夾",
|
||||
"Show app unread badge": "顯示應用程式未讀標記",
|
||||
"Show desktop notifications": "顯示桌面版通知",
|
||||
"Show downloaded files in file manager": "顯示下載檔案於檔案管理中",
|
||||
"Show sidebar": "顯示側邊欄",
|
||||
"Start app at login": "登入時開啟應用程式",
|
||||
"Switch to Next Organization": "切換至後一個組織",
|
||||
"Switch to Previous Organization": "切換至前一個組織",
|
||||
"These desktop app shortcuts extend the Zulip webapp's": "這些桌面版快捷鍵是從 Zulip web 版應用程式擴展而來",
|
||||
"This will delete all application data including all added accounts and preferences": "這樣將會刪除所有應用程式的資料,包含所有帳號跟其設定",
|
||||
"Tip": "提示",
|
||||
"Toggle DevTools for Active Tab": "切換 DevTools for Active Tab",
|
||||
"Toggle DevTools for Zulip App": "切換 DevTools for Zulip App",
|
||||
"Toggle Do Not Disturb": "切換勿擾模式",
|
||||
"Toggle Full Screen": "切換全螢幕",
|
||||
"Toggle Sidebar": "切換側邊欄",
|
||||
"Toggle Tray Icon": "切換夾圖示",
|
||||
"Tools": "工具",
|
||||
"Undo": "復原",
|
||||
"Upload": "上傳",
|
||||
"Use system proxy settings (requires restart)": "使用系統 proxy 設定(需要重新啟動)",
|
||||
"View": "檢視",
|
||||
"View Shortcuts": "檢視快捷鍵",
|
||||
"Window": "視窗",
|
||||
"Window Shortcuts": "視窗快捷鍵",
|
||||
"YES": "是",
|
||||
"Zoom In": "放大",
|
||||
"Zoom Out": "縮小",
|
||||
"Zulip Help": "Zulip 幫助",
|
||||
"keyboard shortcuts": "鍵盤快捷鍵",
|
||||
"script": "script",
|
||||
"Quit when the window is closed": "Quit when the window is closed",
|
||||
"Ask where to save files before downloading": "Ask where to save files before downloading",
|
||||
"Services": "Services",
|
||||
"Hide": "Hide",
|
||||
"Hide Others": "Hide Others",
|
||||
"Unhide": "Unhide",
|
||||
"AddServer": "AddServer",
|
||||
"App language (requires restart)": "App language (requires restart)",
|
||||
"Factory Reset Data": "Factory Reset Data",
|
||||
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "Reset the application, thus deleting all the connected organizations, accounts, and certificates.",
|
||||
"On macOS, the OS spellchecker is used.": "On macOS, the OS spellchecker is used.",
|
||||
"Change the language from System Preferences → Keyboard → Text → Spelling.": "Change the language from System Preferences → Keyboard → Text → Spelling.",
|
||||
"Copy Link": "Copy Link",
|
||||
"Copy Image": "Copy Image",
|
||||
"Copy Image URL": "Copy Image URL",
|
||||
"No Suggestion Found": "No Suggestion Found",
|
||||
"You can select a maximum of 3 languages for spellchecking.": "You can select a maximum of 3 languages for spellchecking.",
|
||||
"Spellchecker Languages": "Spellchecker Languages",
|
||||
"Add to Dictionary": "Add to Dictionary",
|
||||
"Look Up": "Look Up"
|
||||
"Quit when the window is closed": "當關閉視窗時退出",
|
||||
"Ask where to save files before downloading": "下載前詢問檔案儲存位置",
|
||||
"Services": "服務",
|
||||
"Hide": "隱藏",
|
||||
"Hide Others": "隱藏其他",
|
||||
"Unhide": "取消隱藏",
|
||||
"AddServer": "新增伺服器",
|
||||
"App language (requires restart)": "應用程式語言(需要重新啟動)",
|
||||
"Factory Reset Data": "設定初始化",
|
||||
"Reset the application, thus deleting all the connected organizations, accounts, and certificates.": "重置應用程式,此操作將刪除:已連結的組織、帳號、跟證書",
|
||||
"On macOS, the OS spellchecker is used.": "在 macOS 下使用作業系統的拼字檢查",
|
||||
"Change the language from System Preferences → Keyboard → Text → Spelling.": "變更語言:System Preferences → Keyboard → Text → Spelling.",
|
||||
"Copy Link": "複製網址",
|
||||
"Copy Image": "複製圖",
|
||||
"Copy Image URL": "複製圖網址",
|
||||
"No Suggestion Found": "找不到建議事項",
|
||||
"You can select a maximum of 3 languages for spellchecking.": "您最多可以選擇 3 個語言拼字檢查",
|
||||
"Spellchecker Languages": "需要拼字檢查的語言",
|
||||
"Add to Dictionary": "新增至資料夾",
|
||||
"Look Up": "查詢"
|
||||
}
|
||||
|
||||
BIN
build/icon.icns
BIN
build/icon.ico
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 106 KiB |
BIN
build/icons/1024x1024.png
Executable file → Normal file
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 737 B After Width: | Height: | Size: 581 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 797 B |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
build/zulip.png
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 13 KiB |
51
changelog.md
@@ -2,6 +2,57 @@
|
||||
|
||||
All notable changes to the Zulip desktop app are documented in this file.
|
||||
|
||||
### v5.4.1-beta --2020-07-29
|
||||
|
||||
**Fixes**:
|
||||
* Resized the large application icon on macOS dock to be coherent with other icons.
|
||||
|
||||
**Potential Fixes**:
|
||||
* macOS: Electron 9 upgrade is a potential fix for the ['grey screen issue'](https://chat.zulip.org/#narrow/stream/9-issues/topic/Grey.20Window.20on.20macOS) reported.
|
||||
|
||||
**Dependencies**:
|
||||
* Upgrade all dependencies, including Electron 9.1.1.
|
||||
|
||||
### v5.4.0 --2020-07-21
|
||||
|
||||
**New features**:
|
||||
* Added support for certificates from system store.
|
||||
* Added support for Slovak as application language.
|
||||
|
||||
**Fixes**:
|
||||
* Fix bug in *Copy Link* and add *Copy Email* option in context menu.
|
||||
* Enable *Copy* option in context menu only when copying is possible.
|
||||
* Remove leading and trailing separators in context menu on non-mac systems.
|
||||
* ignoreCerts: Accommodate WebSocket URLs in certificate-error handler.
|
||||
|
||||
**Dependencies**:
|
||||
* Upgrade all dependencies, including Electron 8.4.0.
|
||||
|
||||
**Deprecations**:
|
||||
* This release supports certificates from Zulip store as well as system store. Zulip certificate store will be deprecated in the next release.
|
||||
Users are hereby requested to move to system store. For more information, please see the [documentation](https://zulip.com/help/custom-certificates).
|
||||
|
||||
### v5.3.0 --2020-06-24
|
||||
|
||||
**Security fixes**:
|
||||
* Remove the insecure ignoreCerts option.
|
||||
|
||||
**Fixes**:
|
||||
* Windows: Turn off start at login by default.
|
||||
* Fix zoom issues where some webviews would not get zoomed-out once zoomed-in.
|
||||
* Fix overflowing contents on 'Add Organization' view.
|
||||
|
||||
**New features**:
|
||||
* Add a cancel button in the report-issue modal.
|
||||
* macOS: Use electron API to get dark tray icon instead of the green icon for the light theme.
|
||||
* Remove 'Reset App Data' option. Factory Reset option has been moved to Settings → General.
|
||||
* Support pkg installer on macOS.
|
||||
* Use electron 8 built-in spellchecker. Linux and Windows users can now choose upto three spellchecker languages from Settings → General. On macOS, default spellchecker is used.
|
||||
* Setup Transifex for better synchronization of translations. The application now supports 41 languages instead of 21.
|
||||
|
||||
**Dependencies**:
|
||||
* Upgrade all dependencies, including Electron 8.3.3.
|
||||
|
||||
### v5.2.0 --2020-05-04
|
||||
|
||||
**Security fixes**:
|
||||
|
||||
14
gulpfile.js
@@ -1,16 +1,16 @@
|
||||
'use strict';
|
||||
const gulp = require('gulp');
|
||||
const {execSync} = require('child_process');
|
||||
|
||||
const electron = require('electron-connect').server.create({
|
||||
verbose: true
|
||||
});
|
||||
const tape = require('gulp-tape');
|
||||
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 gulp = require('gulp');
|
||||
const tape = require('gulp-tape');
|
||||
const ts = require('gulp-typescript');
|
||||
const tapColorize = require('tap-colorize');
|
||||
|
||||
const tsProject = ts.createProject('tsconfig.json');
|
||||
const baseFilePattern = 'app/+(main|renderer)/**/*';
|
||||
const globOptions = {cwd: __dirname};
|
||||
const jsFiles = glob.sync(baseFilePattern + '.js', globOptions);
|
||||
|
||||
5557
package-lock.json
generated
61
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.2",
|
||||
"main": "./app/main",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
@@ -50,7 +50,7 @@
|
||||
"files": [
|
||||
"app/**/*"
|
||||
],
|
||||
"copyright": "©2019 Kandra Labs, Inc.",
|
||||
"copyright": "©2020 Kandra Labs, Inc.",
|
||||
"mac": {
|
||||
"category": "public.app-category.productivity",
|
||||
"target": [
|
||||
@@ -146,22 +146,22 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@electron-elements/send-feedback": "^2.0.3",
|
||||
"@sentry/electron": "^1.3.0",
|
||||
"@yaireo/tagify": "^3.10.2",
|
||||
"adm-zip": "^0.4.14",
|
||||
"@sentry/electron": "^1.5.0",
|
||||
"@yaireo/tagify": "^3.15.4",
|
||||
"adm-zip": "^0.4.16",
|
||||
"auto-launch": "^5.0.5",
|
||||
"backoff": "^2.5.0",
|
||||
"electron-is-dev": "^1.2.0",
|
||||
"electron-log": "^4.1.2",
|
||||
"electron-log": "^4.2.2",
|
||||
"electron-updater": "^4.3.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"escape-html": "^1.0.3",
|
||||
"fs-extra": "^9.0.0",
|
||||
"i18n": "^0.9.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"get-stream": "^5.1.0",
|
||||
"i18n": "^0.10.0",
|
||||
"iso-639-1": "^2.1.3",
|
||||
"nan": "^2.14.0",
|
||||
"node-json-db": "^1.1.0",
|
||||
"request": "^2.88.2",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
@@ -171,41 +171,50 @@
|
||||
"@types/adm-zip": "^0.4.33",
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
"@types/backoff": "^2.5.1",
|
||||
"@types/escape-html": "0.0.20",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"@types/escape-html": "^1.0.0",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/i18n": "^0.8.6",
|
||||
"@types/node": "^13.13.4",
|
||||
"@types/request": "^2.48.4",
|
||||
"@types/node": "^14.0.25",
|
||||
"@types/requestidlecallback": "^0.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.30.0",
|
||||
"@typescript-eslint/parser": "^2.30.0",
|
||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||
"@typescript-eslint/parser": "^3.7.0",
|
||||
"devtron": "^1.4.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"electron": "^8.3.3",
|
||||
"electron-builder": "^22.6.0",
|
||||
"electron": "^9.2.0",
|
||||
"electron-builder": "^22.8.0",
|
||||
"electron-connect": "^0.6.3",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"eslint-config-xo-typescript": "^0.28.0",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-tape": "^1.0.0",
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"htmlhint": "^0.11.0",
|
||||
"nodemon": "^2.0.3",
|
||||
"htmlhint": "^0.14.1",
|
||||
"nodemon": "^2.0.4",
|
||||
"pre-commit": "^1.2.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"spectron": "^10.0.1",
|
||||
"stylelint": "^13.3.3",
|
||||
"spectron": "^11.1.0",
|
||||
"stylelint": "^13.6.1",
|
||||
"tap-colorize": "^1.2.0",
|
||||
"tape": "^5.0.0",
|
||||
"typescript": "^3.8.3",
|
||||
"xo": "^0.30.0"
|
||||
"tape": "^5.0.1",
|
||||
"typescript": "^3.9.7",
|
||||
"xo": "^0.32.1"
|
||||
},
|
||||
"xo": {
|
||||
"rules": {
|
||||
"@typescript-eslint/no-dynamic-delete": "off",
|
||||
"@typescript-eslint/prefer-readonly-parameter-types": "off",
|
||||
"arrow-body-style": "error",
|
||||
"import/first": "error",
|
||||
"import/newline-after-import": "error",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"alphabetize": {
|
||||
"order": "asc"
|
||||
},
|
||||
"newlines-between": "always"
|
||||
}
|
||||
],
|
||||
"import/unambiguous": "error",
|
||||
"max-lines": [
|
||||
"warn",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
|
||||
const dotenv = require('dotenv');
|
||||
const {notarize} = require('electron-notarize');
|
||||
|
||||
dotenv.config({path: path.join(__dirname, '/../.env')});
|
||||
|
||||
const {notarize} = require('electron-notarize');
|
||||
|
||||
exports.default = async function (context) {
|
||||
const {electronPlatformName, appOutDir} = context;
|
||||
if (electronPlatformName !== 'darwin') {
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
const test = require('tape');
|
||||
|
||||
const setup = require('./setup');
|
||||
|
||||
test('app runs', async t => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
'use strict';
|
||||
const {Application} = require('spectron');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const rimraf = require('rimraf');
|
||||
const {Application} = require('spectron');
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
const test = require('tape');
|
||||
|
||||
const setup = require('./setup');
|
||||
|
||||
test('add-organization', async t => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
const test = require('tape');
|
||||
|
||||
const setup = require('./setup');
|
||||
|
||||
// Create new org link should open in the default browser [WIP]
|
||||
|
||||
9
typings.d.ts
vendored
@@ -1,3 +1,8 @@
|
||||
declare module 'electron' {
|
||||
// https://github.com/electron/typescript-definitions/issues/170
|
||||
interface IncomingMessage extends NodeJS.ReadableStream {}
|
||||
}
|
||||
|
||||
declare module '@electron-elements/send-feedback' {
|
||||
class SendFeedback extends HTMLElement {
|
||||
customStyles: string;
|
||||
@@ -9,11 +14,13 @@ declare module '@electron-elements/send-feedback' {
|
||||
buttonLabel: string;
|
||||
loaderSuccessText: string;
|
||||
logs: string[];
|
||||
useReporter: (reporter: string, data: object) => void;
|
||||
useReporter: (reporter: string, data: Record<string, unknown>) => void;
|
||||
}
|
||||
export = SendFeedback;
|
||||
}
|
||||
|
||||
declare module 'electron-connect';
|
||||
|
||||
declare module 'node-mac-notifier';
|
||||
|
||||
declare module '@yaireo/tagify';
|
||||
|
||||