Compare commits

..

2 Commits

Author SHA1 Message Date
Akash Nimare
4e21ff9d6c menu: Update the method to get current active server. 2019-06-20 22:11:52 +05:30
Akash Nimare
037cb227da menu: Open help page of current active server.
Fixes: #758.
2019-06-19 03:41:29 +05:30
110 changed files with 2947 additions and 6949 deletions

View File

@@ -2,7 +2,6 @@ root = true
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true

3
.gitignore vendored
View File

@@ -36,6 +36,3 @@ config.gypi
# tests/package.json
.python-version
# Ignore all the typescript compiled files
app/**/*.js

View File

@@ -6,7 +6,7 @@ The following is a set of guidelines for contributing to Zulip's desktop Client.
## Getting Started
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](https://jlord.us/essential-electron) great article.
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](https://jlord.dev/blog/essential-electron) great article.
## Community

View File

@@ -18,15 +18,6 @@ Please see the [installation guide](https://zulipchat.com/help/desktop-app-insta
* Multi-language spell checker
* Automatic updates
# Reporting issues
This desktop client shares most of its code with the Zulip webapp.
Issues in an individual organization's Zulip window should be reported
in the [Zulip server and webapp
project](https://github.com/zulip/zulip/issues/new). Other
issues in the desktop app and its settings should be reported [in this
project](https://github.com/zulip/zulip-desktop/issues/new).
# Contribute
First, join us on the [Zulip community server](https://zulip.readthedocs.io/en/latest/contributing/chat-zulip-org.html)!

View File

@@ -1,19 +1,18 @@
'use strict';
import { app, dialog, shell } from 'electron';
import { autoUpdater } from 'electron-updater';
import { linuxUpdateNotification } from './linuxupdater'; // Required only in case of linux
const { app, dialog, shell } = require('electron');
const { autoUpdater } = require('electron-updater');
const isDev = require('electron-is-dev');
import log = require('electron-log');
import isDev = require('electron-is-dev');
import ConfigUtil = require('../renderer/js/utils/config-util');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
export function appUpdater(updateFromMenu = false): void {
function appUpdater(updateFromMenu = false) {
// Don't initiate auto-updates in development
if (isDev) {
return;
}
if (process.platform === 'linux' && !process.env.APPIMAGE) {
const { linuxUpdateNotification } = require('./linuxupdater');
linuxUpdateNotification();
return;
}
@@ -24,6 +23,8 @@ export function appUpdater(updateFromMenu = false): void {
const LogsDir = `${app.getPath('userData')}/Logs`;
// Log whats happening
const log = require('electron-log');
log.transports.file.file = `${LogsDir}/updates.log`;
log.transports.file.level = 'info';
autoUpdater.logger = log;
@@ -83,6 +84,7 @@ Current Version: ${app.getVersion()}`
});
// Ask the user if update is available
// eslint-disable-next-line no-unused-vars
autoUpdater.on('update-downloaded', event => {
// Ask user to update the app
dialog.showMessageBox({
@@ -104,3 +106,7 @@ Current Version: ${app.getVersion()}`
// Init for updates
autoUpdater.checkForUpdates();
}
module.exports = {
appUpdater
};

View File

@@ -1,25 +1,21 @@
'use strict';
import { sentryInit } from '../renderer/js/utils/sentry-util';
import { appUpdater } from './autoupdater';
import { setAutoLaunch } from './startup';
const path = require('path');
const fs = require('fs');
import windowStateKeeper = require('electron-window-state');
import path = require('path');
import fs = require('fs');
import isDev = require('electron-is-dev');
import electron = require('electron');
const { app, ipcMain, session } = electron;
const electron = require('electron');
const windowStateKeeper = require('electron-window-state');
const isDev = require('electron-is-dev');
const appMenu = require('./menu');
const { appUpdater } = require('./autoupdater');
import AppMenu = require('./menu');
import BadgeSettings = require('../renderer/js/pages/preference/badge-settings');
import ConfigUtil = require('../renderer/js/utils/config-util');
import ProxyUtil = require('../renderer/js/utils/proxy-util');
const { setAutoLaunch } = require('./startup');
interface PatchedGlobal extends NodeJS.Global {
mainWindowState: windowStateKeeper.State;
}
const { app, ipcMain } = electron;
const globalPatched = global as PatchedGlobal;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
const ProxyUtil = require('./../renderer/js/utils/proxy-util.js');
const { sentryInit } = require('./../renderer/js/utils/sentry-util.js');
// Adds debug features like hotkeys for triggering dev tools and reload
// in development mode
@@ -28,8 +24,8 @@ if (isDev) {
}
// Prevent window being garbage collected
let mainWindow: Electron.BrowserWindow;
let badgeCount: number;
let mainWindow;
let badgeCount;
let isQuitting = false;
@@ -53,20 +49,20 @@ if (singleInstanceLock) {
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
const iconPath = (): string => {
const iconPath = () => {
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
};
function createMainWindow(): Electron.BrowserWindow {
function createMainWindow() {
// Load the previous state with fallback to defaults
const mainWindowState: windowStateKeeper.State = windowStateKeeper({
const mainWindowState = windowStateKeeper({
defaultWidth: 1100,
defaultHeight: 720,
path: `${app.getPath('userData')}/config`
});
// Let's keep the window position global so that we can access it in other process
globalPatched.mainWindowState = mainWindowState;
global.mainWindowState = mainWindowState;
const win = new electron.BrowserWindow({
// This settings needs to be saved in config
@@ -94,9 +90,6 @@ function createMainWindow(): Electron.BrowserWindow {
// Keep the app running in background on close event
win.on('close', e => {
if (ConfigUtil.getConfigItem("quitOnClose")) {
app.quit();
}
if (!isQuitting) {
e.preventDefault();
@@ -141,7 +134,7 @@ app.disableHardwareAcceleration();
app.commandLine.appendSwitch('force-color-profile', 'srgb');
// eslint-disable-next-line max-params
app.on('certificate-error', (event: Event, _webContents: Electron.WebContents, _url: string, _error: string, _certificate: any, callback) => {
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
callback(true);
});
@@ -153,7 +146,7 @@ app.on('activate', () => {
});
app.on('ready', () => {
AppMenu.setMenu({
appMenu.setMenu({
tabs: []
});
mainWindow = createMainWindow();
@@ -185,10 +178,6 @@ app.on('ready', () => {
} else {
mainWindow.show();
}
if (!ConfigUtil.isConfigItemExists('userAgent')) {
const userAgent = session.fromPartition('webview:persistsession').getUserAgent();
ConfigUtil.setConfigItem('userAgent', userAgent);
}
});
page.once('did-frame-finish-load', () => {
@@ -245,7 +234,7 @@ app.on('ready', () => {
});
ipcMain.on('clear-app-settings', () => {
globalPatched.mainWindowState.unmanage();
global.mainWindowState.unmanage(mainWindow);
app.relaunch();
app.exit();
});
@@ -262,49 +251,49 @@ app.on('ready', () => {
BadgeSettings.updateBadge(badgeCount, mainWindow);
});
ipcMain.on('toggle-menubar', (_event: Electron.IpcMessageEvent, showMenubar: boolean) => {
ipcMain.on('toggle-menubar', (event, showMenubar) => {
mainWindow.setAutoHideMenuBar(showMenubar);
mainWindow.setMenuBarVisibility(!showMenubar);
page.send('toggle-autohide-menubar', showMenubar, true);
});
ipcMain.on('update-badge', (_event: Electron.IpcMessageEvent, messageCount: number) => {
ipcMain.on('update-badge', (event, messageCount) => {
badgeCount = messageCount;
BadgeSettings.updateBadge(badgeCount, mainWindow);
page.send('tray', messageCount);
});
ipcMain.on('update-taskbar-icon', (_event: Electron.IpcMessageEvent, data: any, text: string) => {
ipcMain.on('update-taskbar-icon', (event, data, text) => {
BadgeSettings.updateTaskbarIcon(data, text, mainWindow);
});
ipcMain.on('forward-message', (_event: Electron.IpcMessageEvent, listener: any, ...params: any[]) => {
ipcMain.on('forward-message', (event, listener, ...params) => {
page.send(listener, ...params);
});
ipcMain.on('update-menu', (_event: Electron.IpcMessageEvent, props: any) => {
AppMenu.setMenu(props);
ipcMain.on('update-menu', (event, props) => {
appMenu.setMenu(props);
const activeTab = props.tabs[props.activeTabIndex];
if (activeTab) {
mainWindow.setTitle(`Zulip - ${activeTab.webview.props.name}`);
}
});
ipcMain.on('toggleAutoLauncher', (_event: Electron.IpcMessageEvent, AutoLaunchValue: boolean) => {
ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => {
setAutoLaunch(AutoLaunchValue);
});
ipcMain.on('downloadFile', (_event: Electron.IpcMessageEvent, url: string, downloadPath: string) => {
ipcMain.on('downloadFile', (event, url, downloadPath) => {
page.downloadURL(url);
page.session.once('will-download', (_event: Event, item) => {
page.session.once('will-download', (event, item) => {
const filePath = path.join(downloadPath, item.getFilename());
const getTimeStamp = (): any => {
const getTimeStamp = () => {
const date = new Date();
return date.getTime();
};
const formatFile = (filePath: string): string => {
const formatFile = filePath => {
const fileExtension = path.extname(filePath);
const baseName = path.basename(filePath, fileExtension);
return `${baseName}-${getTimeStamp()}${fileExtension}`;
@@ -318,7 +307,7 @@ app.on('ready', () => {
item.setSavePath(setFilePath);
item.on('updated', (_event: Event, state) => {
item.on('updated', (event, state) => {
switch (state) {
case 'interrupted': {
// Can interrupted to due to network error, cancel download then
@@ -338,7 +327,7 @@ app.on('ready', () => {
}
}
});
item.once('done', (_event: Event, state) => {
item.once('done', (event, state) => {
const getFileName = fs.existsSync(filePath) ? formatFile(filePath) : item.getFilename();
if (state === 'completed') {
page.send('downloadFileCompleted', item.getSavePath(), getFileName);
@@ -352,43 +341,19 @@ app.on('ready', () => {
});
});
ipcMain.on('realm-name-changed', (_event: Electron.IpcMessageEvent, serverURL: string, realmName: string) => {
ipcMain.on('realm-name-changed', (event, serverURL, realmName) => {
page.send('update-realm-name', serverURL, realmName);
});
ipcMain.on('realm-icon-changed', (_event: Electron.IpcMessageEvent, serverURL: string, iconURL: string) => {
ipcMain.on('realm-icon-changed', (event, serverURL, iconURL) => {
page.send('update-realm-icon', serverURL, iconURL);
});
// Using event.sender.send instead of page.send here to
// make sure the value of errorReporting is sent only once on load.
ipcMain.on('error-reporting', (event: Electron.IpcMessageEvent) => {
ipcMain.on('error-reporting', event => {
event.sender.send('error-reporting-val', errorReporting);
});
ipcMain.on('save-last-tab', (_event: Electron.IpcMessageEvent, index: number) => {
ConfigUtil.setConfigItem('lastActiveTab', index);
});
// Update user idle status for each realm after every 15s
const idleCheckInterval = 15 * 1000; // 15 seconds
setInterval(() => {
// Set user idle if no activity in 1 second (idleThresholdSeconds)
const idleThresholdSeconds = 1; // 1 second
// TODO: Remove typecast to any when types get added
// TODO: use powerMonitor.getSystemIdleState when upgrading electron
// powerMonitor.querySystemIdleState is deprecated in current electron
// version at the time of writing.
const powerMonitor = electron.powerMonitor as any;
powerMonitor.querySystemIdleState(idleThresholdSeconds, (idleState: string) => {
if (idleState === 'active') {
page.send('set-active');
} else {
page.send('set-idle');
}
});
}, idleCheckInterval);
});
app.on('before-quit', () => {

View File

@@ -1,18 +1,19 @@
import { app, Notification } from 'electron';
const { app } = require('electron');
const { Notification } = require('electron');
import request = require('request');
import semver = require('semver');
import ConfigUtil = require('../renderer/js/utils/config-util');
import ProxyUtil = require('../renderer/js/utils/proxy-util');
import LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
import Logger = require('../renderer/js/utils/logger-util');
const request = require('request');
const semver = require('semver');
const ConfigUtil = require('../renderer/js/utils/config-util');
const ProxyUtil = require('../renderer/js/utils/proxy-util');
const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
const Logger = require('../renderer/js/utils/logger-util');
const logger = new Logger({
file: 'linux-update-util.log',
timestamp: true
});
export function linuxUpdateNotification(): void {
function linuxUpdateNotification() {
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');
@@ -24,7 +25,7 @@ export function linuxUpdateNotification(): void {
ecdhCurve: 'auto'
};
request(options, (error: any, response: any, body: any) => {
request(options, (error, response, body) => {
if (error) {
logger.error('Linux update error.');
logger.error(error);
@@ -46,3 +47,7 @@ export function linuxUpdateNotification(): void {
}
});
}
module.exports = {
linuxUpdateNotification
};

View File

@@ -1,14 +1,15 @@
'use strict';
import { app, shell, BrowserWindow, Menu, dialog } from 'electron';
import { appUpdater } from './autoupdater';
const path = require('path');
import AdmZip = require('adm-zip');
import fs = require('fs-extra');
import path = require('path');
import DNDUtil = require('../renderer/js/utils/dnd-util');
import Logger = require('../renderer/js/utils/logger-util');
import ConfigUtil = require('../renderer/js/utils/config-util');
import t = require('../renderer/js/utils/translation-util');
const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
const fs = require('fs-extra');
const AdmZip = require('adm-zip');
const { appUpdater } = require('./autoupdater');
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/../renderer/js/utils/dnd-util.js');
const Logger = require(__dirname + '/../renderer/js/utils/logger-util.js');
const appName = app.getName();
@@ -18,21 +19,19 @@ const logger = new Logger({
});
class AppMenu {
getHistorySubmenu(enableMenu: boolean): Electron.MenuItemConstructorOptions[] {
getHistorySubmenu() {
return [{
label: t.__('Back'),
label: 'Back',
accelerator: process.platform === 'darwin' ? 'Command+Left' : 'Alt+Left',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('back');
}
}
}, {
label: t.__('Forward'),
label: 'Forward',
accelerator: process.platform === 'darwin' ? 'Command+Right' : 'Alt+Right',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('forward');
}
@@ -40,64 +39,58 @@ class AppMenu {
}];
}
getToolsSubmenu(): Electron.MenuItemConstructorOptions[] {
getToolsSubmenu() {
return [{
label: t.__(`Check for Updates`),
label: `Check for Updates`,
click() {
AppMenu.checkForUpdate();
}
},
{
label: t.__(`Release Notes`),
label: `Release Notes`,
click() {
shell.openExternal(`https://github.com/zulip/zulip-desktop/releases/tag/v${app.getVersion()}`);
}
},
{
}, {
type: 'separator'
},
{
label: t.__('Factory Reset'),
}, {
label: 'Factory Reset',
accelerator: process.platform === 'darwin' ? 'Command+Shift+D' : 'Ctrl+Shift+D',
click() {
AppMenu.resetAppSettings();
}
},
{
label: t.__('Download App Logs'),
}, {
label: 'Download App Logs',
click() {
const zip = new AdmZip();
const date = new Date();
const dateString = date.toLocaleDateString().replace(/\//g, '-');
let date = new Date();
date = date.toLocaleDateString().replace(/\//g, '-');
// Create a zip file of all the logs and config data
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/Logs`);
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/config`);
// Put the log file in downloads folder
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${dateString}.zip`;
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${date}.zip`;
zip.writeZip(logFilePath);
// Open and select the log file
shell.showItemInFolder(logFilePath);
}
},
{
}, {
type: 'separator'
},
{
label: t.__('Toggle DevTools for Zulip App'),
}, {
label: 'Toggle DevTools for Zulip App',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.openDevTools({mode: 'undocked'});
focusedWindow.webContents.toggleDevTools();
}
}
},
{
label: t.__('Toggle DevTools for Active Tab'),
}, {
label: 'Toggle DevTools for Active Tab',
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('tab-devtools');
}
@@ -105,19 +98,19 @@ class AppMenu {
}];
}
getViewSubmenu(): Electron.MenuItemConstructorOptions[] {
getViewSubmenu() {
return [{
label: t.__('Reload'),
label: 'Reload',
accelerator: 'CommandOrControl+R',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('reload-current-viewer');
}
}
}, {
label: t.__('Hard Reload'),
label: 'Hard Reload',
accelerator: 'CommandOrControl+Shift+R',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('hard-reload');
}
@@ -125,28 +118,27 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: t.__('Toggle Full Screen'),
role: 'togglefullscreen'
}, {
label: t.__('Zoom In'),
role: 'zoomin',
click(_item: any, focusedWindow: any) {
label: 'Zoom In',
accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomIn');
}
}
}, {
label: t.__('Zoom Out'),
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomOut');
}
}
}, {
label: t.__('Actual Size'),
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomActualSize');
}
@@ -154,16 +146,16 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: t.__('Toggle Tray Icon'),
click(_item: any, focusedWindow: any) {
label: 'Toggle Tray Icon',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.send('toggletray');
}
}
}, {
label: t.__('Toggle Sidebar'),
label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+Shift+S',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
focusedWindow.webContents.send('toggle-sidebar', newValue);
@@ -171,10 +163,10 @@ class AppMenu {
}
}
}, {
label: t.__('Auto hide Menu bar'),
label: 'Auto hide Menu bar',
checked: ConfigUtil.getConfigItem('autoHideMenubar', false),
visible: process.platform !== 'darwin',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('autoHideMenubar');
focusedWindow.setAutoHideMenuBar(newValue);
@@ -187,30 +179,29 @@ class AppMenu {
}];
}
getHelpSubmenu(): Electron.MenuItemConstructorOptions[] {
getHelpSubmenu() {
return [
{
label: `${appName + ' Desktop'} v${app.getVersion()}`,
enabled: false
},
{
label: t.__('About Zulip'),
click(_item: any, focusedWindow: any) {
label: 'About Zulip',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
}
},
{
label: t.__(`Help Center`),
label: `Help Center`,
click(focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-help');
}
}
},
{
label: t.__('Report an Issue'),
}, {
label: 'Report an Issue',
click() {
// the goal is to notify the main.html BrowserWindow
// which may not be the focused window.
@@ -218,16 +209,13 @@ class AppMenu {
window.webContents.send('open-feedback-modal');
});
}
}
];
}];
}
getWindowSubmenu(tabs: any[], activeTabIndex: number, enableMenu: boolean): Electron.MenuItemConstructorOptions[] {
const initialSubmenu: any[] = [{
label: t.__('Minimize'),
getWindowSubmenu(tabs, activeTabIndex) {
const initialSubmenu = [{
role: 'minimize'
}, {
label: t.__('Close'),
role: 'close'
}];
@@ -236,41 +224,41 @@ class AppMenu {
initialSubmenu.push({
type: 'separator'
});
tabs.forEach(tab => {
for (let i = 0; i < tabs.length; i++) {
// Do not add functional tab settings to list of windows in menu bar
if (tab.props.role === 'function' && tab.props.name === 'Settings') {
return;
if (tabs[i].props.role === 'function' && tabs[i].webview.props.name === 'Settings') {
continue;
}
initialSubmenu.push({
label: tab.props.name,
accelerator: tab.props.role === 'function' ? '' : `${ShortcutKey} + ${tab.props.index + 1}`,
checked: tab.props.index === activeTabIndex,
click(_item: any, focusedWindow: any) {
label: tabs[i].webview.props.name,
accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`,
checked: tabs[i].props.index === activeTabIndex,
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('switch-server-tab', tab.props.index);
AppMenu.sendAction('switch-server-tab', tabs[i].props.index);
}
},
type: 'checkbox'
});
});
}
initialSubmenu.push({
type: 'separator'
});
initialSubmenu.push({
label: t.__('Switch to Next Organization'),
label: 'Switch to Next Organization',
accelerator: `Ctrl+Tab`,
enabled: tabs.length > 1,
click(_item: any, focusedWindow: any) {
enabled: tabs[activeTabIndex].props.role === 'server',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('switch-server-tab', AppMenu.getNextServer(tabs, activeTabIndex));
}
}
}, {
label: t.__('Switch to Previous Organization'),
label: 'Switch to Previous Organization',
accelerator: `Ctrl+Shift+Tab`,
enabled: tabs.length > 1,
click(_item: any, focusedWindow: any) {
enabled: tabs[activeTabIndex].props.role === 'server',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('switch-server-tab', AppMenu.getPreviousServer(tabs, activeTabIndex));
}
@@ -281,39 +269,39 @@ class AppMenu {
return initialSubmenu;
}
getDarwinTpl(props: any): Electron.MenuItemConstructorOptions[] {
getDarwinTpl(props) {
const { tabs, activeTabIndex, enableMenu } = props;
return [{
label: `${app.getName()}`,
submenu: [{
label: t.__('Add Organization'),
label: 'Add Organization',
accelerator: 'Cmd+Shift+N',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('new-server');
}
}
}, {
label: t.__('Toggle Do Not Disturb'),
label: 'Toggle Do Not Disturb',
accelerator: 'Cmd+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, {
label: t.__('Desktop Settings'),
label: 'Desktop Settings',
accelerator: 'Cmd+,',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-settings');
}
}
}, {
label: t.__('Keyboard Shortcuts'),
label: 'Keyboard Shortcuts',
accelerator: 'Cmd+Shift+K',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
}
@@ -321,19 +309,18 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: t.__('Copy Zulip URL'),
label: 'Copy Zulip URL',
accelerator: 'Cmd+Shift+C',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('copy-zulip-url');
}
}
}, {
label: t.__('Log Out of Organization'),
label: 'Log Out of Organization',
accelerator: 'Cmd+L',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('log-out');
}
@@ -341,85 +328,67 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: t.__('Services'),
role: 'services',
submenu: []
}, {
type: 'separator'
}, {
label: t.__('Hide'),
role: 'hide'
}, {
label: t.__('Hide Others'),
role: 'hideothers'
}, {
label: t.__('Unhide'),
role: 'unhide'
}, {
type: 'separator'
}, {
label: t.__('Minimize'),
role: 'minimize'
}, {
label: t.__('Close'),
role: 'close'
}, {
label: t.__('Quit'),
role: 'quit'
}]
}, {
label: t.__('Edit'),
label: 'Edit',
submenu: [{
label: t.__('Undo'),
role: 'undo'
}, {
label: t.__('Redo'),
role: 'redo'
}, {
type: 'separator'
}, {
label: t.__('Cut'),
role: 'cut'
}, {
label: t.__('Copy'),
role: 'copy'
}, {
label: t.__('Paste'),
role: 'paste'
}, {
label: t.__('Paste and Match Style'),
role: 'pasteandmatchstyle'
}, {
label: t.__('Select All'),
role: 'selectall'
}]
}, {
label: t.__('View'),
label: 'View',
submenu: this.getViewSubmenu()
}, {
label: t.__('History'),
submenu: this.getHistorySubmenu(enableMenu)
label: 'History',
submenu: this.getHistorySubmenu()
}, {
label: t.__('Window'),
submenu: this.getWindowSubmenu(tabs, activeTabIndex, enableMenu)
label: 'Window',
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
}, {
label: t.__('Tools'),
label: 'Tools',
submenu: this.getToolsSubmenu()
}, {
label: t.__('Help'),
role: 'help',
submenu: this.getHelpSubmenu()
}];
}
getOtherTpl(props: any): Electron.MenuItemConstructorOptions[] {
getOtherTpl(props) {
const { tabs, activeTabIndex, enableMenu } = props;
return [{
label: t.__('File'),
label: '&File',
submenu: [{
label: t.__('Add Organization'),
label: 'Add Organization',
accelerator: 'Ctrl+Shift+N',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('new-server');
}
@@ -427,25 +396,25 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: t.__('Toggle Do Not Disturb'),
label: 'Toggle Do Not Disturb',
accelerator: 'Ctrl+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, {
label: t.__('Desktop Settings'),
label: 'Desktop Settings',
accelerator: 'Ctrl+,',
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-settings');
}
}
}, {
label: t.__('Keyboard Shortcuts'),
label: 'Keyboard Shortcuts',
accelerator: 'Ctrl+Shift+K',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
}
@@ -453,19 +422,18 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: t.__('Copy Zulip URL'),
label: 'Copy Zulip URL',
accelerator: 'Ctrl+Shift+C',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('copy-zulip-url');
}
}
}, {
label: t.__('Log Out of Organization'),
label: 'Log Out of Organization',
accelerator: 'Ctrl+L',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('log-out');
}
@@ -473,64 +441,50 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: t.__('Minimize'),
role: 'minimize'
}, {
label: t.__('Close'),
role: 'close'
}, {
label: t.__('Quit'),
role: 'quit',
accelerator: 'Ctrl+Q'
}]
}, {
label: t.__('Edit'),
label: '&Edit',
submenu: [{
label: t.__('Undo'),
role: 'undo'
}, {
label: t.__('Redo'),
role: 'redo'
}, {
type: 'separator'
}, {
label: t.__('Cut'),
role: 'cut'
}, {
label: t.__('Copy'),
role: 'copy'
}, {
label: t.__('Paste'),
role: 'paste'
}, {
label: t.__('Paste and Match Style'),
role: 'pasteandmatchstyle'
}, {
type: 'separator'
}, {
label: t.__('Select All'),
role: 'selectall'
}]
}, {
label: t.__('View'),
label: '&View',
submenu: this.getViewSubmenu()
}, {
label: t.__('History'),
submenu: this.getHistorySubmenu(enableMenu)
label: '&History',
submenu: this.getHistorySubmenu()
}, {
label: t.__('Window'),
submenu: this.getWindowSubmenu(tabs, activeTabIndex, enableMenu)
label: '&Window',
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
}, {
label: t.__('Tools'),
label: '&Tools',
submenu: this.getToolsSubmenu()
}, {
label: t.__('Help'),
label: '&Help',
role: 'help',
submenu: this.getHelpSubmenu()
}];
}
static sendAction(action: any, ...params: any[]): void {
static sendAction(action, ...params) {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
@@ -540,11 +494,11 @@ class AppMenu {
win.webContents.send(action, ...params);
}
static checkForUpdate(): void {
static checkForUpdate() {
appUpdater(true);
}
static getNextServer(tabs: any[], activeTabIndex: number): number {
static getNextServer(tabs, activeTabIndex) {
do {
activeTabIndex = (activeTabIndex + 1) % tabs.length;
}
@@ -552,7 +506,7 @@ class AppMenu {
return activeTabIndex;
}
static getPreviousServer(tabs: any[], activeTabIndex: number): number {
static getPreviousServer(tabs, activeTabIndex) {
do {
activeTabIndex = (activeTabIndex - 1 + tabs.length) % tabs.length;
}
@@ -560,7 +514,7 @@ class AppMenu {
return activeTabIndex;
}
static resetAppSettings(): void {
static resetAppSettings() {
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
// We save App's settings/configurations in following files
@@ -576,7 +530,7 @@ class AppMenu {
if (response === 0) {
settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, (error: any) => {
fs.access(getSettingFilesPath, error => {
if (error) {
logger.error('Error while resetting app settings.');
logger.error(error);
@@ -591,11 +545,11 @@ class AppMenu {
});
}
setMenu(props: any): void {
setMenu(props) {
const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props);
const menu = Menu.buildFromTemplate(tpl);
Menu.setApplicationMenu(menu);
}
}
export = new AppMenu();
module.exports = new AppMenu();

View File

@@ -1,11 +1,10 @@
'use strict';
import { app } from 'electron';
const { app } = require('electron');
const AutoLaunch = require('auto-launch');
const isDev = require('electron-is-dev');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
import AutoLaunch = require('auto-launch');
import isDev = require('electron-is-dev');
import ConfigUtil = require('../renderer/js/utils/config-util');
export const setAutoLaunch = (AutoLaunchValue: boolean): void => {
const setAutoLaunch = AutoLaunchValue => {
// Don't run this in development
if (isDev) {
return;
@@ -29,3 +28,7 @@ export const setAutoLaunch = (AutoLaunchValue: boolean): void => {
ZulipAutoLauncher.disable();
}
};
module.exports = {
setAutoLaunch
};

280
app/package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "zulip",
"version": "4.0.1",
"version": "3.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -136,11 +136,6 @@
"resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz",
"integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"adm-zip": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz",
@@ -166,41 +161,6 @@
"json-schema-traverse": "^0.3.0"
}
},
"ambi": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ambi/-/ambi-2.5.0.tgz",
"integrity": "sha1-fI43K+SIkRV+fOoBy2+RQ9H3QiA=",
"requires": {
"editions": "^1.1.1",
"typechecker": "^4.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
},
"typechecker": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/typechecker/-/typechecker-4.7.0.tgz",
"integrity": "sha512-4LHc1KMNJ6NDGO+dSM/yNfZQRtp8NN7psYrPHUblD62Dvkwsp3VShsbM78kOgpcmMkRTgvwdKOTjctS+uMllgQ==",
"requires": {
"editions": "^2.1.0"
},
"dependencies": {
"editions": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
"integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
"requires": {
"errlop": "^1.1.1",
"semver": "^5.6.0"
}
}
}
}
}
},
"applescript": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz",
@@ -224,11 +184,6 @@
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -305,7 +260,7 @@
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -450,11 +405,6 @@
}
}
},
"csextends": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/csextends/-/csextends-1.2.0.tgz",
"integrity": "sha512-S/8k1bDTJIwuGgQYmsRoE+8P+ohV32WhQ0l4zqrc0XDdxOhjQQD7/wTZwCzoZX53jSX3V/qwjT+OkPTxWQcmjg=="
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -514,24 +464,11 @@
"dns-packet": "^1.1.0"
}
},
"dotenv": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.0.0.tgz",
"integrity": "sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg=="
},
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
},
"eachr": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/eachr/-/eachr-2.0.4.tgz",
"integrity": "sha1-Rm98qhBwj2EFCeMsgHqv5X/BIr8=",
"requires": {
"typechecker": "^2.0.8"
}
},
"ecc-jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
@@ -541,11 +478,6 @@
"jsbn": "~0.1.0"
}
},
"editions": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz",
"integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg=="
},
"electron-fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.1.0.tgz",
@@ -636,30 +568,6 @@
"iconv-lite": "~0.4.13"
}
},
"errlop": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/errlop/-/errlop-1.1.1.tgz",
"integrity": "sha512-WX7QjiPHhsny7/PQvrhS5VMizXXKoKCS3udaBp8gjlARdbn+XmK300eKBAAN0hGyRaTCtRpOaxK+xFVPUJ3zkw==",
"requires": {
"editions": "^2.1.2"
},
"dependencies": {
"editions": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
"integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
"requires": {
"errlop": "^1.1.1",
"semver": "^5.6.0"
}
},
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
}
}
},
"es-abstract": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
@@ -708,36 +616,6 @@
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
},
"extendr": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/extendr/-/extendr-2.1.0.tgz",
"integrity": "sha1-MBqgu+pWX00tyPVw8qImEahSe1Y=",
"requires": {
"typechecker": "~2.0.1"
},
"dependencies": {
"typechecker": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.0.8.tgz",
"integrity": "sha1-6D2oS7ZMWEzLNFg4V2xAsDN9uC4="
}
}
},
"extract-opts": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-2.2.0.tgz",
"integrity": "sha1-H6KOunNSxttID4hc63GkaBC+bX0=",
"requires": {
"typechecker": "~2.0.1"
},
"dependencies": {
"typechecker": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.0.8.tgz",
"integrity": "sha1-6D2oS7ZMWEzLNFg4V2xAsDN9uC4="
}
}
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
@@ -897,7 +775,7 @@
"hashids": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/hashids/-/hashids-1.1.4.tgz",
"integrity": "sha512-U/fnTE3edW0AV92ZI/BfEluMZuVcu3MDOopsN7jS+HqDYcarQo8rXQiWlsBlm0uX48/taYSdxRsfzh2HRg5Z6w=="
"integrity": "sha1-5P+SrWa2hKO9aqznwX1mYY7l+iE="
},
"hawk": {
"version": "6.0.2",
@@ -930,19 +808,6 @@
"sshpk": "^1.7.0"
}
},
"i18n": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/i18n/-/i18n-0.8.3.tgz",
"integrity": "sha1-LYzxwkciYCwgQdAbpq5eqlE4jw4=",
"requires": {
"debug": "*",
"make-plural": "^3.0.3",
"math-interval-parser": "^1.1.0",
"messageformat": "^0.3.1",
"mustache": "*",
"sprintf-js": ">=1.0.3"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -951,20 +816,6 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ignorefs": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/ignorefs/-/ignorefs-1.2.0.tgz",
"integrity": "sha1-2ln7hYl25KXkNwLM0fKC/byeV1Y=",
"requires": {
"editions": "^1.3.3",
"ignorepatterns": "^1.1.0"
}
},
"ignorepatterns": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.1.0.tgz",
"integrity": "sha1-rI9DbyI5td+2bV8NOpBKh6xnzF4="
},
"indent-string": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
@@ -1212,30 +1063,6 @@
"resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz",
"integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks="
},
"make-plural": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-3.0.6.tgz",
"integrity": "sha1-IDOgO6wpC487uRJY9lud9+iwHKc=",
"requires": {
"minimist": "^1.2.0"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"optional": true
}
}
},
"math-interval-parser": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-1.1.0.tgz",
"integrity": "sha1-2+2lsGsySZc8bfYXD94jhvCv2JM=",
"requires": {
"xregexp": "^2.0.0"
}
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
@@ -1246,32 +1073,6 @@
"is-buffer": "~1.1.1"
}
},
"messageformat": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/messageformat/-/messageformat-0.3.1.tgz",
"integrity": "sha1-5Y//gkXps5cXmeW0PbWLPpQX9aI=",
"requires": {
"async": "~1.5.2",
"glob": "~6.0.4",
"make-plural": "~3.0.3",
"nopt": "~3.0.6",
"watchr": "~2.4.13"
},
"dependencies": {
"glob": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
"requires": {
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
}
},
"mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
@@ -1293,7 +1094,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -1316,20 +1117,15 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mustache": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-3.0.1.tgz",
"integrity": "sha512-jFI/4UVRsRYdUbuDTKT7KzfOp7FiD5WzYmmwNwXyUVypC0xjoTL78Fqc0jHUPIvvGD+6DQSPHIt1NE7D1ArsqA=="
},
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha1-ltDNYQ69WNS03pzAxoKM2pnHVI8="
},
"node-json-db": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-0.9.2.tgz",
"integrity": "sha512-vd8A6wznm3hi82IKlVKdomZvsEnASYWl0n4iwLkI7ukbIQkXTtFdJG8aZg0BNbEN+Vr/VktDFSqwWx/iSdf31Q==",
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-0.9.1.tgz",
"integrity": "sha512-4BydUI7a10W8QBdHq/J3UBswU1i8WhCgTS4BZU0MjlUKrSU7cuUti71eojistgqe5hIrb4adj/wvAT5dw63NPg==",
"requires": {
"mkdirp": "0.5.x"
}
@@ -1345,14 +1141,6 @@
"uuid": "^3.3.2"
}
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"requires": {
"abbrev": "1"
}
},
"normalize-url": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz",
@@ -1661,14 +1449,6 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safefs": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/safefs/-/safefs-3.2.2.tgz",
"integrity": "sha1-gXDBRE1wOOCMrqBaN0+uL6NJ4Vw=",
"requires": {
"graceful-fs": "*"
}
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -1679,16 +1459,6 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"scandirectory": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/scandirectory/-/scandirectory-2.5.0.tgz",
"integrity": "sha1-bOA/VKCQtmjjy+2/IO354xBZPnI=",
"requires": {
"ignorefs": "^1.0.0",
"safefs": "^3.1.2",
"taskgroup": "^4.0.5"
}
},
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
@@ -1727,7 +1497,7 @@
"spawn-rx": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz",
"integrity": "sha512-gOPXiQQFQ9lTOLuys0iMn3jfxxv9c7zzwhbYLOEbQGvEShHVJ5sSR1oD3Daj88os7jKArDYT7rbOKdvNhe7iEg==",
"integrity": "sha1-tihSlEmUJgib7qDDwewy1/xXo3Y=",
"requires": {
"debug": "^2.5.1",
"lodash.assign": "^4.2.0",
@@ -1782,15 +1552,6 @@
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
"integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ="
},
"taskgroup": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/taskgroup/-/taskgroup-4.3.1.tgz",
"integrity": "sha1-feGT/r12gnPEV3MElwJNUSwnkVo=",
"requires": {
"ambi": "^2.2.0",
"csextends": "^1.0.3"
}
},
"timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
@@ -1823,11 +1584,6 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"optional": true
},
"typechecker": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.1.0.tgz",
"integrity": "sha1-0cIJOlT/ihn1jP+HfuqlTyJC04M="
},
"underscore": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz",
@@ -1891,21 +1647,6 @@
"extsprintf": "^1.2.0"
}
},
"watchr": {
"version": "2.4.13",
"resolved": "https://registry.npmjs.org/watchr/-/watchr-2.4.13.tgz",
"integrity": "sha1-10hHu01vkPYf4sdPn2hmKqDgdgE=",
"requires": {
"eachr": "^2.0.2",
"extendr": "^2.1.0",
"extract-opts": "^2.2.0",
"ignorefs": "^1.0.0",
"safefs": "^3.1.2",
"scandirectory": "^2.5.0",
"taskgroup": "^4.2.0",
"typechecker": "^2.0.8"
}
},
"winreg": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz",
@@ -1926,11 +1667,6 @@
"resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
"integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw="
},
"xregexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",

View File

@@ -1,8 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "4.0.2-beta",
"desktopName": "zulip.desktop",
"version": "3.0.0",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -31,16 +30,14 @@
"@sentry/electron": "0.14.0",
"adm-zip": "0.4.11",
"auto-launch": "5.0.5",
"backoff": "2.5.0",
"dotenv": "8.0.0",
"electron-is-dev": "0.3.0",
"electron-log": "2.2.14",
"electron-spellchecker": "1.1.2",
"electron-updater": "4.0.6",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"i18n": "0.8.3",
"node-json-db": "0.9.2",
"is-online": "7.0.0",
"node-json-db": "0.9.1",
"request": "2.85.0",
"semver": "5.4.1",
"wurl": "2.5.0"

View File

@@ -17,43 +17,27 @@ body {
}
#title {
text-align: left;
font-size: 24px;
font-weight: bold;
margin: 20px 0;
}
#subtitle {
font-size: 20px;
text-align: left;
margin: 12px 0;
}
#description {
text-align: left;
font-size: 16px;
list-style-position: inside;
}
#reconnect {
float: left;
}
#settings {
margin-left: 116px;
}
.button {
font-size: 16px;
background: rgba(0, 150, 136, 1.000);
color: rgba(255, 255, 255, 1.000);
width: 96px;
width: 84px;
height: 32px;
border-radius: 5px;
line-height: 32px;
margin: 20px auto 0;
cursor: pointer;
}
.button:hover {
#reconnect:hover {
opacity: 0.8;
}

View File

@@ -399,17 +399,8 @@ i.open-tab-button {
cursor: pointer;
}
#css-delete-action {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
/*
.action class will add extra margin to right which
we don't want for a button; the extra margin is intended for radio buttons
*/
margin-right: 0px;
.css-delete-action {
margin-bottom: 10px;
}
.selected-css-path,
@@ -462,11 +453,6 @@ i.open-tab-button {
user-select: none;
}
.disallowed:hover {
background-color: rgba(241, 241, 241, 1.000);
cursor: not-allowed;
}
input.toggle-round + label {
padding: 2px;
width: 50px;
@@ -642,26 +628,6 @@ input.toggle-round:checked + label::after {
cursor: pointer;
}
.server-network-option {
font-weight: bold;
font-size: 1.1rem;
margin-top: 10px;
padding-top: 15px;
align-items: center;
text-align: center;
color: rgb(78, 191, 172);
width: 98%;
height: 46px;
cursor: pointer;
}
i.open-network-button {
font-size: 16px;
cursor: pointer;
padding-left: 5px;
position: absolute;
}
/* responsive grid */
@media (max-width: 650px) {

View File

@@ -1,11 +1,11 @@
'use strict';
class BaseComponent {
generateNodeFromTemplate(template: string): Element | null {
generateNodeFromTemplate(template) {
const wrapper = document.createElement('div');
wrapper.innerHTML = template;
return wrapper.firstElementChild;
}
}
export = BaseComponent;
module.exports = BaseComponent;

View File

@@ -1,10 +1,9 @@
'use strict';
import Tab = require('./tab');
const Tab = require(__dirname + '/../components/tab.js');
class FunctionalTab extends Tab {
$closeButton: Element;
template(): string {
template() {
return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tab-badge close-button">
<i class="material-icons">close</i>
@@ -15,22 +14,16 @@ class FunctionalTab extends Tab {
</div>`;
}
// TODO: Typescript - This type for props should be TabProps
constructor(props: any) {
super(props);
this.init();
}
init(): void {
init() {
this.$el = this.generateNodeFromTemplate(this.template());
if (this.props.name !== 'Settings') {
this.props.$root.append(this.$el);
this.$closeButton = this.$el.querySelectorAll('.server-tab-badge')[0];
this.props.$root.appendChild(this.$el);
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
this.registerListeners();
}
}
registerListeners(): void {
registerListeners() {
super.registerListeners();
this.$el.addEventListener('mouseover', () => {
@@ -41,11 +34,11 @@ class FunctionalTab extends Tab {
this.$closeButton.classList.remove('active');
});
this.$closeButton.addEventListener('click', (e: Event) => {
this.$closeButton.addEventListener('click', e => {
this.props.onDestroy();
e.stopPropagation();
});
}
}
export = FunctionalTab;
module.exports = FunctionalTab;

View File

@@ -1,15 +1,12 @@
import { ipcRenderer, remote } from 'electron';
import LinkUtil = require('../utils/link-util');
import DomainUtil = require('../utils/domain-util');
import ConfigUtil = require('../utils/config-util');
const { shell, app } = remote;
const { ipcRenderer } = require('electron');
const { shell, app } = require('electron').remote;
const LinkUtil = require('../utils/link-util');
const DomainUtil = require('../utils/domain-util');
const ConfigUtil = require('../utils/config-util');
const dingSound = new Audio('../resources/sounds/ding.ogg');
// TODO: TypeScript - Figure out a way to pass correct type here.
function handleExternalLink(this: any, event: any): void {
function handleExternalLink(event) {
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
const downloadPath = ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`);
@@ -40,7 +37,7 @@ function handleExternalLink(this: any, event: any): void {
// if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
if (!LinkUtil.isImage(url) && isUploadsURL) {
ipcRenderer.send('downloadFile', url, downloadPath);
ipcRenderer.once('downloadFileCompleted', (_event: Event, filePath: string, fileName: string) => {
ipcRenderer.once('downloadFileCompleted', (event, filePath, fileName) => {
const downloadNotification = new Notification('Download Complete', {
body: shouldShowInFolder ? `Click to show ${fileName} in folder` : `Click to open ${fileName}`,
silent: true // We'll play our own sound - ding.ogg
@@ -51,7 +48,7 @@ function handleExternalLink(this: any, event: any): void {
dingSound.play();
}
downloadNotification.addEventListener('click', () => {
downloadNotification.onclick = () => {
if (shouldShowInFolder) {
// Reveal file in download folder
shell.showItemInFolder(filePath);
@@ -59,7 +56,7 @@ function handleExternalLink(this: any, event: any): void {
// Open file in the default native app
shell.openItem(filePath);
}
});
};
ipcRenderer.removeAllListeners('downloadFileFailed');
});
@@ -80,4 +77,4 @@ function handleExternalLink(this: any, event: any): void {
}
}
export = handleExternalLink;
module.exports = handleExternalLink;

View File

@@ -1,14 +1,12 @@
'use strict';
import { ipcRenderer } from 'electron';
const Tab = require(__dirname + '/../components/tab.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
import Tab = require('./tab');
import SystemUtil = require('../utils/system-util');
const {ipcRenderer} = require('electron');
class ServerTab extends Tab {
$badge: Element;
template(): string {
template() {
return `<div class="tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tooltip" style="display:none">${this.props.name}</div>
<div class="server-tab-badge"></div>
@@ -19,22 +17,16 @@ class ServerTab extends Tab {
</div>`;
}
// TODO: Typescript - This type for props should be TabProps
constructor(props: any) {
super(props);
this.init();
init() {
super.init();
this.$badge = this.$el.getElementsByClassName('server-tab-badge')[0];
}
init(): void {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.append(this.$el);
this.registerListeners();
this.$badge = this.$el.querySelectorAll('.server-tab-badge')[0];
}
updateBadge(count: number): void {
updateBadge(count) {
if (count > 0) {
const formattedCount = count > 999 ? '1K+' : count.toString();
const formattedCount = count > 999 ? '1K+' : count;
this.$badge.innerHTML = formattedCount;
this.$badge.classList.add('active');
} else {
@@ -42,7 +34,7 @@ class ServerTab extends Tab {
}
}
generateShortcutText(): string {
generateShortcutText() {
// Only provide shortcuts for server [0..10]
if (this.props.index >= 10) {
return '';
@@ -65,4 +57,4 @@ class ServerTab extends Tab {
}
}
export = ServerTab;
module.exports = ServerTab;

View File

@@ -1,48 +1,48 @@
'use strict';
import WebView = require('./webview');
import BaseComponent = require('./base');
// TODO: TypeScript - Type annotate props
interface TabProps {
[key: string]: any;
}
const BaseComponent = require(__dirname + '/../components/base.js');
class Tab extends BaseComponent {
props: TabProps;
webview: WebView;
$el: Element;
constructor(props: TabProps) {
constructor(props) {
super();
this.props = props;
this.webview = this.props.webview;
this.init();
}
registerListeners(): void {
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.registerListeners();
}
registerListeners() {
this.$el.addEventListener('click', this.props.onClick);
this.$el.addEventListener('mouseover', this.props.onHover);
this.$el.addEventListener('mouseout', this.props.onHoverOut);
}
showNetworkError(): void {
this.webview.forceLoad();
isLoading() {
return this.webview.isLoading;
}
activate(): void {
activate() {
this.$el.classList.add('active');
this.webview.load();
}
deactivate(): void {
deactivate() {
this.$el.classList.remove('active');
this.webview.hide();
}
destroy(): void {
destroy() {
this.$el.parentNode.removeChild(this.$el);
this.webview.$el.parentNode.removeChild(this.webview.$el);
}
}
export = Tab;
module.exports = Tab;

View File

@@ -1,39 +1,22 @@
'use strict';
import { remote } from 'electron';
import path = require('path');
import fs = require('fs');
import ConfigUtil = require('../utils/config-util');
import SystemUtil = require('../utils/system-util');
import BaseComponent = require('../components/base');
import handleExternalLink = require('../components/handle-external-link');
const path = require('path');
const fs = require('fs');
const { app, dialog } = remote;
const ConfigUtil = require(__dirname + '/../utils/config-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const { app, dialog } = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js');
const handleExternalLink = require(__dirname + '/../components/handle-external-link.js');
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
// TODO: TypeScript - Type annotate WebViewProps.
interface WebViewProps {
[key: string]: any;
}
class WebView extends BaseComponent {
props: any;
zoomFactor: number;
badgeCount: number;
loading: boolean;
customCSS: string;
$webviewsContainer: DOMTokenList;
$el: Electron.WebviewTag;
// This is required because in main.js we access WebView.method as
// webview[method].
[key: string]: any;
constructor(props: WebViewProps) {
constructor(props) {
super();
this.props = props;
this.zoomFactor = 1.0;
this.loading = true;
this.badgeCount = 0;
@@ -41,7 +24,7 @@ class WebView extends BaseComponent {
this.$webviewsContainer = document.querySelector('#webviews-container').classList;
}
template(): string {
template() {
return `<webview
class="disabled"
data-tab-id="${this.props.tabIndex}"
@@ -55,14 +38,14 @@ class WebView extends BaseComponent {
</webview>`;
}
init(): void {
this.$el = this.generateNodeFromTemplate(this.template()) as Electron.WebviewTag;
this.props.$root.append(this.$el);
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.registerListeners();
}
registerListeners(): void {
registerListeners() {
this.$el.addEventListener('new-window', event => {
handleExternalLink.call(this, event);
});
@@ -122,12 +105,10 @@ class WebView extends BaseComponent {
this.$el.addEventListener('did-fail-load', event => {
const { errorDescription } = event;
const hasConnectivityErr = SystemUtil.connectivityERR.includes(errorDescription);
const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0);
if (hasConnectivityErr) {
console.error('error', errorDescription);
if (!this.props.url.includes('network.html')) {
this.props.onNetworkError(this.props.index);
}
this.props.onNetworkError();
}
});
@@ -149,16 +130,12 @@ class WebView extends BaseComponent {
});
}
getBadgeCount(title: string): number {
const messageCountInTitle = (/\((\d+)\)/).exec(title);
getBadgeCount(title) {
const messageCountInTitle = (/\(([0-9]+)\)/).exec(title);
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
}
showNotificationSettings(): void {
this.$el.executeJavaScript('showNotificationSettings()');
}
show(): void {
show() {
// Do not show WebView if another tab was selected and this tab should be in background.
if (!this.props.isActive()) {
return;
@@ -199,7 +176,7 @@ class WebView extends BaseComponent {
}
}
focus(): void {
focus() {
// focus Webview and it's contents when Window regain focus.
const webContents = this.$el.getWebContents();
// HACK: webContents.isFocused() seems to be true even without the element
@@ -208,18 +185,18 @@ class WebView extends BaseComponent {
// HACK: Looks like blur needs to be called on the previously focused
// element to transfer focus correctly, in Electron v3.0.10
// See https://github.com/electron/electron/issues/15718
(document.activeElement as HTMLElement).blur();
document.activeElement.blur();
this.$el.focus();
webContents.focus();
}
}
hide(): void {
hide() {
this.$el.classList.add('disabled');
this.$el.classList.remove('active');
}
load(): void {
load() {
if (this.$el) {
this.show();
} else {
@@ -227,41 +204,41 @@ class WebView extends BaseComponent {
}
}
zoomIn(): void {
zoomIn() {
this.zoomFactor += 0.1;
this.$el.setZoomFactor(this.zoomFactor);
}
zoomOut(): void {
zoomOut() {
this.zoomFactor -= 0.1;
this.$el.setZoomFactor(this.zoomFactor);
}
zoomActualSize(): void {
zoomActualSize() {
this.zoomFactor = 1.0;
this.$el.setZoomFactor(this.zoomFactor);
}
logOut(): void {
logOut() {
this.$el.executeJavaScript('logout()');
}
showShortcut(): void {
showShortcut() {
this.$el.executeJavaScript('shortcut()');
}
openDevTools(): void {
openDevTools() {
this.$el.openDevTools();
}
back(): void {
back() {
if (this.$el.canGoBack()) {
this.$el.goBack();
this.focus();
}
}
canGoBackButton(): void {
canGoBackButton() {
const $backButton = document.querySelector('#actions-container #back-action');
if (this.$el.canGoBack()) {
$backButton.classList.remove('disable');
@@ -270,28 +247,23 @@ class WebView extends BaseComponent {
}
}
forward(): void {
forward() {
if (this.$el.canGoForward()) {
this.$el.goForward();
}
}
reload(): void {
reload() {
this.hide();
// Shows the loading indicator till the webview is reloaded
this.$webviewsContainer.remove('loaded');
this.loading = true;
this.props.switchLoading(true, this.props.url);
this.$el.reload();
}
forceLoad(): void {
this.init();
}
send(channel: string, ...param: any[]): void {
this.$el.send(channel, ...param);
send(...param) {
this.$el.send(...param);
}
}
export = WebView;
module.exports = WebView;

View File

@@ -1,34 +1,17 @@
import { ipcRenderer } from 'electron';
import events = require('events');
type ListenerType = ((...args: any[]) => void);
const events = require('events');
const { ipcRenderer } = require('electron');
// we have and will have some non camelcase stuff
// while working with zulip so just turning the rule off
// for the whole file.
/* eslint-disable @typescript-eslint/camelcase */
/* eslint-disable camelcase */
class ElectronBridge extends events {
send_notification_reply_message_supported: boolean;
idle_on_system: boolean;
last_active_on_system: number;
constructor() {
super();
this.send_notification_reply_message_supported = false;
// Indicates if the user is idle or not
this.idle_on_system = false;
// Indicates the time at which user was last active
this.last_active_on_system = Date.now();
send_event(...args) {
this.emit(...args);
}
send_event(eventName: string | symbol, ...args: any[]): void {
this.emit(eventName, ...args);
}
on_event(eventName: string, listener: ListenerType): void {
this.on(eventName, listener);
on_event(...args) {
this.on(...args);
}
}
@@ -54,6 +37,4 @@ electron_bridge.on('realm_icon_url', iconURL => {
// functions zulip side will emit event using ElectronBrigde.send_event
// which is alias of .emit and on this side we can handle the data by adding
// a listener for the event.
export = electron_bridge;
/* eslint-enable @typescript-eslint/camelcase */
module.exports = electron_bridge;

View File

@@ -1,16 +1,7 @@
import { remote } from 'electron';
import SendFeedback from '@electron-elements/send-feedback';
import path = require('path');
import fs = require('fs');
const { app } = remote;
interface SendFeedback extends HTMLElement {
[key: string]: any;
}
type SendFeedbackType = SendFeedback;
const { app } = require('electron').remote;
const path = require('path');
const fs = require('fs');
const SendFeedback = require('@electron-elements/send-feedback');
// make the button color match zulip app's theme
SendFeedback.customStyles = `
@@ -31,8 +22,8 @@ button {
`;
customElements.define('send-feedback', SendFeedback);
export const sendFeedback: SendFeedbackType = document.querySelector('send-feedback');
export const feedbackHolder = sendFeedback.parentElement;
const sendFeedback = document.querySelector('send-feedback');
const feedbackHolder = sendFeedback.parentElement;
// customize the fields of custom elements
sendFeedback.title = 'Report Issue';
@@ -47,7 +38,7 @@ sendFeedback.useReporter('emailReporter', {
email: 'akash@zulipchat.com'
});
feedbackHolder.addEventListener('click', (e: Event) => {
feedbackHolder.addEventListener('click', e => {
// only remove the class if the grey out faded
// part is clicked and not the feedback element itself
if (e.target === e.currentTarget) {
@@ -64,3 +55,8 @@ sendFeedback.addEventListener('feedback-submitted', () => {
const dataDir = app.getPath('userData');
const logsDir = path.join(dataDir, '/Logs');
sendFeedback.logs.push(...fs.readdirSync(logsDir).map(file => path.join(logsDir, file)));
module.exports = {
feedbackHolder,
sendFeedback
};

View File

@@ -1,156 +1,74 @@
'use strict';
import { ipcRenderer, remote, clipboard, shell } from 'electron';
import { feedbackHolder } from './feedback';
const { ipcRenderer, remote, clipboard, shell } = require('electron');
const isDev = require('electron-is-dev');
import path = require('path');
import escape = require('escape-html');
import isDev = require('electron-is-dev');
const { session, app, Menu, dialog } = remote;
const escape = require('escape-html');
// eslint-disable-next-line import/no-unassigned-import
require('./tray');
require(__dirname + '/js/tray.js');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
const WebView = require(__dirname + '/js/components/webview.js');
const ServerTab = require(__dirname + '/js/components/server-tab.js');
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/js/utils/dnd-util.js');
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const Logger = require(__dirname + '/js/utils/logger-util.js');
const CommonUtil = require(__dirname + '/js/utils/common-util.js');
import DomainUtil = require('./utils/domain-util');
import WebView = require('./components/webview');
import ServerTab = require('./components/server-tab');
import FunctionalTab = require('./components/functional-tab');
import ConfigUtil = require('./utils/config-util');
import DNDUtil = require('./utils/dnd-util');
import ReconnectUtil = require('./utils/reconnect-util');
import Logger = require('./utils/logger-util');
import CommonUtil = require('./utils/common-util');
import EnterpriseUtil = require('./utils/enterprise-util');
import Messages = require('./../../resources/messages');
interface FunctionalTabProps {
name: string;
materialIcon: string;
url: string;
}
interface AnyObject {
[key: string]: any;
}
interface SettingsOptions {
autoHideMenubar: boolean;
trayIcon: boolean;
useManualProxy: boolean;
useSystemProxy: boolean;
showSidebar: boolean;
badgeOption: boolean;
startAtLogin: boolean;
startMinimized: boolean;
enableSpellchecker: boolean;
showNotification: boolean;
autoUpdate: boolean;
betaUpdate: boolean;
errorReporting: boolean;
customCSS: boolean;
silent: boolean;
lastActiveTab: number;
dnd: boolean;
dndPreviousSettings: {
showNotification: boolean;
silent: boolean;
flashTaskbarOnMessage?: boolean;
};
downloadsPath: string;
showDownloadFolder: boolean;
quitOnClose: boolean;
flashTaskbarOnMessage?: boolean;
dockBouncing?: boolean;
loading?: AnyObject;
}
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
const rendererDirectory = path.resolve(__dirname, '..');
type ServerOrFunctionalTab = ServerTab | FunctionalTab;
class ServerManagerView {
$addServerButton: HTMLButtonElement;
$tabsContainer: Element;
$reloadButton: HTMLButtonElement;
$loadingIndicator: HTMLButtonElement;
$settingsButton: HTMLButtonElement;
$webviewsContainer: Element;
$backButton: HTMLButtonElement;
$dndButton: HTMLButtonElement;
$addServerTooltip: HTMLElement;
$reloadTooltip: HTMLElement;
$loadingTooltip: HTMLElement;
$settingsTooltip: HTMLElement;
$serverIconTooltip: HTMLCollectionOf<HTMLElement>;
$backTooltip: HTMLElement;
$dndTooltip: HTMLElement;
$sidebar: Element;
$fullscreenPopup: Element;
$fullscreenEscapeKey: string;
loading: AnyObject;
activeTabIndex: number;
tabs: ServerOrFunctionalTab[];
functionalTabs: AnyObject;
tabIndex: number;
presetOrgs: string[];
constructor() {
this.$addServerButton = document.querySelector('#add-tab');
this.$tabsContainer = document.querySelector('#tabs-container');
this.$addServerButton = document.getElementById('add-tab');
this.$tabsContainer = document.getElementById('tabs-container');
const $actionsContainer = document.querySelector('#actions-container');
const $actionsContainer = document.getElementById('actions-container');
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
this.$loadingIndicator = $actionsContainer.querySelector('#loading-action');
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$webviewsContainer = document.querySelector('#webviews-container');
this.$webviewsContainer = document.getElementById('webviews-container');
this.$backButton = $actionsContainer.querySelector('#back-action');
this.$dndButton = $actionsContainer.querySelector('#dnd-action');
this.$addServerTooltip = document.querySelector('#add-server-tooltip');
this.$addServerTooltip = document.getElementById('add-server-tooltip');
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
this.$loadingTooltip = $actionsContainer.querySelector('#loading-tooltip');
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
// TODO: This should have been querySelector but the problem is that
// querySelector doesn't return elements not present in dom whereas somehow
// getElementsByClassName does. To fix this we need to call this after this.initTabs
// is called in this.init.
// eslint-disable-next-line unicorn/prefer-query-selector
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip') as HTMLCollectionOf<HTMLElement>;
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip');
this.$backTooltip = $actionsContainer.querySelector('#back-tooltip');
this.$dndTooltip = $actionsContainer.querySelector('#dnd-tooltip');
this.$sidebar = document.querySelector('#sidebar');
this.$sidebar = document.getElementById('sidebar');
this.$fullscreenPopup = document.querySelector('#fullscreen-popup');
this.$fullscreenPopup = document.getElementById('fullscreen-popup');
this.$fullscreenEscapeKey = process.platform === 'darwin' ? '^⌘F' : 'F11';
this.$fullscreenPopup.innerHTML = `Press ${this.$fullscreenEscapeKey} to exit full screen`;
this.loading = {};
this.activeTabIndex = -1;
this.tabs = [];
this.presetOrgs = [];
this.functionalTabs = {};
this.tabIndex = 0;
}
init(): void {
init() {
this.loadProxy().then(() => {
this.initDefaultSettings();
this.initSidebar();
if (EnterpriseUtil.configFile) {
this.initPresetOrgs();
}
this.initTabs();
this.initActions();
this.registerIpcs();
this.initDefaultSettings();
});
}
loadProxy(): Promise<boolean> {
loadProxy() {
return new Promise(resolve => {
// To change proxyEnable to useManualProxy in older versions
const proxyEnabledOld = ConfigUtil.isConfigItemExists('useProxy');
@@ -182,9 +100,9 @@ class ServerManagerView {
// Settings are initialized only when user clicks on General/Server/Network section settings
// In case, user doesn't visit these section, those values set to be null automatically
// This will make sure the default settings are correctly set to either true or false
initDefaultSettings(): void {
initDefaultSettings() {
// Default settings which should be respected
const settingOptions: SettingsOptions = {
const settingOptions = {
autoHideMenubar: false,
trayIcon: true,
useManualProxy: false,
@@ -207,8 +125,7 @@ class ServerManagerView {
silent: false
},
downloadsPath: `${app.getPath('downloads')}`,
showDownloadFolder: false,
quitOnClose: false
showDownloadFolder: false
};
// Platform specific settings
@@ -229,120 +146,35 @@ class ServerManagerView {
}
for (const i in settingOptions) {
const setting = i as keyof SettingsOptions;
// give preference to defaults defined in global_config.json
if (EnterpriseUtil.configItemExists(setting)) {
ConfigUtil.setConfigItem(setting, EnterpriseUtil.getConfigItem(setting), true);
} else if (ConfigUtil.getConfigItem(setting) === null) {
ConfigUtil.setConfigItem(setting, settingOptions[setting]);
if (ConfigUtil.getConfigItem(i) === null) {
ConfigUtil.setConfigItem(i, settingOptions[i]);
}
}
}
initSidebar(): void {
initSidebar() {
const showSidebar = ConfigUtil.getConfigItem('showSidebar', true);
this.toggleSidebar(showSidebar);
}
async queueDomain(domain: any): Promise<boolean> {
// allows us to start adding multiple domains to the app simultaneously
// promise of addition resolves in both cases, but we consider it rejected
// if the resolved value is false
try {
const serverConf = await DomainUtil.checkDomain(domain);
await DomainUtil.addDomain(serverConf);
return true;
} catch (err) {
logger.error(err);
logger.error('Could not add ' + domain + '. Please contact your system administrator.');
return false;
}
}
async initPresetOrgs(): Promise<void> {
// read preset organizations from global_config.json and queues them
// for addition to the app's domains
const preAddedDomains = DomainUtil.getDomains();
this.presetOrgs = EnterpriseUtil.getConfigItem('presetOrganizations', []);
// set to true if at least one new domain is added
const domainPromises = [];
for (const url of this.presetOrgs) {
if (DomainUtil.duplicateDomain(url)) {
continue;
}
domainPromises.push(this.queueDomain(url));
}
const domainsAdded = await Promise.all(domainPromises);
if (domainsAdded.includes(true)) {
// at least one domain was resolved
if (preAddedDomains.length > 0) {
// user already has servers added
// ask them before reloading the app
dialog.showMessageBox({
type: 'question',
buttons: ['Yes', 'Later'],
defaultId: 0,
message: 'New server' + (domainsAdded.length > 1 ? 's' : '') + ' added. Reload app now?'
}, response => {
if (response === 0) {
ipcRenderer.send('reload-full-app');
}
});
} else {
ipcRenderer.send('reload-full-app');
}
} else if (domainsAdded.length > 0) {
// find all orgs that failed
const failedDomains: string[] = [];
for (const org of this.presetOrgs) {
if (DomainUtil.duplicateDomain(org)) {
continue;
}
failedDomains.push(org);
}
const { title, content } = Messages.enterpriseOrgError(domainsAdded.length, failedDomains);
dialog.showErrorBox(title, content);
if (DomainUtil.getDomains().length === 0) {
// no orgs present, stop showing loading gif
this.openSettings('AddServer');
}
}
}
initTabs(): void {
initTabs() {
const servers = DomainUtil.getDomains();
if (servers.length > 0) {
for (let i = 0; i < servers.length; i++) {
this.initServer(servers[i], i);
DomainUtil.updateSavedServer(servers[i].url, i);
this.activateTab(i);
}
// Open last active tab
let lastActiveTab = ConfigUtil.getConfigItem('lastActiveTab');
if (lastActiveTab >= servers.length) {
lastActiveTab = 0;
}
// checkDomain() and webview.load() for lastActiveTab before the others
DomainUtil.updateSavedServer(servers[lastActiveTab].url, lastActiveTab);
this.activateTab(lastActiveTab);
for (let i = 0; i < servers.length; i++) {
// after the lastActiveTab is activated, we load the others in the background
// without activating them, to prevent flashing of server icons
if (i === lastActiveTab) {
continue;
}
DomainUtil.updateSavedServer(servers[i].url, i);
this.tabs[i].webview.load();
}
this.activateTab(ConfigUtil.getConfigItem('lastActiveTab'));
// Remove focus from the settings icon at sidebar bottom
this.$settingsButton.classList.remove('active');
} else if (this.presetOrgs.length === 0) {
// not attempting to add organisations in the background
this.openSettings('AddServer');
} else {
this.showLoading(true);
this.openSettings('AddServer');
}
}
initServer(server: any, index: number): void {
initServer(server, index) {
const tabIndex = this.getTabIndex();
this.tabs.push(new ServerTab({
role: 'server',
@@ -364,7 +196,7 @@ class ServerManagerView {
isActive: () => {
return index === this.activeTabIndex;
},
switchLoading: (loading: boolean, url: string) => {
switchLoading: (loading, url) => {
if (!loading && this.loading[url]) {
this.loading[url] = false;
} else if (loading && !this.loading[url]) {
@@ -372,7 +204,7 @@ class ServerManagerView {
}
this.showLoading(this.loading[this.tabs[this.activeTabIndex].webview.props.url]);
},
onNetworkError: (index: number) => this.openNetworkTroubleshooting(index),
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: false,
preload: true
@@ -381,14 +213,14 @@ class ServerManagerView {
this.loading[server.url] = true;
}
initActions(): void {
initActions() {
this.initDNDButton();
this.initServerActions();
this.initLeftSidebarEvents();
}
initServerActions(): void {
const $serverImgs: NodeListOf<HTMLImageElement> = document.querySelectorAll('.server-icons');
initServerActions() {
const $serverImgs = document.querySelectorAll('.server-icons');
$serverImgs.forEach(($serverImg, index) => {
this.addContextMenu($serverImg, index);
if ($serverImg.src.includes('img/icon.png')) {
@@ -400,7 +232,7 @@ class ServerManagerView {
});
}
initLeftSidebarEvents(): void {
initLeftSidebarEvents() {
this.$dndButton.addEventListener('click', () => {
const dndUtil = DNDUtil.toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
@@ -426,22 +258,22 @@ class ServerManagerView {
this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
}
initDNDButton(): void {
initDNDButton() {
const dnd = ConfigUtil.getConfigItem('dnd', false);
this.toggleDNDButton(dnd);
}
getTabIndex(): number {
getTabIndex() {
const currentIndex = this.tabIndex;
this.tabIndex++;
return currentIndex;
}
getCurrentActiveServer(): string {
getCurrentActiveServer() {
return this.tabs[this.activeTabIndex].webview.props.url;
}
displayInitialCharLogo($img: HTMLImageElement, index: number): void {
displayInitialCharLogo($img, index) {
/*
index parameter needed because webview[data-tab-id] can increment
beyond size of sidebar org array and throw error
@@ -464,12 +296,12 @@ class ServerManagerView {
$altIcon.classList.add('alt-icon');
$parent.removeChild($img);
$parent.append($altIcon);
$parent.appendChild($altIcon);
this.addContextMenu($altIcon as HTMLImageElement, index);
this.addContextMenu($altIcon, index);
}
sidebarHoverEvent(SidebarButton: HTMLButtonElement, SidebarTooltip: HTMLElement, addServer = false): void {
sidebarHoverEvent(SidebarButton, SidebarTooltip, addServer = false) {
SidebarButton.addEventListener('mouseover', () => {
SidebarTooltip.removeAttribute('style');
// To handle position of add server tooltip due to scrolling of list of organizations
@@ -486,7 +318,7 @@ class ServerManagerView {
});
}
onHover(index: number): void {
onHover(index) {
// this.$serverIconTooltip[index].innerHTML already has realm name, so we are just
// removing the style.
this.$serverIconTooltip[index].removeAttribute('style');
@@ -497,11 +329,11 @@ class ServerManagerView {
this.$serverIconTooltip[index].style.top = top + 'px';
}
onHoverOut(index: number): void {
onHoverOut(index) {
this.$serverIconTooltip[index].style.display = 'none';
}
openFunctionalTab(tabProps: FunctionalTabProps): void {
openFunctionalTab(tabProps) {
if (this.functionalTabs[tabProps.name] !== undefined) {
this.activateTab(this.functionalTabs[tabProps.name]);
return;
@@ -530,7 +362,7 @@ class ServerManagerView {
isActive: () => {
return this.functionalTabs[tabProps.name] === this.activeTabIndex;
},
switchLoading: (loading: AnyObject, url: string) => {
switchLoading: (loading, url) => {
if (!loading && this.loading[url]) {
this.loading[url] = false;
} else if (loading && !this.loading[url]) {
@@ -538,7 +370,7 @@ class ServerManagerView {
}
this.showLoading(this.loading[this.tabs[this.activeTabIndex].webview.props.url]);
},
onNetworkError: (index: number) => this.openNetworkTroubleshooting(index),
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: true,
preload: false
@@ -552,45 +384,46 @@ class ServerManagerView {
this.activateTab(this.functionalTabs[tabProps.name]);
}
openSettings(nav = 'General'): void {
openSettings(nav = 'General') {
this.openFunctionalTab({
name: 'Settings',
materialIcon: 'settings',
url: `file://${rendererDirectory}/preference.html#${nav}`
url: `file://${__dirname}/preference.html#${nav}`
});
this.$settingsButton.classList.add('active');
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
}
openAbout(): void {
openAbout() {
this.openFunctionalTab({
name: 'About',
materialIcon: 'sentiment_very_satisfied',
url: `file://${rendererDirectory}/about.html`
url: `file://${__dirname}/about.html`
});
}
openNetworkTroubleshooting(index: number): void {
const reconnectUtil = new ReconnectUtil(this.tabs[index].webview);
reconnectUtil.pollInternetAndReload();
this.tabs[index].webview.props.url = `file://${rendererDirectory}/network.html`;
this.tabs[index].showNetworkError();
openNetworkTroubleshooting() {
this.openFunctionalTab({
name: 'Network Troubleshooting',
materialIcon: 'network_check',
url: `file://${__dirname}/network.html`
});
}
activateLastTab(index: number): void {
activateLastTab(index) {
// Open all the tabs in background, also activate the tab based on the index
this.activateTab(index);
// Save last active tab via main process to avoid JSON DB errors
ipcRenderer.send('save-last-tab', index);
// Save last active tab
ConfigUtil.setConfigItem('lastActiveTab', index);
}
// returns this.tabs in an way that does
// 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) => {
get tabsForIpc() {
const tabs = [];
this.tabs.forEach(tab => {
const proto = Object.create(Object.getPrototypeOf(tab));
const tabClone = Object.assign(proto, tab);
@@ -603,7 +436,7 @@ class ServerManagerView {
return tabs;
}
activateTab(index: number, hideOldTab = true): void {
activateTab(index, hideOldTab = true) {
if (!this.tabs[index]) {
return;
}
@@ -640,7 +473,7 @@ class ServerManagerView {
});
}
showLoading(loading: boolean): void {
showLoading(loading) {
if (!loading) {
this.$reloadButton.removeAttribute('style');
this.$loadingIndicator.style.display = 'none';
@@ -650,7 +483,7 @@ class ServerManagerView {
}
}
destroyTab(name: string, index: number): void {
destroyTab(name, index) {
if (this.tabs[index].webview.loading) {
return;
}
@@ -666,7 +499,7 @@ class ServerManagerView {
}
}
destroyView(): void {
destroyView() {
// Show loading indicator
this.$webviewsContainer.classList.remove('loaded');
@@ -680,7 +513,7 @@ class ServerManagerView {
this.$webviewsContainer.innerHTML = '';
}
reloadView(): void {
reloadView() {
// Save and remember the index of last active tab so that we can use it later
const lastActiveTab = this.tabs[this.activeTabIndex].props.index;
ConfigUtil.setConfigItem('lastActiveTab', lastActiveTab);
@@ -693,33 +526,33 @@ class ServerManagerView {
// This will trigger when pressed CTRL/CMD + R [WIP]
// It won't reload the current view properly when you add/delete a server.
reloadCurrentView(): void {
reloadCurrentView() {
this.$reloadButton.click();
}
updateBadge(): void {
updateBadge() {
let messageCountAll = 0;
for (const tab of this.tabs) {
if (tab && tab instanceof ServerTab && tab.updateBadge) {
const count = tab.webview.badgeCount;
for (let i = 0; i < this.tabs.length; i++) {
if (this.tabs[i] && this.tabs[i].updateBadge) {
const count = this.tabs[i].webview.badgeCount;
messageCountAll += count;
tab.updateBadge(count);
this.tabs[i].updateBadge(count);
}
}
ipcRenderer.send('update-badge', messageCountAll);
}
updateGeneralSettings(setting: string, value: any): void {
updateGeneralSettings(setting, value) {
const selector = 'webview:not([class*=disabled])';
const webview: Electron.WebviewTag = document.querySelector(selector);
const webview = document.querySelector(selector);
if (webview) {
const webContents = webview.getWebContents();
webContents.send(setting, value);
}
}
toggleSidebar(show: boolean): void {
toggleSidebar(show) {
if (show) {
this.$sidebar.classList.remove('sidebar-hide');
} else {
@@ -728,17 +561,12 @@ class ServerManagerView {
}
// Toggles the dnd button icon.
toggleDNDButton(alert: boolean): void {
toggleDNDButton(alert) {
this.$dndTooltip.textContent = (alert ? 'Disable' : 'Enable') + ' Do Not Disturb';
this.$dndButton.querySelector('i').textContent = alert ? 'notifications_off' : 'notifications';
}
isLoggedIn(tabIndex: number): boolean {
const url = this.tabs[tabIndex].webview.$el.src;
return !(url.endsWith('/login/') || this.tabs[tabIndex].webview.loading);
}
addContextMenu($serverImg: HTMLImageElement, index: number): void {
addContextMenu($serverImg, index) {
$serverImg.addEventListener('contextmenu', e => {
e.preventDefault();
const template = [
@@ -752,25 +580,12 @@ class ServerManagerView {
message: 'Are you sure you want to disconnect this organization?'
}, response => {
if (response === 0) {
if (DomainUtil.removeDomain(index)) {
DomainUtil.removeDomain(index);
ipcRenderer.send('reload-full-app');
} else {
const { title, content } = Messages.orgRemovalError(DomainUtil.getDomain(index).url);
dialog.showErrorBox(title, content);
}
}
});
}
},
{
label: 'Notification settings',
enabled: this.isLoggedIn(index),
click: () => {
// switch to tab whose icon was right-clicked
this.activateTab(index);
this.tabs[index].webview.showNotificationSettings();
}
},
{
label: 'Copy Zulip URL',
click: () => {
@@ -783,8 +598,8 @@ class ServerManagerView {
});
}
registerIpcs(): void {
const webviewListeners: AnyObject = {
registerIpcs() {
const webviewListeners = {
'webview-reload': 'reload',
back: 'back',
focus: 'focus',
@@ -801,16 +616,12 @@ class ServerManagerView {
ipcRenderer.on(key, () => {
const activeWebview = this.tabs[this.activeTabIndex].webview;
if (activeWebview) {
activeWebview[webviewListeners[key] as string]();
activeWebview[webviewListeners[key]]();
}
});
}
ipcRenderer.on('show-network-error', (event: Event, index: number) => {
this.openNetworkTroubleshooting(index);
});
ipcRenderer.on('open-settings', (event: Event, settingNav: string) => {
ipcRenderer.on('open-settings', (event, settingNav) => {
this.openSettings(settingNav);
});
@@ -834,7 +645,7 @@ class ServerManagerView {
ipcRenderer.send('clear-app-settings');
});
ipcRenderer.on('switch-server-tab', (event: Event, index: number) => {
ipcRenderer.on('switch-server-tab', (event, index) => {
this.activateLastTab(index);
});
@@ -842,7 +653,7 @@ class ServerManagerView {
this.openSettings('AddServer');
});
ipcRenderer.on('reload-proxy', (event: Event, showAlert: boolean) => {
ipcRenderer.on('reload-proxy', (event, showAlert) => {
this.loadProxy().then(() => {
if (showAlert) {
alert('Proxy settings saved!');
@@ -851,7 +662,7 @@ class ServerManagerView {
});
});
ipcRenderer.on('toggle-sidebar', (event: Event, show: boolean) => {
ipcRenderer.on('toggle-sidebar', (event, show) => {
// Toggle the left sidebar
this.toggleSidebar(show);
@@ -859,8 +670,8 @@ class ServerManagerView {
this.updateGeneralSettings('toggle-sidebar-setting', show);
});
ipcRenderer.on('toggle-silent', (event: Event, state: boolean) => {
const webviews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webview');
ipcRenderer.on('toggle-silent', (event, state) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
try {
webview.setAudioMuted(state);
@@ -873,7 +684,7 @@ class ServerManagerView {
});
});
ipcRenderer.on('toggle-autohide-menubar', (event: Event, autoHideMenubar: boolean, updateMenu: boolean) => {
ipcRenderer.on('toggle-autohide-menubar', (event, autoHideMenubar, updateMenu) => {
if (updateMenu) {
ipcRenderer.send('update-menu', {
tabs: this.tabsForIpc,
@@ -884,18 +695,17 @@ class ServerManagerView {
this.updateGeneralSettings('toggle-menubar-setting', autoHideMenubar);
});
ipcRenderer.on('toggle-dnd', (event: Event, state: boolean, newSettings: SettingsOptions) => {
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.toggleDNDButton(state);
ipcRenderer.send('forward-message', 'toggle-silent', newSettings.silent);
const selector = 'webview:not([class*=disabled])';
const webview: Electron.WebviewTag = document.querySelector(selector);
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-dnd', state, newSettings);
});
ipcRenderer.on('update-realm-name', (event: Event, serverURL: string, realmName: string) => {
// TODO: TypeScript - Type annotate getDomains() or this domain paramter.
DomainUtil.getDomains().forEach((domain: any, index: number) => {
ipcRenderer.on('update-realm-name', (event, serverURL, realmName) => {
DomainUtil.getDomains().forEach((domain, index) => {
if (domain.url.includes(serverURL)) {
const serverTooltipSelector = `.tab .server-tooltip`;
const serverTooltips = document.querySelectorAll(serverTooltipSelector);
@@ -915,13 +725,12 @@ class ServerManagerView {
});
});
ipcRenderer.on('update-realm-icon', (event: Event, serverURL: string, iconURL: string) => {
// TODO: TypeScript - Type annotate getDomains() or this domain paramter.
DomainUtil.getDomains().forEach((domain: any, index: number) => {
ipcRenderer.on('update-realm-icon', (event, serverURL, iconURL) => {
DomainUtil.getDomains().forEach((domain, index) => {
if (domain.url.includes(serverURL)) {
DomainUtil.saveServerIcon(iconURL).then((localIconUrl: string) => {
DomainUtil.saveServerIcon(iconURL).then(localIconUrl => {
const serverImgsSelector = `.tab .server-icons`;
const serverImgs: NodeListOf<HTMLImageElement> = document.querySelectorAll(serverImgsSelector);
const serverImgs = document.querySelectorAll(serverImgsSelector);
serverImgs[index].src = localIconUrl;
domain.icon = localIconUrl;
@@ -941,21 +750,21 @@ class ServerManagerView {
this.$fullscreenPopup.classList.remove('show');
});
ipcRenderer.on('focus-webview-with-id', (event: Event, webviewId: number) => {
const webviews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webview');
ipcRenderer.on('focus-webview-with-id', (event, webviewId) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
const currentId = webview.getWebContents().id;
const tabId = webview.getAttribute('data-tab-id');
const concurrentTab: HTMLButtonElement = document.querySelector(`div[data-tab-id="${tabId}"]`);
const concurrentTab = document.querySelector(`div[data-tab-id="${tabId}"]`);
if (currentId === webviewId) {
concurrentTab.click();
}
});
});
ipcRenderer.on('render-taskbar-icon', (event: Event, messageCount: number) => {
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
// Create a canvas from unread messagecounts
function createOverlayIcon(messageCount: number): HTMLCanvasElement {
function createOverlayIcon(messageCount) {
const canvas = document.createElement('canvas');
canvas.height = 128;
canvas.width = 128;
@@ -993,38 +802,29 @@ class ServerManagerView {
ipcRenderer.on('new-server', () => {
this.openSettings('AddServer');
});
ipcRenderer.on('set-active', () => {
const webviews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webview');
webviews.forEach(webview => {
webview.send('set-active');
});
});
ipcRenderer.on('set-idle', () => {
const webviews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webview');
webviews.forEach(webview => {
webview.send('set-idle');
});
});
ipcRenderer.on('open-network-settings', () => {
this.openSettings('Network');
});
}
}
window.addEventListener('load', () => {
window.onload = () => {
const serverManagerView = new ServerManagerView();
const reconnectUtil = new ReconnectUtil(serverManagerView);
serverManagerView.init();
window.addEventListener('online', () => {
reconnectUtil.pollInternetAndReload();
});
window.addEventListener('offline', () => {
reconnectUtil.clearState();
logger.log('No internet connection, you are offline.');
});
// only start electron-connect (auto reload on change) when its ran
// from `npm run dev` or `gulp dev` and not from `npm start` when
// app is started `npm start` main process's proces.argv will have
// `--no-electron-connect`
const mainProcessArgv = remote.getGlobal('process').argv;
if (isDev && !mainProcessArgv.includes('--no-electron-connect')) {
require('electron-connect').client.create();
const electronConnect = require('electron-connect');
electronConnect.client.create();
}
});
export = new ServerManagerView();
};

View File

@@ -1,28 +1,18 @@
'use strict';
import { ipcRenderer } from 'electron';
import {
const { ipcRenderer } = require('electron');
const url = require('url');
const MacNotifier = require('node-mac-notifier');
const ConfigUtil = require('../utils/config-util');
const {
appId, customReply, focusCurrentServer, parseReply, setupReply
} from './helpers';
import url = require('url');
import MacNotifier = require('node-mac-notifier');
import ConfigUtil = require('../utils/config-util');
type ReplyHandler = (response: string) => void;
type ClickHandler = () => void;
let replyHandler: ReplyHandler;
let clickHandler: ClickHandler;
declare const window: ZulipWebWindow;
interface NotificationHandlerArgs {
response: string;
}
} = require('./helpers');
let replyHandler;
let clickHandler;
class DarwinNotification {
tag: string;
constructor(title: string, opts: NotificationOptions) {
const silent: boolean = ConfigUtil.getConfigItem('silent') || false;
constructor(title, opts) {
const silent = ConfigUtil.getConfigItem('silent') || false;
const { host, protocol } = location;
const { icon } = opts;
const profilePic = url.resolve(`${protocol}//${host}`, icon);
@@ -49,53 +39,49 @@ class DarwinNotification {
notification.addEventListener('reply', this.notificationHandler);
}
static requestPermission(): void {
static requestPermission() {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission(): NotificationPermission {
static get permission() {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
set onreply(handler: ReplyHandler) {
set onreply(handler) {
replyHandler = handler;
}
get onreply(): ReplyHandler {
get onreply() {
return replyHandler;
}
set onclick(handler: ClickHandler) {
set onclick(handler) {
clickHandler = handler;
}
get onclick(): ClickHandler {
get onclick() {
return clickHandler;
}
// not something that is common or
// used by zulip server but added to be
// future proff.
addEventListener(event: string, handler: ClickHandler | ReplyHandler): void {
addEventListener(event, handler) {
if (event === 'click') {
clickHandler = handler as ClickHandler;
clickHandler = handler;
}
if (event === 'reply') {
replyHandler = handler as ReplyHandler;
replyHandler = handler;
}
}
notificationHandler({ response }: NotificationHandlerArgs): void {
notificationHandler({ response }) {
response = parseReply(response);
focusCurrentServer();
if (window.electron_bridge.send_notification_reply_message_supported) {
window.electron_bridge.send_event('send_notification_reply_message', this.tag, response);
return;
}
setupReply(this.tag);
if (replyHandler) {
replyHandler(response);
return;
@@ -106,7 +92,7 @@ class DarwinNotification {
// method specific to notification api
// used by zulip
close(): void {
close() {
return; // eslint-disable-line no-useless-return
}
}

View File

@@ -1,13 +1,12 @@
'use strict';
import { ipcRenderer } from 'electron';
import { focusCurrentServer } from './helpers';
import ConfigUtil = require('../utils/config-util');
const { ipcRenderer } = require('electron');
const ConfigUtil = require('../utils/config-util');
const { focusCurrentServer } = require('./helpers');
const NativeNotification = window.Notification;
class BaseNotification extends NativeNotification {
constructor(title: string, opts: NotificationOptions) {
constructor(title, opts) {
opts.silent = true;
super(title, opts);
@@ -19,14 +18,14 @@ class BaseNotification extends NativeNotification {
});
}
static requestPermission(): void {
static requestPermission() {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission(): NotificationPermission {
static get permission() {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
}
export = BaseNotification;
module.exports = BaseNotification;

View File

@@ -1,6 +1,5 @@
import { remote } from 'electron';
import Logger = require('../utils/logger-util');
const { remote } = require('electron');
const Logger = require('../utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
@@ -8,25 +7,24 @@ const logger = new Logger({
});
// Do not change this
export const appId = 'org.zulip.zulip-electron';
const appId = 'org.zulip.zulip-electron';
export type BotListItem = [string, string];
const botsList: BotListItem[] = [];
const botsList = [];
let botsListLoaded = false;
// this function load list of bots from the server
// sync=True for a synchronous getJSON request
// in case botsList isn't already completely loaded when required in parseRely
export function loadBots(sync = false): void {
function loadBots(sync = false) {
const { $ } = window;
botsList.length = 0;
if (sync) {
$.ajaxSetup({async: false});
}
$.getJSON('/json/users')
.done((data: any) => {
const { members } = data;
members.forEach((membersRow: any) => {
.done(data => {
const members = data.members;
members.forEach(membersRow => {
if (membersRow.is_bot) {
const bot = `@${membersRow.full_name}`;
const mention = `@**${bot.replace(/^@/, '')}**`;
@@ -35,7 +33,7 @@ export function loadBots(sync = false): void {
});
botsListLoaded = true;
})
.fail((error: any) => {
.fail(error => {
logger.log('Load bots request failed: ', error.responseText);
logger.log('Load bots request status: ', error.statusText);
});
@@ -44,7 +42,7 @@ export function loadBots(sync = false): void {
}
}
export function checkElements(...elements: any[]): boolean {
function checkElements(...elements) {
let status = true;
elements.forEach(element => {
if (element === null || element === undefined) {
@@ -54,13 +52,13 @@ export function checkElements(...elements: any[]): boolean {
return status;
}
export function customReply(reply: string): void {
function customReply(reply) {
// server does not support notification reply yet.
const buttonSelector = '.messagebox #send_controls button[type=submit]';
const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content';
const textarea: HTMLInputElement = document.querySelector('#compose-textarea');
const messagebox: HTMLButtonElement = document.querySelector(messageboxSelector);
const sendButton: HTMLButtonElement = document.querySelector(buttonSelector);
const textarea = document.querySelector('#compose-textarea');
const messagebox = document.querySelector(messageboxSelector);
const sendButton = document.querySelector(buttonSelector);
// sanity check for old server versions
const elementsExists = checkElements(textarea, messagebox, sendButton);
@@ -79,24 +77,19 @@ const webContentsId = webContents.id;
// this function will focus the server that sent
// the notification. Main function implemented in main.js
export function focusCurrentServer(): void {
// TODO: TypeScript: currentWindow of type BrowserWindow doesn't
// have a .send() property per typescript.
(currentWindow as any).send('focus-webview-with-id', webContentsId);
function focusCurrentServer() {
currentWindow.send('focus-webview-with-id', webContentsId);
}
// this function parses the reply from to notification
// making it easier to reply from notification eg
// @username in reply will be converted to @**username**
// #stream in reply will be converted to #**stream**
// bot mentions are not yet supported
export function parseReply(reply: string): string {
function parseReply(reply) {
const usersDiv = document.querySelectorAll('#user_presences li');
const streamHolder = document.querySelectorAll('#stream_filters li');
type UsersItem = BotListItem;
type StreamsItem = BotListItem;
const users: UsersItem[] = [];
const streams: StreamsItem[] = [];
const users = [];
const streams = [];
usersDiv.forEach(userRow => {
const anchor = userRow.querySelector('span a');
@@ -146,8 +139,18 @@ export function parseReply(reply: string): string {
return reply;
}
export function setupReply(id: string): void {
function setupReply(id) {
const { narrow } = window;
const narrowByTopic = narrow.by_topic || narrow.by_subject;
narrowByTopic(id, { trigger: 'notification' });
}
module.exports = {
appId,
checkElements,
customReply,
parseReply,
setupReply,
focusCurrentServer,
loadBots
};

View File

@@ -1,11 +1,12 @@
'use strict';
import { remote } from 'electron';
import * as params from '../utils/params-util';
import { appId, loadBots } from './helpers';
const {
remote: { app }
} = require('electron');
import DefaultNotification = require('./default-notification');
const { app } = remote;
const params = require('../utils/params-util.js');
const DefaultNotification = require('./default-notification');
const { appId, loadBots } = require('./helpers');
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
@@ -14,11 +15,12 @@ app.setAppUserModelId(appId);
window.Notification = DefaultNotification;
if (process.platform === 'darwin') {
window.Notification = require('./darwin-notifications');
const DarwinNotification = require('./darwin-notifications');
window.Notification = DarwinNotification;
}
window.addEventListener('load', () => {
// eslint-disable-next-line no-undef, @typescript-eslint/camelcase
// eslint-disable-next-line no-undef, camelcase
if (params.isPageParams() && page_params.realm_uri) {
loadBots();
}

View File

@@ -0,0 +1,20 @@
'use strict';
const {ipcRenderer} = require('electron');
class NetworkTroubleshootingView {
constructor() {
this.$reconnectButton = document.getElementById('reconnect');
}
init() {
this.$reconnectButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
}
window.onload = () => {
const networkTroubleshootingView = new NetworkTroubleshootingView();
networkTroubleshootingView.init();
};

View File

@@ -1,16 +0,0 @@
'use strict';
import { ipcRenderer } from 'electron';
class NetworkTroubleshootingView {
init($reconnectButton: Element, $settingsButton: Element): void {
$reconnectButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
$settingsButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'open-settings');
});
}
}
export = new NetworkTroubleshootingView();

View File

@@ -1,52 +1,43 @@
'use-strict';
import { remote, OpenDialogOptions } from 'electron';
const { dialog } = require('electron').remote;
const path = require('path');
import path = require('path');
import BaseComponent = require('../../components/base');
import CertificateUtil = require('../../utils/certificate-util');
import DomainUtil = require('../../utils/domain-util');
import t = require('../../utils/translation-util');
const { dialog } = remote;
const BaseComponent = require(__dirname + '/../../components/base.js');
const CertificateUtil = require(__dirname + '/../../utils/certificate-util.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class AddCertificate extends BaseComponent {
// TODO: TypeScript - Here props should be object type
props: any;
_certFile: string;
$addCertificate: Element | null;
addCertificateButton: Element | null;
serverUrl: HTMLInputElement | null;
constructor(props: any) {
constructor(props) {
super();
this.props = props;
this._certFile = '';
}
template(): string {
template() {
return `
<div class="settings-card certificates-card">
<div class="certificate-input">
<div>${t.__('Organization URL')}</div>
<div>Organization URL</div>
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="certificate-input">
<div>${t.__('Certificate file')}</div>
<button class="green" id="add-certificate-button">${t.__('Upload')}</button>
<div>Certificate file</div>
<button class="green" id="add-certificate-button">Upload</button>
</div>
</div>
`;
}
init(): void {
init() {
this.$addCertificate = this.generateNodeFromTemplate(this.template());
this.props.$root.append(this.$addCertificate);
this.props.$root.appendChild(this.$addCertificate);
this.addCertificateButton = this.$addCertificate.querySelector('#add-certificate-button');
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0];
this.initListeners();
}
validateAndAdd(): void {
validateAndAdd() {
const certificate = this._certFile;
const serverUrl = this.serverUrl.value;
if (certificate !== '' && serverUrl !== '') {
@@ -68,9 +59,10 @@ class AddCertificate extends BaseComponent {
}
}
addHandler(): void {
const showDialogOptions: OpenDialogOptions = {
addHandler() {
const showDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'crt, pem', extensions: ['crt', 'pem'] }]
};
@@ -82,7 +74,7 @@ class AddCertificate extends BaseComponent {
});
}
initListeners(): void {
initListeners() {
this.addCertificateButton.addEventListener('click', () => {
this.addHandler();
});
@@ -97,4 +89,4 @@ class AddCertificate extends BaseComponent {
}
}
export = AddCertificate;
module.exports = AddCertificate;

View File

@@ -1,10 +1,10 @@
'use strict';
import { app } from 'electron';
const electron = require('electron');
const { app } = require('electron');
import electron = require('electron');
import ConfigUtil = require('../../utils/config-util');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
let instance: BadgeSettings | any = null;
let instance = null;
class BadgeSettings {
constructor() {
@@ -17,16 +17,16 @@ class BadgeSettings {
return instance;
}
showBadgeCount(messageCount: number, mainWindow: electron.BrowserWindow): void {
showBadgeCount(messageCount, mainWindow) {
if (process.platform === 'darwin') {
app.setBadgeCount(messageCount);
}
if (process.platform === 'win32') {
this.updateOverlayIcon(messageCount, mainWindow);
} else {
// This should work on both macOS and Linux
app.setBadgeCount(messageCount);
}
}
hideBadgeCount(mainWindow: electron.BrowserWindow): void {
hideBadgeCount(mainWindow) {
if (process.platform === 'darwin') {
app.setBadgeCount(0);
}
@@ -35,7 +35,7 @@ class BadgeSettings {
}
}
updateBadge(badgeCount: number, mainWindow: electron.BrowserWindow): void {
updateBadge(badgeCount, mainWindow) {
if (ConfigUtil.getConfigItem('badgeOption', true)) {
this.showBadgeCount(badgeCount, mainWindow);
} else {
@@ -43,7 +43,7 @@ class BadgeSettings {
}
}
updateOverlayIcon(messageCount: number, mainWindow: electron.BrowserWindow): void {
updateOverlayIcon(messageCount, mainWindow) {
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(ConfigUtil.getConfigItem('flashTaskbarOnMessage'));
}
@@ -54,10 +54,10 @@ class BadgeSettings {
}
}
updateTaskbarIcon(data: string, text: string, mainWindow: electron.BrowserWindow): void {
updateTaskbarIcon(data, text, mainWindow) {
const img = electron.nativeImage.createFromDataURL(data);
mainWindow.setOverlayIcon(img, text);
}
}
export = new BadgeSettings();
module.exports = new BadgeSettings();

View File

@@ -0,0 +1,46 @@
'use strict';
const {ipcRenderer} = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.js');
class BaseSection extends BaseComponent {
generateSettingOption(props) {
const {$element, value, clickHandler} = props;
$element.innerHTML = '';
const $optionControl = this.generateNodeFromTemplate(this.generateOptionTemplate(value));
$element.appendChild($optionControl);
$optionControl.addEventListener('click', clickHandler);
}
generateOptionTemplate(settingOption) {
if (settingOption) {
return `
<div class="action">
<div class="switch">
<input class="toggle toggle-round" type="checkbox" checked>
<label></label>
</div>
</div>
`;
} else {
return `
<div class="action">
<div class="switch">
<input class="toggle toggle-round" type="checkbox">
<label></label>
</div>
</div>
`;
}
}
reloadApp() {
ipcRenderer.send('forward-message', 'reload-viewer');
}
}
module.exports = BaseSection;

View File

@@ -1,50 +0,0 @@
'use strict';
import { ipcRenderer } from 'electron';
import BaseComponent = require('../../components/base');
class BaseSection extends BaseComponent {
// TODO: TypeScript - Here props should be object type
generateSettingOption(props: any): void {
const {$element, disabled, value, clickHandler} = props;
$element.innerHTML = '';
const $optionControl = this.generateNodeFromTemplate(this.generateOptionTemplate(value, disabled));
$element.append($optionControl);
if (!disabled) {
$optionControl.addEventListener('click', clickHandler);
}
}
generateOptionTemplate(settingOption: boolean, disabled: boolean): string {
const label = disabled ? `<label class="disallowed" title="Setting locked by system administrator."/>` : `<label/>`;
if (settingOption) {
return `
<div class="action">
<div class="switch">
<input class="toggle toggle-round" type="checkbox" checked disabled>
${label}
</div>
</div>
`;
} else {
return `
<div class="action">
<div class="switch">
<input class="toggle toggle-round" type="checkbox">
${label}
</div>
</div>
`;
}
}
reloadApp(): void {
ipcRenderer.send('forward-message', 'reload-viewer');
}
}
export = BaseSection;

View File

@@ -0,0 +1,82 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js');
const AddCertificate = require(__dirname + '/add-certificate.js');
const FindAccounts = require(__dirname + '/find-accounts.js');
class ConnectedOrgSection extends BaseSection {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">Connected organizations</div>
<div class="title" id="existing-servers">All the connected orgnizations will appear here.</div>
<div id="server-info-container"></div>
<div id="new-org-button"><button class="green sea w-250">Connect to another organization</button></div>
<div class="page-title">Add Custom Certificates</div>
<div id="add-certificate-container"></div>
<div class="page-title">Find accounts by email</div>
<div id="find-accounts-container"></div>
</div>
`;
}
init() {
this.initServers();
}
initServers() {
this.props.$root.innerHTML = '';
const servers = DomainUtil.getDomains();
this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.getElementById('server-info-container');
this.$existingServers = document.getElementById('existing-servers');
this.$newOrgButton = document.getElementById('new-org-button');
this.$addCertificateContainer = document.getElementById('add-certificate-container');
this.$findAccountsContainer = document.getElementById('find-accounts-container');
const noServerText = 'All the connected orgnizations will appear here';
// Show noServerText if no servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? noServerText : '';
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.reloadApp
}).init();
}
this.$newOrgButton.addEventListener('click', () => {
// We don't need to import this since it's already imported in other files
// eslint-disable-next-line no-undef
ipcRenderer.send('forward-message', 'open-org-tab');
});
this.initAddCertificate();
this.initFindAccounts();
}
initAddCertificate() {
new AddCertificate({
$root: this.$addCertificateContainer
}).init();
}
initFindAccounts() {
new FindAccounts({
$root: this.$findAccountsContainer
}).init();
}
}
module.exports = ConnectedOrgSection;

View File

@@ -1,90 +0,0 @@
'use strict';
import { ipcRenderer } from 'electron';
import BaseSection = require('./base-section');
import DomainUtil = require('../../utils/domain-util');
import ServerInfoForm = require('./server-info-form');
import AddCertificate = require('./add-certificate');
import FindAccounts = require('./find-accounts');
import t = require('../../utils/translation-util');
class ConnectedOrgSection extends BaseSection {
// TODO: TypeScript - Here props should be object type
props: any;
$serverInfoContainer: Element | null;
$existingServers: Element | null;
$newOrgButton: HTMLButtonElement | null;
$addCertificateContainer: Element | null;
$findAccountsContainer: Element | null;
constructor(props: any) {
super();
this.props = props;
}
template(): string {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">${t.__('Connected organizations')}</div>
<div class="title" id="existing-servers">${t.__('All the connected orgnizations will appear here.')}</div>
<div id="server-info-container"></div>
<div id="new-org-button"><button class="green sea w-250">${t.__('Connect to another organization')}</button></div>
<div class="page-title">${t.__('Add Custom Certificates')}</div>
<div id="add-certificate-container"></div>
<div class="page-title">${t.__('Find accounts by email')}</div>
<div id="find-accounts-container"></div>
</div>
`;
}
init(): void {
this.initServers();
}
initServers(): void {
this.props.$root.innerHTML = '';
const servers = DomainUtil.getDomains();
this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.querySelector('#server-info-container');
this.$existingServers = document.querySelector('#existing-servers');
this.$newOrgButton = document.querySelector('#new-org-button');
this.$addCertificateContainer = document.querySelector('#add-certificate-container');
this.$findAccountsContainer = document.querySelector('#find-accounts-container');
const noServerText = t.__('All the connected orgnizations will appear here');
// Show noServerText if no servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? noServerText : '';
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.reloadApp
}).init();
}
this.$newOrgButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'open-org-tab');
});
this.initAddCertificate();
this.initFindAccounts();
}
initAddCertificate(): void {
new AddCertificate({
$root: this.$addCertificateContainer
}).init();
}
initFindAccounts(): void {
new FindAccounts({
$root: this.$findAccountsContainer
}).init();
}
}
export = ConnectedOrgSection;

View File

@@ -1,44 +1,38 @@
'use-strict';
import { shell } from 'electron';
const { shell } = require('electron');
import BaseComponent = require('../../components/base');
import t = require('../../utils/translation-util');
const BaseComponent = require(__dirname + '/../../components/base.js');
class FindAccounts extends BaseComponent {
// TODO: TypeScript - Here props should be object type
props: any;
$findAccounts: Element | null;
$findAccountsButton: Element | null;
$serverUrlField: HTMLInputElement | null;
constructor(props: any) {
constructor(props) {
super();
this.props = props;
}
template(): string {
template() {
return `
<div class="settings-card certificate-card">
<div class="certificate-input">
<div>${t.__('Organization URL')}</div>
<div>Organization URL</div>
<input class="setting-input-value" value="zulipchat.com"/>
</div>
<div class="certificate-input">
<button class="green w-150" id="find-accounts-button">${t.__('Find accounts')}</button>
<button class="green w-150" id="find-accounts-button">Find accounts</button>
</div>
</div>
`;
}
init(): void {
init() {
this.$findAccounts = this.generateNodeFromTemplate(this.template());
this.props.$root.append(this.$findAccounts);
this.props.$root.appendChild(this.$findAccounts);
this.$findAccountsButton = this.$findAccounts.querySelector('#find-accounts-button');
this.$serverUrlField = this.$findAccounts.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
this.$serverUrlField = this.$findAccounts.querySelectorAll('input.setting-input-value')[0];
this.initListeners();
}
findAccounts(url: string): void {
findAccounts(url) {
if (!url) {
return;
}
@@ -48,7 +42,7 @@ class FindAccounts extends BaseComponent {
shell.openExternal(url + '/accounts/find');
}
initListeners(): void {
initListeners() {
this.$findAccountsButton.addEventListener('click', () => {
this.findAccounts(this.$serverUrlField.value);
});
@@ -75,4 +69,4 @@ class FindAccounts extends BaseComponent {
}
}
export = FindAccounts;
module.exports = FindAccounts;

View File

@@ -1,111 +1,101 @@
'use strict';
import { ipcRenderer, remote, OpenDialogOptions } from 'electron';
import path = require('path');
import fs = require('fs-extra');
const path = require('path');
const { ipcRenderer, remote } = require('electron');
const fs = require('fs-extra');
const { app, dialog } = remote;
const currentBrowserWindow = remote.getCurrentWindow();
import BaseSection = require('./base-section');
import ConfigUtil = require('../../utils/config-util');
import EnterpriseUtil = require('./../../utils/enterprise-util');
import t = require('../../utils/translation-util');
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
class GeneralSection extends BaseSection {
// TODO: TypeScript - Here props should be object type
props: any;
constructor(props: any) {
constructor(props) {
super();
this.props = props;
}
template(): string {
template() {
return `
<div class="settings-pane">
<div class="title">${t.__('Appearance')}</div>
<div class="title">Appearance</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="tray-option">
<div class="setting-description">${t.__('Show app icon in system tray')}</div>
<div class="setting-description">Show app icon in system tray</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="menubar-option" style= "display:${process.platform === 'darwin' ? 'none' : ''}">
<div class="setting-description">${t.__('Auto hide menu bar (Press Alt key to display)')}</div>
<div class="setting-description">Auto hide menu bar (Press Alt key to display)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="sidebar-option">
<div class="setting-description">${t.__('Show sidebar')} (<span class="code">${process.platform === 'darwin' ? 'Cmd+Shift+S' : 'Ctrl+Shift+S'}</span>)</div>
<div class="setting-description">Show sidebar (<span class="code">${process.platform === 'darwin' ? 'Cmd+Shift+S' : 'Ctrl+Shift+S'}</span>)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="badge-option">
<div class="setting-description">${t.__('Show app unread badge')}</div>
<div class="setting-description">Show app unread badge</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="dock-bounce-option" style= "display:${process.platform === 'darwin' ? '' : 'none'}">
<div class="setting-description">${t.__('Bounce dock on new private message')}</div>
<div class="setting-description">Bounce dock on new private message</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="flash-taskbar-option" style= "display:${process.platform === 'win32' ? '' : 'none'}">
<div class="setting-description">${t.__('Flash taskbar on new message')}</div>
<div class="setting-description">Flash taskbar on new message</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">${t.__('Desktop Notifications')}</div>
<div class="title">Desktop Notifications</div>
<div class="settings-card">
<div class="setting-row" id="show-notification-option">
<div class="setting-description">${t.__('Show desktop notifications')}</div>
<div class="setting-description">Show desktop notifications</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="silent-option">
<div class="setting-description">${t.__('Mute all sounds from Zulip')}</div>
<div class="setting-description">Mute all sounds from Zulip</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">${t.__('App Updates')}</div>
<div class="title">App Updates</div>
<div class="settings-card">
<div class="setting-row" id="autoupdate-option">
<div class="setting-description">${t.__('Enable auto updates')}</div>
<div class="setting-description">Enable auto updates</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="betaupdate-option">
<div class="setting-description">${t.__('Get beta updates')}</div>
<div class="setting-description">Get beta updates</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">${t.__('Functionality')}</div>
<div class="title">Functionality</div>
<div class="settings-card">
<div class="setting-row" id="startAtLogin-option">
<div class="setting-description">${t.__('Start app at login')}</div>
<div class="setting-description">Start app at login</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="start-minimize-option">
<div class="setting-description">${t.__('Always start minimized')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="quitOnClose-option">
<div class="setting-description">${t.__('Quit when the window is closed')}</div>
<div class="setting-description">Always start minimized</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="enable-spellchecker-option">
<div class="setting-description">${t.__('Enable spellchecker (requires restart)')}</div>
<div class="setting-description">Enable spellchecker (requires restart)</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">${t.__('Advanced')}</div>
<div class="title">Advanced</div>
<div class="settings-card">
<div class="setting-row" id="enable-error-reporting">
<div class="setting-description">${t.__('Enable error reporting (requires restart)')}</div>
<div class="setting-description">Enable error reporting (requires restart)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="show-download-folder">
<div class="setting-description">${t.__('Show downloaded files in file manager')}</div>
<div class="setting-description">Show downloaded files in file manager</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="add-custom-css">
<div class="setting-description">
${t.__('Add custom CSS')}
Add custom CSS
</div>
<button class="custom-css-button green">${t.__('Upload')}</button>
<button class="custom-css-button green">Upload</button>
</div>
<div class="setting-row" id="remove-custom-css">
<div class="setting-description">
@@ -113,14 +103,14 @@ class GeneralSection extends BaseSection {
</div>
<div class="action red" id="css-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>${t.__('Delete')}</span>
<span>Delete</span>
</div>
</div>
<div class="setting-row" id="download-folder">
<div class="setting-description">
${t.__('Default download location')}
Default download location
</div>
<button class="download-folder-button green">${t.__('Change')}</button>
<button class="download-folder-button green">Change</button>
</div>
<div class="setting-row">
<div class="setting-description">
@@ -129,19 +119,19 @@ class GeneralSection extends BaseSection {
</div>
</div>
<div class="title">${t.__('Reset Application Data')}</div>
<div class="title">Reset Application Data</div>
<div class="settings-card">
<div class="setting-row" id="resetdata-option">
<div class="setting-description">${t.__('This will delete all application data including all added accounts and preferences')}
<div class="setting-description">This will delete all application data including all added accounts and preferences
</div>
<button class="reset-data-button red w-150">${t.__('Reset App Data')}</button>
<button class="reset-data-button red w-150">Reset App Data</button>
</div>
</div>
</div>
`;
}
init(): void {
init() {
this.props.$root.innerHTML = this.template();
this.updateTrayOption();
this.updateBadgeOption();
@@ -159,7 +149,6 @@ class GeneralSection extends BaseSection {
this.removeCustomCSS();
this.downloadFolder();
this.showDownloadFolder();
this.updateQuitOnCloseOption();
this.enableErrorReporting();
// Platform specific settings
@@ -179,7 +168,7 @@ class GeneralSection extends BaseSection {
}
}
updateTrayOption(): void {
updateTrayOption() {
this.generateSettingOption({
$element: document.querySelector('#tray-option .setting-control'),
value: ConfigUtil.getConfigItem('trayIcon', true),
@@ -192,7 +181,7 @@ class GeneralSection extends BaseSection {
});
}
updateMenubarOption(): void {
updateMenubarOption() {
this.generateSettingOption({
$element: document.querySelector('#menubar-option .setting-control'),
value: ConfigUtil.getConfigItem('autoHideMenubar', false),
@@ -205,7 +194,7 @@ class GeneralSection extends BaseSection {
});
}
updateBadgeOption(): void {
updateBadgeOption() {
this.generateSettingOption({
$element: document.querySelector('#badge-option .setting-control'),
value: ConfigUtil.getConfigItem('badgeOption', true),
@@ -218,7 +207,7 @@ class GeneralSection extends BaseSection {
});
}
updateDockBouncing(): void {
updateDockBouncing() {
this.generateSettingOption({
$element: document.querySelector('#dock-bounce-option .setting-control'),
value: ConfigUtil.getConfigItem('dockBouncing', true),
@@ -230,7 +219,7 @@ class GeneralSection extends BaseSection {
});
}
updateFlashTaskbar(): void {
updateFlashTaskbar() {
this.generateSettingOption({
$element: document.querySelector('#flash-taskbar-option .setting-control'),
value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true),
@@ -242,10 +231,9 @@ class GeneralSection extends BaseSection {
});
}
autoUpdateOption(): void {
autoUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#autoupdate-option .setting-control'),
disabled: EnterpriseUtil.configItemExists('autoUpdate'),
value: ConfigUtil.getConfigItem('autoUpdate', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('autoUpdate');
@@ -259,7 +247,7 @@ class GeneralSection extends BaseSection {
});
}
betaUpdateOption(): void {
betaUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#betaupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('betaUpdate', false),
@@ -273,7 +261,7 @@ class GeneralSection extends BaseSection {
});
}
updateSilentOption(): void {
updateSilentOption() {
this.generateSettingOption({
$element: document.querySelector('#silent-option .setting-control'),
value: ConfigUtil.getConfigItem('silent', false),
@@ -281,14 +269,12 @@ class GeneralSection extends BaseSection {
const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption();
// TODO: TypeScript: currentWindow of type BrowserWindow doesn't
// have a .send() property per typescript.
(currentBrowserWindow as any).send('toggle-silent', newValue);
currentBrowserWindow.send('toggle-silent', newValue);
}
});
}
showDesktopNotification(): void {
showDesktopNotification() {
this.generateSettingOption({
$element: document.querySelector('#show-notification-option .setting-control'),
value: ConfigUtil.getConfigItem('showNotification', true),
@@ -300,7 +286,7 @@ class GeneralSection extends BaseSection {
});
}
updateSidebarOption(): void {
updateSidebarOption() {
this.generateSettingOption({
$element: document.querySelector('#sidebar-option .setting-control'),
value: ConfigUtil.getConfigItem('showSidebar', true),
@@ -313,7 +299,7 @@ class GeneralSection extends BaseSection {
});
}
updateStartAtLoginOption(): void {
updateStartAtLoginOption() {
this.generateSettingOption({
$element: document.querySelector('#startAtLogin-option .setting-control'),
value: ConfigUtil.getConfigItem('startAtLogin', false),
@@ -326,19 +312,7 @@ class GeneralSection extends BaseSection {
});
}
updateQuitOnCloseOption(): void {
this.generateSettingOption({
$element: document.querySelector('#quitOnClose-option .setting-control'),
value: ConfigUtil.getConfigItem('quitOnClose', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('quitOnClose');
ConfigUtil.setConfigItem('quitOnClose', newValue);
this.updateQuitOnCloseOption();
}
});
}
enableSpellchecker(): void {
enableSpellchecker() {
this.generateSettingOption({
$element: document.querySelector('#enable-spellchecker-option .setting-control'),
value: ConfigUtil.getConfigItem('enableSpellchecker', true),
@@ -350,7 +324,7 @@ class GeneralSection extends BaseSection {
});
}
enableErrorReporting(): void {
enableErrorReporting() {
this.generateSettingOption({
$element: document.querySelector('#enable-error-reporting .setting-control'),
value: ConfigUtil.getConfigItem('errorReporting', true),
@@ -362,7 +336,7 @@ class GeneralSection extends BaseSection {
});
}
clearAppDataDialog(): void {
clearAppDataDialog() {
const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
const getAppPath = path.join(app.getPath('appData'), app.getName());
@@ -380,9 +354,10 @@ class GeneralSection extends BaseSection {
});
}
customCssDialog(): void {
const showDialogOptions: OpenDialogOptions = {
customCssDialog() {
const showDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'CSS file', extensions: ['css'] }]
};
@@ -395,14 +370,14 @@ class GeneralSection extends BaseSection {
});
}
updateResetDataOption(): void {
updateResetDataOption() {
const resetDataButton = document.querySelector('#resetdata-option .reset-data-button');
resetDataButton.addEventListener('click', () => {
this.clearAppDataDialog();
});
}
minimizeOnStart(): void {
minimizeOnStart() {
this.generateSettingOption({
$element: document.querySelector('#start-minimize-option .setting-control'),
value: ConfigUtil.getConfigItem('startMinimized', false),
@@ -414,50 +389,51 @@ class GeneralSection extends BaseSection {
});
}
addCustomCSS(): void {
addCustomCSS() {
const customCSSButton = document.querySelector('#add-custom-css .custom-css-button');
customCSSButton.addEventListener('click', () => {
this.customCssDialog();
});
}
showCustomCSSPath(): void {
showCustomCSSPath() {
if (!ConfigUtil.getConfigItem('customCSS')) {
const cssPATH: HTMLElement = document.querySelector('#remove-custom-css');
const cssPATH = document.getElementById('remove-custom-css');
cssPATH.style.display = 'none';
}
}
removeCustomCSS(): void {
const removeCSSButton = document.querySelector('#css-delete-action');
removeCustomCSS() {
const removeCSSButton = document.getElementById('css-delete-action');
removeCSSButton.addEventListener('click', () => {
ConfigUtil.setConfigItem('customCSS', "");
ConfigUtil.setConfigItem('customCSS');
ipcRenderer.send('forward-message', 'hard-reload');
});
}
downloadFolderDialog(): void {
const showDialogOptions: OpenDialogOptions = {
downloadFolderDialog() {
const showDialogOptions = {
title: 'Select Download Location',
defaultId: 1,
properties: ['openDirectory']
};
dialog.showOpenDialog(showDialogOptions, selectedFolder => {
if (selectedFolder) {
ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
const downloadFolderPath: HTMLElement = document.querySelector('.download-folder-path');
const downloadFolderPath = document.querySelector('.download-folder-path');
downloadFolderPath.innerText = selectedFolder[0];
}
});
}
downloadFolder(): void {
downloadFolder() {
const downloadFolder = document.querySelector('#download-folder .download-folder-button');
downloadFolder.addEventListener('click', () => {
this.downloadFolderDialog();
});
}
showDownloadFolder(): void {
showDownloadFolder() {
this.generateSettingOption({
$element: document.querySelector('#show-download-folder .setting-control'),
value: ConfigUtil.getConfigItem('showDownloadFolder', false),
@@ -468,6 +444,7 @@ class GeneralSection extends BaseSection {
}
});
}
}
export = GeneralSection;
module.exports = GeneralSection;

View File

@@ -1,50 +1,49 @@
'use strict';
import BaseComponent = require('../../components/base');
import t = require('../../utils/translation-util');
const BaseComponent = require(__dirname + '/../../components/base.js');
class PreferenceNav extends BaseComponent {
// TODO: TypeScript - Here props should be object type
props: any;
navItems: string[];
$el: Element;
constructor(props: any) {
constructor(props) {
super();
this.props = props;
this.navItems = ['General', 'Network', 'AddServer', 'Organizations', 'Shortcuts'];
this.init();
}
template(): string {
template() {
let navItemsTemplate = '';
for (const navItem of this.navItems) {
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${t.__(navItem)}</div>`;
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${navItem}</div>`;
}
return `
<div>
<div id="settings-header">${t.__('Settings')}</div>
<div id="settings-header">Settings</div>
<div id="nav-container">${navItemsTemplate}</div>
</div>
`;
}
init(): void {
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.append(this.$el);
this.props.$root.appendChild(this.$el);
this.registerListeners();
}
registerListeners(): void {
registerListeners() {
for (const navItem of this.navItems) {
const $item = document.querySelector(`#nav-${navItem}`);
const $item = document.getElementById(`nav-${navItem}`);
$item.addEventListener('click', () => {
this.props.onItemSelected(navItem);
});
}
}
select(navItemToSelect: string): void {
select(navItemToSelect) {
for (const navItem of this.navItems) {
if (navItem === navItemToSelect) {
this.activate(navItem);
@@ -54,15 +53,15 @@ class PreferenceNav extends BaseComponent {
}
}
activate(navItem: string): void {
const $item = document.querySelector(`#nav-${navItem}`);
activate(navItem) {
const $item = document.getElementById(`nav-${navItem}`);
$item.classList.add('active');
}
deactivate(navItem: string): void {
const $item = document.querySelector(`#nav-${navItem}`);
deactivate(navItem) {
const $item = document.getElementById(`nav-${navItem}`);
$item.classList.remove('active');
}
}
export = PreferenceNav;
module.exports = PreferenceNav;

View File

@@ -1,53 +1,45 @@
'use strict';
import { ipcRenderer } from 'electron';
const {ipcRenderer} = require('electron');
import BaseSection = require('./base-section');
import ConfigUtil = require('../../utils/config-util');
import t = require('../../utils/translation-util');
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
class NetworkSection extends BaseSection {
// TODO: TypeScript - Here props should be object type
props: any;
$proxyPAC: HTMLInputElement;
$proxyRules: HTMLInputElement;
$proxyBypass: HTMLInputElement;
$proxySaveAction: Element;
$manualProxyBlock: Element;
constructor(props: any) {
constructor(props) {
super();
this.props = props;
}
template(): string {
template() {
return `
<div class="settings-pane">
<div class="title">${t.__('Proxy')}</div>
<div class="title">Proxy</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="use-system-settings">
<div class="setting-description">${t.__('Use system proxy settings (requires restart)')}</div>
<div class="setting-description">Use system proxy settings (requires restart)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="use-manual-settings">
<div class="setting-description">${t.__('Manual proxy configuration')}</div>
<div class="setting-description">Manual proxy configuration</div>
<div class="setting-control"></div>
</div>
<div class="manual-proxy-block">
<div class="setting-row" id="proxy-pac-option">
<span class="setting-input-key">PAC ${t.__('script')}</span>
<span class="setting-input-key">PAC script</span>
<input class="setting-input-value" placeholder="e.g. foobar.com/pacfile.js"/>
</div>
<div class="setting-row" id="proxy-rules-option">
<span class="setting-input-key">${t.__('Proxy rules')}</span>
<span class="setting-input-key">Proxy rules</span>
<input class="setting-input-value" placeholder="e.g. http=foopy:80;ftp=foopy2"/>
</div>
<div class="setting-row" id="proxy-bypass-option">
<span class="setting-input-key">${t.__('Proxy bypass rules')}</span>
<span class="setting-input-key">Proxy bypass rules</span>
<input class="setting-input-value" placeholder="e.g. foobar.com"/>
</div>
<div class="setting-row">
<div class="action green" id="proxy-save-action">
<span>${t.__('Save')}</span>
<span>Save</span>
</div>
</div>
</div>
@@ -56,12 +48,12 @@ class NetworkSection extends BaseSection {
`;
}
init(): void {
init() {
this.props.$root.innerHTML = this.template();
this.$proxyPAC = document.querySelector('#proxy-pac-option .setting-input-value');
this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
this.$proxySaveAction = document.querySelector('#proxy-save-action');
this.$proxySaveAction = document.getElementById('proxy-save-action');
this.$manualProxyBlock = this.props.$root.querySelector('.manual-proxy-block');
this.initProxyOption();
@@ -78,14 +70,14 @@ class NetworkSection extends BaseSection {
});
}
initProxyOption(): void {
initProxyOption() {
const manualProxyEnabled = ConfigUtil.getConfigItem('useManualProxy', false);
this.toggleManualProxySettings(manualProxyEnabled);
this.updateProxyOption();
}
toggleManualProxySettings(option: boolean): void {
toggleManualProxySettings(option) {
if (option) {
this.$manualProxyBlock.classList.remove('hidden');
} else {
@@ -93,7 +85,7 @@ class NetworkSection extends BaseSection {
}
}
updateProxyOption(): void {
updateProxyOption() {
this.generateSettingOption({
$element: document.querySelector('#use-system-settings .setting-control'),
value: ConfigUtil.getConfigItem('useSystemProxy', false),
@@ -133,4 +125,4 @@ class NetworkSection extends BaseSection {
}
}
export = NetworkSection;
module.exports = NetworkSection;

View File

@@ -0,0 +1,89 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const shell = require('electron').shell;
class NewServerForm extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="server-input-container">
<div class="title">Organization URL</div>
<div class="add-server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="connect">Connect</button>
</div>
</div>
<div class="server-center">
<div class="divider">
<hr class="left"/>OR<hr class="right" />
</div>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="open-create-org-link">Create a new organization</button>
</div>
</div>
</div>
`;
}
init() {
this.initForm();
this.initActions();
}
initForm() {
this.$newServerForm = this.generateNodeFromTemplate(this.template());
this.$saveServerButton = this.$newServerForm.getElementsByClassName('server-save-action')[0];
this.props.$root.innerHTML = '';
this.props.$root.appendChild(this.$newServerForm);
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.setting-input-value')[0];
}
submitFormHandler() {
this.$saveServerButton.children[0].innerHTML = 'Connecting...';
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
DomainUtil.addDomain(serverConf).then(() => {
this.props.onChange(this.props.index);
});
}, errorMessage => {
this.$saveServerButton.children[0].innerHTML = 'Connect';
alert(errorMessage);
});
}
openCreateNewOrgExternalLink() {
const link = 'https://zulipchat.com/new/';
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
initActions() {
this.$saveServerButton.addEventListener('click', () => {
this.submitFormHandler();
});
this.$newServerUrl.addEventListener('keypress', event => {
const EnterkeyCode = event.keyCode;
// Submit form when Enter key is pressed
if (EnterkeyCode === 13) {
this.submitFormHandler();
}
});
// open create new org link in default browser
this.openCreateNewOrgExternalLink();
}
}
module.exports = NewServerForm;

View File

@@ -1,107 +0,0 @@
'use strict';
import { shell, ipcRenderer } from 'electron';
import BaseComponent = require('../../components/base');
import DomainUtil = require('../../utils/domain-util');
import t = require('../../utils/translation-util');
class NewServerForm extends BaseComponent {
// TODO: TypeScript - Here props should be object type
props: any;
$newServerForm: Element;
$saveServerButton: HTMLButtonElement;
$newServerUrl: HTMLInputElement;
constructor(props: any) {
super();
this.props = props;
}
template(): string {
return `
<div class="server-input-container">
<div class="title">${t.__('Organization URL')}</div>
<div class="add-server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="connect">${t.__('Connect')}</button>
</div>
</div>
<div class="server-center">
<div class="divider">
<hr class="left"/>${t.__('OR')}<hr class="right" />
</div>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="open-create-org-link">${t.__('Create a new organization')}</button>
</div>
</div>
<div class="server-center">
<div class="server-network-option">
<span id="open-network-settings">${t.__('Network and Proxy Settings')}</span>
<i class="material-icons open-network-button">open_in_new</i>
</div>
</div>
</div>
`;
}
init(): void {
this.initForm();
this.initActions();
}
initForm(): void {
this.$newServerForm = this.generateNodeFromTemplate(this.template());
this.$saveServerButton = this.$newServerForm.querySelectorAll('.server-save-action')[0] as HTMLButtonElement;
this.props.$root.innerHTML = '';
this.props.$root.append(this.$newServerForm);
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
}
submitFormHandler(): void {
this.$saveServerButton.children[0].innerHTML = 'Connecting...';
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
DomainUtil.addDomain(serverConf).then(() => {
this.props.onChange(this.props.index);
});
}, errorMessage => {
this.$saveServerButton.children[0].innerHTML = 'Connect';
alert(errorMessage);
});
}
openCreateNewOrgExternalLink(): void {
const link = 'https://zulipchat.com/new/';
const externalCreateNewOrgEl = document.querySelector('#open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
networkSettingsLink(): void {
const networkSettingsId = document.querySelectorAll('.server-network-option')[0];
networkSettingsId.addEventListener('click', () => ipcRenderer.send('forward-message', 'open-network-settings'));
}
initActions(): void {
this.$saveServerButton.addEventListener('click', () => {
this.submitFormHandler();
});
this.$newServerUrl.addEventListener('keypress', event => {
const EnterkeyCode = event.keyCode;
// Submit form when Enter key is pressed
if (EnterkeyCode === 13) {
this.submitFormHandler();
}
});
// open create new org link in default browser
this.openCreateNewOrgExternalLink();
this.networkSettingsLink();
}
}
export = NewServerForm;

View File

@@ -1,29 +1,24 @@
'use strict';
import { ipcRenderer } from 'electron';
const BaseComponent = require(__dirname + '/js/components/base.js');
const { ipcRenderer } = require('electron');
import BaseComponent = require('../../components/base');
import Nav = require('./nav');
import ServersSection = require('./servers-section');
import GeneralSection = require('./general-section');
import NetworkSection = require('./network-section');
import ConnectedOrgSection = require('./connected-org-section');
import ShortcutsSection = require('./shortcuts-section');
type Section = ServersSection | GeneralSection | NetworkSection | ConnectedOrgSection | ShortcutsSection;
const Nav = require(__dirname + '/js/pages/preference/nav.js');
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js');
const NetworkSection = require(__dirname + '/js/pages/preference/network-section.js');
const ConnectedOrgSection = require(__dirname + '/js/pages/preference/connected-org-section.js');
const ShortcutsSection = require(__dirname + '/js/pages/preference/shortcuts-section.js');
class PreferenceView extends BaseComponent {
$sidebarContainer: Element;
$settingsContainer: Element;
nav: Nav;
section: Section;
constructor() {
super();
this.$sidebarContainer = document.querySelector('#sidebar');
this.$settingsContainer = document.querySelector('#settings-container');
this.$sidebarContainer = document.getElementById('sidebar');
this.$settingsContainer = document.getElementById('settings-container');
}
init(): void {
init() {
this.nav = new Nav({
$root: this.$sidebarContainer,
onItemSelected: this.handleNavigation.bind(this)
@@ -33,7 +28,7 @@ class PreferenceView extends BaseComponent {
this.registerIpcs();
}
setDefaultView(): void {
setDefaultView() {
let nav = 'General';
const hasTag = window.location.hash;
if (hasTag) {
@@ -42,7 +37,7 @@ class PreferenceView extends BaseComponent {
this.handleNavigation(nav);
}
handleNavigation(navItem: string): void {
handleNavigation(navItem) {
this.nav.select(navItem);
switch (navItem) {
case 'AddServer': {
@@ -82,32 +77,32 @@ class PreferenceView extends BaseComponent {
}
// Handle toggling and reflect changes in preference page
handleToggle(elementName: string, state: boolean): void {
handleToggle(elementName, state) {
const inputSelector = `#${elementName} .action .switch input`;
const input: HTMLInputElement = document.querySelector(inputSelector);
const input = document.querySelector(inputSelector);
if (input) {
input.checked = state;
}
}
registerIpcs(): void {
ipcRenderer.on('switch-settings-nav', (_event: Event, navItem: string) => {
registerIpcs() {
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
this.handleNavigation(navItem);
});
ipcRenderer.on('toggle-sidebar-setting', (_event: Event, state: boolean) => {
ipcRenderer.on('toggle-sidebar-setting', (event, state) => {
this.handleToggle('sidebar-option', state);
});
ipcRenderer.on('toggle-menubar-setting', (_event: Event, state: boolean) => {
ipcRenderer.on('toggle-menubar-setting', (event, state) => {
this.handleToggle('menubar-option', state);
});
ipcRenderer.on('toggletray', (_event: Event, state: boolean) => {
ipcRenderer.on('toggletray', (event, state) => {
this.handleToggle('tray-option', state);
});
ipcRenderer.on('toggle-dnd', (_event: Event, _state: boolean, newSettings: any) => {
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.handleToggle('show-notification-option', newSettings.showNotification);
this.handleToggle('silent-option', newSettings.silent);
@@ -118,9 +113,7 @@ class PreferenceView extends BaseComponent {
}
}
window.addEventListener('load', () => {
window.onload = () => {
const preferenceView = new PreferenceView();
preferenceView.init();
});
export = PreferenceView;
};

View File

@@ -1,28 +1,17 @@
'use strict';
const { dialog } = require('electron').remote;
const { ipcRenderer } = require('electron');
import { remote, ipcRenderer } from 'electron';
import BaseComponent = require('../../components/base');
import DomainUtil = require('../../utils/domain-util');
import Messages = require('./../../../../resources/messages');
import t = require('../../utils/translation-util');
const { dialog } = remote;
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class ServerInfoForm extends BaseComponent {
// TODO: TypeScript - Here props should be object type
props: any;
$serverInfoForm: Element;
$serverInfoAlias: Element;
$serverIcon: Element;
$deleteServerButton: Element;
$openServerButton: Element;
constructor(props: any) {
constructor(props) {
super();
this.props = props;
}
template(): string {
template() {
return `
<div class="settings-card">
<div class="server-info-left">
@@ -38,7 +27,7 @@ class ServerInfoForm extends BaseComponent {
</div>
<div class="server-info-row">
<div class="action red server-delete-action">
<span>${t.__('Disconnect')}</span>
<span>Disconnect</span>
</div>
</div>
</div>
@@ -46,35 +35,31 @@ class ServerInfoForm extends BaseComponent {
`;
}
init(): void {
init() {
this.initForm();
this.initActions();
}
initForm(): void {
initForm() {
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
this.$serverInfoAlias = this.$serverInfoForm.querySelectorAll('.server-info-alias')[0];
this.$serverIcon = this.$serverInfoForm.querySelectorAll('.server-info-icon')[0];
this.$deleteServerButton = this.$serverInfoForm.querySelectorAll('.server-delete-action')[0];
this.$openServerButton = this.$serverInfoForm.querySelectorAll('.open-tab-button')[0];
this.props.$root.append(this.$serverInfoForm);
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0];
this.$serverIcon = this.$serverInfoForm.getElementsByClassName('server-info-icon')[0];
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
this.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0];
this.props.$root.appendChild(this.$serverInfoForm);
}
initActions(): void {
initActions() {
this.$deleteServerButton.addEventListener('click', () => {
dialog.showMessageBox({
type: 'warning',
buttons: [t.__('YES'), t.__('NO')],
buttons: ['YES', 'NO'],
defaultId: 0,
message: t.__('Are you sure you want to disconnect this organization?')
message: 'Are you sure you want to disconnect this organization?'
}, response => {
if (response === 0) {
if (DomainUtil.removeDomain(this.props.index)) {
ipcRenderer.send('reload-full-app');
} else {
const { title, content } = Messages.orgRemovalError(DomainUtil.getDomain(this.props.index).url);
dialog.showErrorBox(title, content);
}
DomainUtil.removeDomain(this.props.index);
this.props.onChange(this.props.index);
}
});
});
@@ -91,6 +76,7 @@ class ServerInfoForm extends BaseComponent {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
}
}
export = ServerInfoForm;
module.exports = ServerInfoForm;

View File

@@ -1,24 +1,20 @@
'use strict';
import BaseSection = require('./base-section');
import NewServerForm = require('./new-server-form');
import t = require('../../utils/translation-util');
const BaseSection = require(__dirname + '/base-section.js');
const NewServerForm = require(__dirname + '/new-server-form.js');
class ServersSection extends BaseSection {
// TODO: TypeScript - Here props should be object type
props: any;
$newServerContainer: Element;
constructor(props: any) {
constructor(props) {
super();
this.props = props;
}
template(): string {
template() {
return `
<div class="add-server-modal">
<div class="modal-container">
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">${t.__('Add a Zulip organization')}</div>
<div class="page-title">Add a Zulip organization</div>
<div id="new-server-container"></div>
</div>
</div>
@@ -26,20 +22,20 @@ class ServersSection extends BaseSection {
`;
}
init(): void {
init() {
this.initServers();
}
initServers(): void {
initServers() {
this.props.$root.innerHTML = '';
this.props.$root.innerHTML = this.template();
this.$newServerContainer = document.querySelector('#new-server-container');
this.$newServerContainer = document.getElementById('new-server-container');
this.initNewServerForm();
}
initNewServerForm(): void {
initNewServerForm() {
new NewServerForm({
$root: this.$newServerContainer,
onChange: this.reloadApp
@@ -47,4 +43,4 @@ class ServersSection extends BaseSection {
}
}
export = ServersSection;
module.exports = ServersSection;

View File

@@ -0,0 +1,338 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const shell = require('electron').shell;
class ShortcutsSection extends BaseSection {
constructor(props) {
super();
this.props = props;
}
templateMac() {
const userOSKey = '⌘';
return `
<div class="settings-pane">
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link"> keyboard shortcuts</span>.</p></div>
<div class="title">Application Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>,</kbd></td>
<td>Settings</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
<td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
<td>Reset App Settings</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>L</kbd></td>
<td>Log Out</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<td>Hide Zulip</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<td>Hide Others</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Q</kbd></td>
<td>Quit Zulip</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Edit Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td>Undo</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td>Redo</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>X</kbd></td>
<td>Cut</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>C</kbd></td>
<td>Copy</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td>Paste</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td>Paste and Match Style</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td>Select All</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<td>Emoji & Symbols</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">View Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td>Reload</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td>Hard Reload</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
<td>Enter Full Screen</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>+</kbd></td>
<td>Zoom In</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>-</kbd></td>
<td>Zoom Out</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>0</kbd></td>
<td>Actual Size</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>I</kbd></td>
<td>Toggle DevTools for Zulip App</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>U</kbd></td>
<td>Toggle DevTools for Active Tab</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Tab</kbd></td>
<td>Switch to Next Organization</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>Switch to Previous Organization</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">History Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>←</kbd></td>
<td>Back</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>→</kbd></td>
<td>Forward</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Window Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>M</kbd></td>
<td>Minimize</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>W</kbd></td>
<td>Close</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
</div>
`;
}
templateWinLin() {
const userOSKey = 'Ctrl';
return `
<div class="settings-pane">
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link"> keyboard shortcuts</span>.</p></div>
<div class="title">Application Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>,</kbd></td>
<td>Settings</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
<td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
<td>Log Out</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Q</kbd></td>
<td>Quit Zulip</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Edit Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Z</kbd></td>
<td>Undo</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Y</kbd></td>
<td>Redo</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>X</kbd></td>
<td>Cut</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>C</kbd></td>
<td>Copy</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>V</kbd></td>
<td>Paste</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd></td>
<td>Paste and Match Style</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>A</kbd></td>
<td>Select All</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">View Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>R</kbd></td>
<td>Reload</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd></td>
<td>Hard Reload</td>
</tr>
<tr>
<td><kbd>F11</kbd></td>
<td>Toggle Full Screen</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>=</kbd></td>
<td>Zoom In</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>-</kbd></td>
<td>Zoom Out</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>0</kbd></td>
<td>Actual Size</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td>
<td>Toggle DevTools for Zulip App</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td>
<td>Toggle DevTools for Active Tab</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Tab</kbd></td>
<td>Switch to Next Organization</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>Switch to Previous Organization</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">History Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>Alt</kbd> + <kbd>←</kbd></td>
<td>Back</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>→</kbd></td>
<td>Forward</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Window Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>M</kbd></td>
<td>Minimize</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>W</kbd></td>
<td>Close</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
</div>
`;
}
openHotkeysExternalLink() {
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
const externalCreateNewOrgEl = document.getElementById('open-hotkeys-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
init() {
this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMac() : this.templateWinLin();
this.openHotkeysExternalLink();
}
}
module.exports = ShortcutsSection;

View File

@@ -1,345 +0,0 @@
'use strict';
import { shell } from 'electron';
import BaseSection = require('./base-section');
import t = require('../../utils/translation-util');
class ShortcutsSection extends BaseSection {
// TODO: TypeScript - Here props should be object type
props: any;
constructor(props: any) {
super();
this.props = props;
}
// TODO - Deduplicate templateMac and templateWinLin functions. In theory
// they both should be the same the only thing different should be the userOSKey
// variable but there seems to be inconsistences between both function, one has more
// lines though one may just be using more new lines and other thing is the use of +.
templateMac(): string {
const userOSKey = '⌘';
return `
<div class="settings-pane">
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>${t.__('Tip')}: </b>${t.__('These desktop app shortcuts extend the Zulip webapp\'s')} <span id="open-hotkeys-link"> ${t.__('keyboard shortcuts')}</span>.</p></div>
<div class="title">${t.__('Application Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>,</kbd></td>
<td>${t.__('Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
<td>${t.__('Keyboard Shortcuts')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>${t.__('Toggle Do Not Disturb')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
<td>${t.__('Reset App Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>L</kbd></td>
<td>${t.__('Log Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<td>${t.__('Hide Zulip')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<td>${t.__('Hide Others')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Q</kbd></td>
<td>${t.__('Quit Zulip')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('Edit Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td>${t.__('Undo')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td>${t.__('Redo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>X</kbd></td>
<td>${t.__('Cut')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>C</kbd></td>
<td>${t.__('Copy')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td>${t.__('Paste')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td>${t.__('Paste and Match Style')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td>${t.__('Select All')}</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<td>${t.__('Emoji & Symbols')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('View Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td>${t.__('Reload')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td>${t.__('Hard Reload')}</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
<td>${t.__('Enter Full Screen')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>+</kbd></td>
<td>${t.__('Zoom In')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>-</kbd></td>
<td>${t.__('Zoom Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>0</kbd></td>
<td>${t.__('Actual Size')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>${t.__('Toggle Sidebar')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>I</kbd></td>
<td>${t.__('Toggle DevTools for Zulip App')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>U</kbd></td>
<td>${t.__('Toggle DevTools for Active Tab')}</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Next Organization')}</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Previous Organization')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('History Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>←</kbd></td>
<td>${t.__('Back')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>→</kbd></td>
<td>${t.__('Forward')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Window Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>M</kbd></td>
<td>${t.__('Minimize')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>W</kbd></td>
<td>${t.__('Close')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
</div>
`;
}
templateWinLin(): string {
const userOSKey = 'Ctrl';
return `
<div class="settings-pane">
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>${t.__('Tip')}: </b>${t.__('These desktop app shortcuts extend the Zulip webapp\'s')} <span id="open-hotkeys-link"> ${t.__('keyboard shortcuts')}</span>.</p></div>
<div class="title">${t.__('Application Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>,</kbd></td>
<td>${t.__('Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
<td>${t.__('Keyboard Shortcuts')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>${t.__('Toggle Do Not Disturb')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
<td>${t.__('Log Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Q</kbd></td>
<td>${t.__('Quit Zulip')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('Edit Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Z</kbd></td>
<td>${t.__('Undo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Y</kbd></td>
<td>${t.__('Redo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>X</kbd></td>
<td>${t.__('Cut')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>C</kbd></td>
<td>${t.__('Copy')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste and Match Style')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>A</kbd></td>
<td>${t.__('Select All')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('View Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>R</kbd></td>
<td>${t.__('Reload')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd></td>
<td>${t.__('Hard Reload')}</td>
</tr>
<tr>
<td><kbd>F11</kbd></td>
<td>${t.__('Toggle Full Screen')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>=</kbd></td>
<td>${t.__('Zoom In')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>-</kbd></td>
<td>${t.__('Zoom Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>0</kbd></td>
<td>${t.__('Actual Size')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>${t.__('Toggle Sidebar')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td>
<td>${t.__('Toggle DevTools for Zulip App')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td>
<td>${t.__('Toggle DevTools for Active Tab')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Next Organization')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Previous Organization')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('History Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>Alt</kbd> + <kbd>←</kbd></td>
<td>${t.__('Back')}</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>→</kbd></td>
<td>${t.__('Forward')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('Window Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>M</kbd></td>
<td>${t.__('Minimize')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>W</kbd></td>
<td>${t.__('Close')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
</div>
`;
}
openHotkeysExternalLink(): void {
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
const externalCreateNewOrgEl = document.querySelector('#open-hotkeys-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
init(): void {
this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMac() : this.templateWinLin();
this.openHotkeysExternalLink();
}
}
export = ShortcutsSection;

105
app/renderer/js/preload.js Normal file
View File

@@ -0,0 +1,105 @@
'use strict';
const { ipcRenderer, shell } = require('electron');
const SetupSpellChecker = require('./spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const LinkUtil = require(__dirname + '/utils/link-util.js');
const params = require(__dirname + '/utils/params-util.js');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
// eslint-disable-next-line camelcase
window.electron_bridge = require('./electron-bridge');
const logout = () => {
// Create the menu for the below
document.querySelector('.dropdown-toggle').click();
const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
nodes[nodes.length - 1].click();
};
const shortcut = () => {
// Create the menu for the below
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
if (node.text.trim().toLowerCase() === 'keyboard shortcuts (?)') {
node.click();
} else {
// Atleast click the dropdown
document.querySelector('.dropdown-toggle').click();
}
};
process.once('loaded', () => {
global.logout = logout;
global.shortcut = shortcut;
});
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', () => {
if (params.isPageParams()) {
// Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (e) {
const url = $(this).attr('href');
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
// Open image in the default browser if image preview is unavailable
if (!$img[0]) {
shell.openExternal(window.location.origin + url);
}
// Open image in lightbox
lightbox.open($img);
}
});
}
});
// Clean up spellchecker events after you navigate away from this page;
// otherwise, you may experience errors
window.addEventListener('beforeunload', () => {
SetupSpellChecker.unsubscribeSpellChecker();
});
// electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
// Zoom from numpad keys is not supported by electron, so adding it through listeners.
document.addEventListener('keydown', event => {
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
} else if (event.ctrlKey && event.code === 'NumpadAdd') {
ipcRenderer.send('forward-message', 'zoomIn');
} else if (event.ctrlKey && event.code === 'NumpadSubtract') {
ipcRenderer.send('forward-message', 'zoomOut');
} else if (event.ctrlKey && event.code === 'Numpad0') {
ipcRenderer.send('forward-message', 'zoomActualSize');
}
});

View File

@@ -1,165 +0,0 @@
// we have and will have some non camelcase stuff
// while working with zulip and electron bridge
// so turning the rule off for the whole file.
/* eslint-disable @typescript-eslint/camelcase */
'use strict';
import { ipcRenderer, shell } from 'electron';
import SetupSpellChecker from './spellchecker';
import isDev = require('electron-is-dev');
import LinkUtil = require('./utils/link-util');
import params = require('./utils/params-util');
import NetworkError = require('./pages/network');
interface PatchedGlobal extends NodeJS.Global {
logout: () => void;
shortcut: () => void;
showNotificationSettings: () => void;
}
const globalPatched = global as PatchedGlobal;
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
declare let window: ZulipWebWindow;
window.electron_bridge = require('./electron-bridge');
const logout = (): void => {
// Create the menu for the below
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
dropdown.click();
const nodes: NodeListOf<HTMLElement> = document.querySelectorAll('.dropdown-menu li:last-child a');
nodes[nodes.length - 1].click();
};
const shortcut = (): void => {
// Create the menu for the below
const node: HTMLElement = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
if (node.textContent.trim().toLowerCase() === 'keyboard shortcuts (?)') {
node.click();
} else {
// Atleast click the dropdown
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
dropdown.click();
}
};
const showNotificationSettings = (): void => {
// Create the menu for the below
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
dropdown.click();
const nodes: NodeListOf<HTMLElement> = document.querySelectorAll('.dropdown-menu li a');
nodes[2].click();
const notificationItem: NodeListOf<HTMLElement> = document.querySelectorAll('.normal-settings-list li div');
// wait until the notification dom element shows up
setTimeout(() => {
notificationItem[2].click();
}, 100);
};
process.once('loaded', (): void => {
globalPatched.logout = logout;
globalPatched.shortcut = shortcut;
globalPatched.showNotificationSettings = showNotificationSettings;
});
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', (): void => {
if (params.isPageParams()) {
// Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef
if (serverLanguage) {
// Init spellchecker
SetupSpellChecker.init(serverLanguage);
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (this: HTMLElement, e: Event) {
const url = $(this).attr('href');
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
// Open image in the default browser if image preview is unavailable
if (!$img[0]) {
shell.openExternal(window.location.origin + url);
}
// Open image in lightbox
lightbox.open($img);
}
});
}
});
// Clean up spellchecker events after you navigate away from this page;
// otherwise, you may experience errors
window.addEventListener('beforeunload', (): void => {
SetupSpellChecker.unsubscribeSpellChecker();
});
window.addEventListener('load', (event: any): void => {
if (!event.target.URL.includes('app/renderer/network.html')) {
return;
}
const $reconnectButton = document.querySelector('#reconnect');
const $settingsButton = document.querySelector('#settings');
NetworkError.init($reconnectButton, $settingsButton);
});
// electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
// Zoom from numpad keys is not supported by electron, so adding it through listeners.
document.addEventListener('keydown', event => {
const cmdOrCtrl = event.ctrlKey || event.metaKey;
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
} else if (cmdOrCtrl && (event.code === 'NumpadAdd' || event.code === 'Equal')) {
ipcRenderer.send('forward-message', 'zoomIn');
} else if (cmdOrCtrl && event.code === 'NumpadSubtract') {
ipcRenderer.send('forward-message', 'zoomOut');
} else if (cmdOrCtrl && event.code === 'Numpad0') {
ipcRenderer.send('forward-message', 'zoomActualSize');
}
});
// Set user as active and update the time of last activity
ipcRenderer.on('set-active', () => {
if (isDev) {
console.log('active');
}
window.electron_bridge.idle_on_system = false;
window.electron_bridge.last_active_on_system = Date.now();
});
// Set user as idle and time of last activity is left unchanged
ipcRenderer.on('set-idle', () => {
if (isDev) {
console.log('idle');
}
window.electron_bridge.idle_on_system = true;
});

View File

@@ -5,7 +5,7 @@
// It doesn't affect the compose box so that users can still
// use drag and drop event to share files etc
const preventDragAndDrop = (): void => {
const preventDragAndDrop = () => {
const preventEvents = ['dragover', 'drop'];
preventEvents.forEach(dragEvents => {
document.addEventListener(dragEvents, event => {

View File

@@ -0,0 +1,55 @@
'use strict';
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const Logger = require(__dirname + '/utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class SetupSpellChecker {
init() {
if (ConfigUtil.getConfigItem('enableSpellchecker')) {
this.enableSpellChecker();
}
this.enableContextMenu();
}
enableSpellChecker() {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
logger.error(err);
}
}
enableContextMenu() {
if (this.SpellCheckHandler) {
this.SpellCheckHandler.attachToInput();
const userLanguage = ConfigUtil.getConfigItem('spellcheckerLanguage');
this.SpellCheckHandler.switchLanguage(userLanguage);
}
const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler);
this.contextMenuListener = new ContextMenuListener(info => {
contextMenuBuilder.showPopupMenu(info);
});
}
unsubscribeSpellChecker() {
// eslint-disable-next-line no-undef
if (this.SpellCheckHandler) {
this.SpellCheckHandler.unsubscribe();
}
if (this.contextMenuListener) {
this.contextMenuListener.unsubscribe();
}
}
}
module.exports = new SetupSpellChecker();

View File

@@ -1,56 +0,0 @@
'use strict';
import { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } from 'electron-spellchecker';
import ConfigUtil = require('./utils/config-util');
import Logger = require('./utils/logger-util');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class SetupSpellChecker {
SpellCheckHandler: typeof SpellCheckHandler;
contextMenuListener: typeof ContextMenuListener;
init(serverLanguage: string): void {
if (ConfigUtil.getConfigItem('enableSpellchecker')) {
this.enableSpellChecker();
}
this.enableContextMenu(serverLanguage);
}
enableSpellChecker(): void {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
logger.error(err);
}
}
enableContextMenu(serverLanguage: string): void {
if (this.SpellCheckHandler) {
this.SpellCheckHandler.attachToInput();
this.SpellCheckHandler.switchLanguage(serverLanguage);
this.SpellCheckHandler.currentSpellcheckerChanged.subscribe(() => {
this.SpellCheckHandler.switchLanguage(this.SpellCheckHandler.currentSpellcheckerLanguage);
});
}
const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler);
this.contextMenuListener = new ContextMenuListener((info: object) => {
contextMenuBuilder.showPopupMenu(info);
});
}
unsubscribeSpellChecker(): void {
if (this.SpellCheckHandler) {
this.SpellCheckHandler.unsubscribe();
}
if (this.contextMenuListener) {
this.contextMenuListener.unsubscribe();
}
}
}
export = new SetupSpellChecker();

View File

@@ -1,15 +1,17 @@
'use strict';
import { ipcRenderer, remote, WebviewTag, NativeImage } from 'electron';
const path = require('path');
const electron = require('electron');
const { ipcRenderer, remote } = electron;
import path = require('path');
import ConfigUtil = require('./utils/config-util.js');
const { Tray, Menu, nativeImage, BrowserWindow } = remote;
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
declare let window: ZulipWebWindow;
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const iconPath = (): string => {
const iconPath = () => {
if (process.platform === 'linux') {
return APP_ICON + 'linux.png';
}
@@ -18,7 +20,7 @@ const iconPath = (): string => {
let unread = 0;
const trayIconSize = (): number => {
const trayIconSize = () => {
switch (process.platform) {
case 'darwin':
return 20;
@@ -43,7 +45,7 @@ const config = {
thick: process.platform === 'win32'
};
const renderCanvas = function (arg: number): Promise<HTMLCanvasElement> {
const renderCanvas = function (arg) {
config.unreadCount = arg;
return new Promise(resolve => {
@@ -77,10 +79,10 @@ const renderCanvas = function (arg: number): Promise<HTMLCanvasElement> {
ctx.fillText('99+', CENTER, CENTER + (SIZE * 0.15));
} else if (config.unreadCount < 10) {
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
ctx.fillText(String(config.unreadCount), CENTER, CENTER + (SIZE * 0.20));
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.20));
} else {
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
ctx.fillText(String(config.unreadCount), CENTER, CENTER + (SIZE * 0.15));
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.15));
}
resolve(canvas);
@@ -92,23 +94,18 @@ const renderCanvas = function (arg: number): Promise<HTMLCanvasElement> {
* @param arg: Unread count
* @return the native image
*/
const renderNativeImage = function (arg: number): Promise<NativeImage> {
const renderNativeImage = function (arg) {
return Promise.resolve()
.then(() => renderCanvas(arg))
.then(canvas => {
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPNG();
// TODO: Fix the function to correctly use Promise correctly.
// the Promise.resolve().then(...) above is useless we should
// start with renderCanvas(arg).then
// eslint-disable-next-line promise/no-return-wrap
return Promise.resolve(nativeImage.createFromBuffer(pngData, {
scaleFactor: config.pixelRatio
}));
});
};
function sendAction(action: string): void {
function sendAction(action) {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
@@ -118,7 +115,7 @@ function sendAction(action: string): void {
win.webContents.send(action);
}
const createTray = function (): void {
const createTray = function () {
window.tray = new Tray(iconPath());
const contextMenu = Menu.buildFromTemplate([
{
@@ -152,9 +149,9 @@ const createTray = function (): void {
}
};
ipcRenderer.on('destroytray', (event: Event): Event => {
ipcRenderer.on('destroytray', event => {
if (!window.tray) {
return undefined;
return;
}
window.tray.destroy();
@@ -167,7 +164,7 @@ ipcRenderer.on('destroytray', (event: Event): Event => {
return event;
});
ipcRenderer.on('tray', (_event: Event, arg: number): void => {
ipcRenderer.on('tray', (event, arg) => {
if (!window.tray) {
return;
}
@@ -187,7 +184,7 @@ ipcRenderer.on('tray', (_event: Event, arg: number): void => {
}
});
function toggleTray(): void {
function toggleTray() {
let state;
if (window.tray) {
state = false;
@@ -208,7 +205,7 @@ function toggleTray(): void {
ConfigUtil.setConfigItem('trayIcon', true);
}
const selector = 'webview:not([class*=disabled])';
const webview: WebviewTag = document.querySelector(selector);
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggletray', state);
}

View File

@@ -1,14 +1,11 @@
'use strict';
import { remote } from 'electron';
import JsonDB from 'node-json-db';
import { initSetUp } from './default-util';
import fs = require('fs');
import path = require('path');
import Logger = require('./logger-util');
const { app, dialog } = remote;
const { app, dialog } = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const { initSetUp } = require('./default-util');
initSetUp();
@@ -17,12 +14,10 @@ const logger = new Logger({
timestamp: true
});
let instance: null | CertificateUtil = null;
let instance = null;
const certificatesDir = `${app.getPath('userData')}/certificates`;
class CertificateUtil {
db: JsonDB;
constructor() {
if (instance) {
return instance;
@@ -33,8 +28,7 @@ class CertificateUtil {
this.reloadDB();
return instance;
}
getCertificate(server: string, defaultValue: any = null): any {
getCertificate(server, defaultValue = null) {
this.reloadDB();
const value = this.db.getData('/')[server];
if (value === undefined) {
@@ -43,9 +37,8 @@ class CertificateUtil {
return value;
}
}
// Function to copy the certificate to userData folder
copyCertificate(_server: string, location: string, fileName: string): boolean {
copyCertificate(server, location, fileName) {
let copied = false;
const filePath = `${certificatesDir}/${fileName}`;
try {
@@ -61,19 +54,16 @@ class CertificateUtil {
}
return copied;
}
setCertificate(server: string, fileName: string): void {
const filePath = `${fileName}`;
setCertificate(server, fileName) {
const filePath = `${certificatesDir}/${fileName}`;
this.db.push(`/${server}`, filePath, true);
this.reloadDB();
}
removeCertificate(server: string): void {
removeCertificate(server) {
this.db.delete(`/${server}`);
this.reloadDB();
}
reloadDB(): void {
reloadDB() {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/certificates.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
@@ -93,4 +83,4 @@ class CertificateUtil {
}
}
export = new CertificateUtil();
module.exports = new CertificateUtil();

View File

@@ -1,6 +1,6 @@
'use strict';
let instance: null | CommonUtil = null;
let instance = null;
class CommonUtil {
constructor() {
@@ -13,13 +13,13 @@ class CommonUtil {
}
// unescape already encoded/escaped strings
decodeString(stringInput: string): string {
decodeString(string) {
const parser = new DOMParser();
const dom = parser.parseFromString(
'<!doctype html><body>' + stringInput,
'<!doctype html><body>' + string,
'text/html');
return dom.body.textContent;
}
}
export = new CommonUtil();
module.exports = new CommonUtil();

View File

@@ -1,34 +1,32 @@
'use strict';
import JsonDB from 'node-json-db';
import fs = require('fs');
import path = require('path');
import electron = require('electron');
import Logger = require('./logger-util');
import EnterpriseUtil = require('./enterprise-util');
const fs = require('fs');
const path = require('path');
const process = require('process');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const logger = new Logger({
file: 'config-util.log',
timestamp: true
});
let instance: null | ConfigUtil = null;
let dialog: Electron.Dialog = null;
let app: Electron.App = null;
let instance = null;
let dialog = null;
let app = null;
/* To make the util runnable in both main and renderer process */
if (process.type === 'renderer') {
const { remote } = electron;
const remote = require('electron').remote;
dialog = remote.dialog;
app = remote.app;
} else {
const electron = require('electron');
dialog = electron.dialog;
app = electron.app;
}
class ConfigUtil {
db: JsonDB;
constructor() {
if (instance) {
return instance;
@@ -40,7 +38,7 @@ class ConfigUtil {
return instance;
}
getConfigItem(key: string, defaultValue: any = null): any {
getConfigItem(key, defaultValue = null) {
try {
this.db.reload();
} catch (err) {
@@ -55,9 +53,8 @@ class ConfigUtil {
return value;
}
}
// This function returns whether a key exists in the configuration file (settings.json)
isConfigItemExists(key: string): boolean {
isConfigItemExists(key) {
try {
this.db.reload();
} catch (err) {
@@ -68,21 +65,17 @@ class ConfigUtil {
return (value !== undefined);
}
setConfigItem(key: string, value: any, override? : boolean): void {
if (EnterpriseUtil.configItemExists(key) && !override) {
// if item is in global config and we're not trying to override
return;
}
setConfigItem(key, value) {
this.db.push(`/${key}`, value, true);
this.db.save();
}
removeConfigItem(key: string): void {
removeConfigItem(key) {
this.db.delete(`/${key}`);
this.db.save();
}
reloadDB(): void {
reloadDB() {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/settings.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
@@ -103,4 +96,4 @@ class ConfigUtil {
}
}
export = new ConfigUtil();
module.exports = new ConfigUtil();

View File

@@ -1,6 +1,6 @@
import fs = require('fs');
const fs = require('fs');
let app: Electron.App = null;
let app = null;
let setupCompleted = false;
if (process.type === 'renderer') {
app = require('electron').remote.app;
@@ -12,7 +12,7 @@ const zulipDir = app.getPath('userData');
const logDir = `${zulipDir}/Logs/`;
const certificatesDir = `${zulipDir}/certificates/`;
const configDir = `${zulipDir}/config/`;
export const initSetUp = (): void => {
const initSetUp = () => {
// if it is the first time the app is running
// create zulip dir in userData folder to
// avoid errors
@@ -72,3 +72,7 @@ export const initSetUp = (): void => {
setupCompleted = true;
}
};
module.exports = {
initSetUp
};

View File

@@ -1,30 +1,17 @@
'use strict';
import ConfigUtil = require('./config-util');
const ConfigUtil = require(__dirname + '/config-util.js');
// TODO: TypeScript - add to Setting interface
// the list of settings since we have fixed amount of them
// We want to do this by creating a new module that exports
// this interface
interface Setting {
[key: string]: any;
}
interface Toggle {
dnd: boolean;
newSettings: Setting;
}
export function toggle(): Toggle {
function toggle() {
const dnd = !ConfigUtil.getConfigItem('dnd', false);
const dndSettingList = ['showNotification', 'silent'];
if (process.platform === 'win32') {
dndSettingList.push('flashTaskbarOnMessage');
}
let newSettings: Setting;
let newSettings;
if (dnd) {
const oldSettings: Setting = {};
const oldSettings = {};
newSettings = {};
// Iterate through the dndSettingList.
@@ -48,3 +35,7 @@ export function toggle(): Toggle {
ConfigUtil.setConfigItem('dnd', dnd);
return {dnd, newSettings};
}
module.exports = {
toggle
};

View File

@@ -1,31 +1,27 @@
'use strict';
import JsonDB from 'node-json-db';
import escape = require('escape-html');
import request = require('request');
import fs = require('fs');
import path = require('path');
import Logger = require('./logger-util');
import electron = require('electron');
const { app, dialog } = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
const escape = require('escape-html');
import RequestUtil = require('./request-util');
import EnterpriseUtil = require('./enterprise-util');
import Messages = require('../../../resources/messages');
const Logger = require('./logger-util');
const { ipcRenderer } = electron;
const { app, dialog } = electron.remote;
const RequestUtil = require(__dirname + '/../utils/request-util.js');
const Messages = require(__dirname + '/../../../resources/messages.js');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
let instance: null | DomainUtil = null;
let instance = null;
const defaultIconUrl = '../renderer/img/icon.png';
class DomainUtil {
db: JsonDB;
constructor() {
if (instance) {
return instance;
@@ -46,7 +42,7 @@ class DomainUtil {
return instance;
}
getDomains(): any {
getDomains() {
this.reloadDB();
if (this.db.getData('/').domains === undefined) {
return [];
@@ -55,28 +51,18 @@ class DomainUtil {
}
}
getDomain(index: number): any {
getDomain(index) {
this.reloadDB();
return this.db.getData(`/domains[${index}]`);
}
shouldIgnoreCerts(url: string): boolean {
const domains = this.getDomains();
for (const domain of domains) {
if (domain.url === url) {
return domain.ignoreCerts;
}
}
return null;
}
updateDomain(index: number, server: object): void {
updateDomain(index, server) {
this.reloadDB();
this.db.push(`/domains[${index}]`, server, true);
}
addDomain(server: any): Promise<void> {
const { ignoreCerts } = server;
addDomain(server) {
const ignoreCerts = server.ignoreCerts;
return new Promise(resolve => {
if (server.icon) {
this.saveServerIcon(server, ignoreCerts).then(localIconUrl => {
@@ -94,22 +80,18 @@ class DomainUtil {
});
}
removeDomains(): void {
removeDomains() {
this.db.delete('/domains');
this.reloadDB();
}
removeDomain(index: number): boolean {
if (EnterpriseUtil.isPresetOrg(this.getDomain(index).url)) {
return false;
}
removeDomain(index) {
this.db.delete(`/domains[${index}]`);
this.reloadDB();
return true;
}
// Check if domain is already added
duplicateDomain(domain: any): boolean {
duplicateDomain(domain) {
domain = this.formatUrl(domain);
const servers = this.getDomains();
for (const i in servers) {
@@ -120,30 +102,30 @@ class DomainUtil {
return false;
}
async checkCertError(domain: any, serverConf: any, error: string, silent: boolean): Promise<string | object> {
async checkCertError(domain, serverConf, error, silent) {
if (silent) {
// since getting server settings has already failed
return serverConf;
} else {
// Report error to sentry to get idea of possible certificate errors
// users get when adding the servers
logger.reportSentry(new Error(error).toString());
logger.reportSentry(new Error(error));
const certErrorMessage = Messages.certErrorMessage(domain, error);
const certErrorDetail = Messages.certErrorDetail();
const response = await (dialog.showMessageBox({
const response = await dialog.showMessageBox({
type: 'warning',
buttons: ['Yes', 'No'],
defaultId: 1,
message: certErrorMessage,
detail: certErrorDetail
}) as any); // TODO: TypeScript - Figure this out
});
if (response === 0) {
// set ignoreCerts parameter to true in case user responds with yes
serverConf.ignoreCerts = true;
try {
return await this.getServerSettings(domain, serverConf.ignoreCerts);
} catch (_) {
} catch (err) {
if (error === Messages.noOrgsError(domain)) {
throw new Error(error);
}
@@ -157,7 +139,7 @@ class DomainUtil {
// ignoreCerts parameter helps in fetching server icon and
// other server details when user chooses to ignore certificate warnings
async checkDomain(domain: any, ignoreCerts = false, silent = false): Promise<any> {
async checkDomain(domain, ignoreCerts = false, silent = false) {
if (!silent && this.duplicateDomain(domain)) {
// Do not check duplicate in silent mode
throw new Error('This server has been added.');
@@ -186,21 +168,25 @@ class DomainUtil {
const certsError = error.toString().includes('certificate');
if (domain.indexOf(whitelistDomains) >= 0 || certsError) {
return this.checkCertError(domain, serverConf, error, silent);
try {
return await this.checkCertError(domain, serverConf, error, silent);
} catch (err) {
throw err;
}
} else {
throw Messages.invalidZulipServerError(domain);
}
}
}
getServerSettings(domain: any, ignoreCerts = false): Promise<object | string> {
getServerSettings(domain, ignoreCerts = false) {
const serverSettingsOptions = {
url: domain + '/api/v1/server_settings',
...RequestUtil.requestOptions(domain, ignoreCerts)
};
return new Promise((resolve, reject) => {
request(serverSettingsOptions, (error: string, response: any) => {
request(serverSettingsOptions, (error, response) => {
if (!error && response.statusCode === 200) {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
@@ -222,7 +208,7 @@ class DomainUtil {
});
}
saveServerIcon(server: any, ignoreCerts = false): Promise<string> {
saveServerIcon(server, ignoreCerts = false) {
const url = server.icon;
const domain = server.url;
@@ -236,8 +222,8 @@ class DomainUtil {
const filePath = this.generateFilePath(url);
const file = fs.createWriteStream(filePath);
try {
request(serverIconOptions).on('response', (response: any) => {
response.on('error', (err: string) => {
request(serverIconOptions).on('response', response => {
response.on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
@@ -246,7 +232,7 @@ class DomainUtil {
response.pipe(file).on('finish', () => {
resolve(filePath);
});
}).on('error', (err: string) => {
}).on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
@@ -261,24 +247,19 @@ class DomainUtil {
});
}
async updateSavedServer(url: string, index: number): Promise<void> {
updateSavedServer(url, index) {
// Does not promise successful update
const oldIcon = this.getDomain(index).icon;
const { ignoreCerts } = this.getDomain(index);
try {
const newServerConf = await this.checkDomain(url, ignoreCerts, true);
const localIconUrl = await this.saveServerIcon(newServerConf, ignoreCerts);
if (!oldIcon || localIconUrl !== '../renderer/img/icon.png') {
const ignoreCerts = this.getDomain(index).ignoreCerts;
this.checkDomain(url, ignoreCerts, true).then(newServerConf => {
this.saveServerIcon(newServerConf, ignoreCerts).then(localIconUrl => {
newServerConf.icon = localIconUrl;
this.updateDomain(index, newServerConf);
this.reloadDB();
}
} catch (err) {
ipcRenderer.send('forward-message', 'show-network-error', index);
}
});
});
}
reloadDB(): void {
reloadDB() {
const domainJsonPath = path.join(app.getPath('userData'), 'config/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
@@ -299,7 +280,7 @@ class DomainUtil {
this.db = new JsonDB(domainJsonPath, true, true);
}
generateFilePath(url: string): string {
generateFilePath(url) {
const dir = `${app.getPath('userData')}/server-icons`;
const extension = path.extname(url).split('?')[0];
@@ -318,7 +299,7 @@ class DomainUtil {
return `${dir}/${hash >>> 0}${extension}`;
}
formatUrl(domain: any): string {
formatUrl(domain) {
const hasPrefix = (domain.indexOf('http') === 0);
if (hasPrefix) {
return domain;
@@ -328,4 +309,4 @@ class DomainUtil {
}
}
export = new DomainUtil();
module.exports = new DomainUtil();

View File

@@ -1,80 +0,0 @@
import fs = require('fs');
import path = require('path');
import Logger = require('./logger-util');
const logger = new Logger({
file: 'enterprise-util.log',
timestamp: true
});
let instance: null | EnterpriseUtil = null;
class EnterpriseUtil {
// todo: replace enterpriseSettings type with an interface once settings are final
enterpriseSettings: any;
configFile: boolean;
constructor() {
if (instance) {
return instance;
}
instance = this;
this.reloadDB();
}
reloadDB(): void {
let enterpriseFile = '/etc/zulip-desktop-config/global_config.json';
if (process.platform === 'win32') {
enterpriseFile = 'C:\\Program Files\\Zulip-Desktop-Config\\global_config.json';
}
enterpriseFile = path.resolve(enterpriseFile);
if (fs.existsSync(enterpriseFile)) {
this.configFile = true;
try {
const file = fs.readFileSync(enterpriseFile, 'utf8');
this.enterpriseSettings = JSON.parse(file);
} catch (err) {
logger.log('Error while JSON parsing global_config.json: ');
logger.log(err);
}
} else {
this.configFile = false;
}
}
getConfigItem(key: string, defaultValue?: any): any {
this.reloadDB();
if (!this.configFile) {
return defaultValue;
}
if (defaultValue === undefined) {
defaultValue = null;
}
return this.configItemExists(key) ? this.enterpriseSettings[key] : defaultValue;
}
configItemExists(key: string): boolean {
this.reloadDB();
if (!this.configFile) {
return false;
}
return (this.enterpriseSettings[key] !== undefined);
}
isPresetOrg(url: string): boolean {
if (!this.configFile || !this.configItemExists('presetOrganizations')) {
return false;
}
const presetOrgs = this.enterpriseSettings.presetOrganizations;
for (const org of presetOrgs) {
if (url.includes(org)) {
return true;
}
}
return false;
}
}
export = new EnterpriseUtil();

View File

@@ -1,14 +1,8 @@
'use strict';
// TODO: TypeScript - Add @types/
import wurl = require('wurl');
const wurl = require('wurl');
let instance: null | LinkUtil = null;
interface IsInternalResponse {
isInternalUrl: boolean;
isUploadsUrl: boolean;
}
let instance = null;
class LinkUtil {
constructor() {
@@ -21,7 +15,7 @@ class LinkUtil {
return instance;
}
isInternal(currentUrl: string, newUrl: string): IsInternalResponse {
isInternal(currentUrl, newUrl) {
const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl);
@@ -35,17 +29,17 @@ class LinkUtil {
};
}
isImage(url: string): boolean {
isImage(url) {
// test for images extension as well as urls like .png?s=100
const isImageUrl = /\.(bmp|gif|jpg|jpeg|png|webp)\?*.*$/i;
return isImageUrl.test(url);
}
isPDF(url: string): boolean {
isPDF(url) {
// test for pdf extension
const isPDFUrl = /\.(pdf)\?*.*$/i;
return isPDFUrl.test(url);
}
}
export = new LinkUtil();
module.exports = new LinkUtil();

View File

@@ -1,13 +1,12 @@
'use strict';
import JsonDB from 'node-json-db';
import fs = require('fs');
import path = require('path');
import electron = require('electron');
import Logger = require('./logger-util');
const fs = require('fs');
const path = require('path');
const process = require('process');
const remote =
process.type === 'renderer' ? electron.remote : electron;
process.type === 'renderer' ? require('electron').remote : require('electron');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const logger = new Logger({
file: 'linux-update-util.log',
@@ -16,11 +15,10 @@ const logger = new Logger({
/* To make the util runnable in both main and renderer process */
const { dialog, app } = remote;
let instance: null | LinuxUpdateUtil = null;
let instance = null;
class LinuxUpdateUtil {
db: JsonDB;
constructor() {
if (instance) {
return instance;
@@ -32,7 +30,7 @@ class LinuxUpdateUtil {
return instance;
}
getUpdateItem(key: string, defaultValue: any = null): any {
getUpdateItem(key, defaultValue = null) {
this.reloadDB();
const value = this.db.getData('/')[key];
if (value === undefined) {
@@ -43,17 +41,17 @@ class LinuxUpdateUtil {
}
}
setUpdateItem(key: string, value: any): void {
setUpdateItem(key, value) {
this.db.push(`/${key}`, value, true);
this.reloadDB();
}
removeUpdateItem(key: string): void {
removeUpdateItem(key) {
this.db.delete(`/${key}`);
this.reloadDB();
}
reloadDB(): void {
reloadDB() {
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/config/updates.json');
try {
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
@@ -73,4 +71,4 @@ class LinuxUpdateUtil {
}
}
export = new LinuxUpdateUtil();
module.exports = new LinuxUpdateUtil();

View File

@@ -1,57 +1,37 @@
import { Console as NodeConsole } from 'console'; // eslint-disable-line node/prefer-global/console
import { initSetUp } from './default-util';
import { sentryInit, captureException } from './sentry-util';
import fs = require('fs');
import os = require('os');
import isDev = require('electron-is-dev');
import electron = require('electron');
// this interface adds [key: string]: any so
// we can do console[type] later on in the code
interface PatchedConsole extends Console {
[key: string]: any;
}
interface LoggerOptions {
timestamp?: any;
file?: string;
level?: boolean;
logInDevMode?: boolean;
}
const NodeConsole = require('console').Console;
const fs = require('fs');
const os = require('os');
const isDev = require('electron-is-dev');
const { initSetUp } = require('./default-util');
const { sentryInit, captureException } = require('./sentry-util');
initSetUp();
let app: Electron.App = null;
let app = null;
let reportErrors = true;
if (process.type === 'renderer') {
app = electron.remote.app;
app = require('electron').remote.app;
// Report Errors to Sentry only if it is enabled in settings
// Gets the value of reportErrors from config-util for renderer process
// For main process, sentryInit() is handled in index.js
const { ipcRenderer } = electron;
const { ipcRenderer } = require('electron');
ipcRenderer.send('error-reporting');
ipcRenderer.on('error-reporting-val', (_event: any, errorReporting: boolean) => {
ipcRenderer.on('error-reporting-val', (event, errorReporting) => {
reportErrors = errorReporting;
if (reportErrors) {
sentryInit();
}
});
} else {
app = electron.app;
app = require('electron').app;
}
const browserConsole: PatchedConsole = console;
const browserConsole = console;
const logDir = `${app.getPath('userData')}/Logs`;
class Logger {
nodeConsole: PatchedConsole;
timestamp: any; // TODO: TypeScript - Figure out how to make this work with string | Function.
level: boolean;
logInDevMode: boolean;
[key: string]: any;
constructor(opts: LoggerOptions = {}) {
constructor(opts = {}) {
let {
timestamp = true,
file = 'console.log',
@@ -81,7 +61,7 @@ class Logger {
this.setUpConsole();
}
_log(type: string, ...args: any[]): void {
_log(type, ...args) {
const {
nodeConsole, timestamp, level, logInDevMode
} = this;
@@ -97,7 +77,7 @@ class Logger {
case isDev || logInDevMode:
nodeConsoleLog = nodeConsole[type] || nodeConsole.log;
nodeConsoleLog.apply(null, args); // eslint-disable-line prefer-spread
nodeConsoleLog.apply(null, args);
default: break;
}
@@ -106,20 +86,19 @@ class Logger {
browserConsole[type].apply(null, args);
}
setUpConsole(): void {
setUpConsole() {
for (const type in browserConsole) {
this.setupConsoleMethod(type);
}
}
setupConsoleMethod(type: string): void {
this[type] = (...args: any[]) => {
const log = this._log.bind(this, type, ...args);
log();
setupConsoleMethod(type) {
this[type] = (...args) => {
this._log(type, ...args);
};
}
getTimestamp(): string {
getTimestamp() {
const date = new Date();
const timestamp =
`${date.getMonth()}/${date.getDate()} ` +
@@ -127,13 +106,13 @@ class Logger {
return timestamp;
}
reportSentry(err: string): void {
reportSentry(err) {
if (reportErrors) {
captureException(err);
}
}
trimLog(file: string): void{
trimLog(file) {
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
throw err;
@@ -152,4 +131,4 @@ class Logger {
}
}
export = Logger;
module.exports = Logger;

View File

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

View File

@@ -1,15 +1,9 @@
'use strict';
import url = require('url');
const url = require('url');
const ConfigUtil = require('./config-util.js');
import ConfigUtil = require('./config-util');
let instance: null | ProxyUtil = null;
interface ProxyRule {
hostname?: string;
port?: number;
}
let instance = null;
class ProxyUtil {
constructor() {
@@ -23,13 +17,8 @@ class ProxyUtil {
}
// Return proxy to be used for a particular uri, to be used for request
getProxy(_uri: string): ProxyRule | void {
const parsedUri = url.parse(_uri);
if (parsedUri === null) {
return;
}
const uri = parsedUri;
getProxy(uri) {
uri = url.parse(uri);
const proxyRules = ConfigUtil.getConfigItem('proxyRules', '').split(';');
// If SPS is on and system uses no proxy then request should not try to use proxy from
// environment. NO_PROXY = '*' makes request ignore all environment proxy variables.
@@ -38,9 +27,9 @@ class ProxyUtil {
return;
}
const proxyRule: any = {};
const proxyRule = {};
if (uri.protocol === 'http:') {
proxyRules.forEach((proxy: string) => {
proxyRules.forEach(proxy => {
if (proxy.includes('http=')) {
proxyRule.hostname = proxy.split('http=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('http=')[1].trim().split(':')[1];
@@ -50,7 +39,7 @@ class ProxyUtil {
}
if (uri.protocol === 'https:') {
proxyRules.forEach((proxy: string) => {
proxyRules.forEach(proxy => {
if (proxy.includes('https=')) {
proxyRule.hostname = proxy.split('https=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('https=')[1].trim().split(':')[1];
@@ -60,15 +49,14 @@ class ProxyUtil {
}
}
// TODO: Refactor to async function
resolveSystemProxy(mainWindow: Electron.BrowserWindow): void {
resolveSystemProxy(mainWindow) {
const page = mainWindow.webContents;
const ses = page.session;
const resolveProxyUrl = 'www.example.com';
// Check HTTP Proxy
const httpProxy = new Promise(resolve => {
ses.resolveProxy('http://' + resolveProxyUrl, (proxy: string) => {
ses.resolveProxy('http://' + resolveProxyUrl, proxy => {
let httpString = '';
if (proxy !== 'DIRECT') {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
@@ -82,12 +70,12 @@ class ProxyUtil {
});
// Check HTTPS Proxy
const httpsProxy = new Promise(resolve => {
ses.resolveProxy('https://' + resolveProxyUrl, (proxy: string) => {
ses.resolveProxy('https://' + resolveProxyUrl, proxy => {
let httpsString = '';
if (proxy !== 'DIRECT' || proxy.includes('HTTPS')) {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
// for all other HTTP or direct url:port both uses PROXY
if (proxy.includes('PROXY') || proxy.includes('HTTPS')) {
if (proxy.includes('PROXY' || proxy.includes('HTTPS'))) {
httpsString += 'https=' + proxy.split('PROXY')[1] + ';';
}
}
@@ -97,7 +85,7 @@ class ProxyUtil {
// Check FTP Proxy
const ftpProxy = new Promise(resolve => {
ses.resolveProxy('ftp://' + resolveProxyUrl, (proxy: string) => {
ses.resolveProxy('ftp://' + resolveProxyUrl, proxy => {
let ftpString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('PROXY')) {
@@ -110,7 +98,7 @@ class ProxyUtil {
// Check SOCKS Proxy
const socksProxy = new Promise(resolve => {
ses.resolveProxy('socks4://' + resolveProxyUrl, (proxy: string) => {
ses.resolveProxy('socks4://' + resolveProxyUrl, proxy => {
let socksString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('SOCKS5')) {
@@ -139,4 +127,4 @@ class ProxyUtil {
}
}
export = new ProxyUtil();
module.exports = new ProxyUtil();

View File

@@ -0,0 +1,60 @@
const isOnline = require('is-online');
const Logger = require('./logger-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
class ReconnectUtil {
constructor(serverManagerView) {
this.serverManagerView = serverManagerView;
this.alreadyReloaded = false;
}
clearState() {
this.alreadyReloaded = false;
}
pollInternetAndReload() {
const pollInterval = setInterval(() => {
this._checkAndReload()
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
}, 1500);
}
_checkAndReload() {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
isOnline()
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
logger.log('You\'re back online.');
return resolve(true);
}
logger.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = `
<div>Your internet connection doesn't seem to work properly!</div>
<div>Verify that it works and then click try again.</div>`;
}
return resolve(false);
});
} else {
return resolve(true);
}
});
}
}
module.exports = ReconnectUtil;

View File

@@ -1,102 +0,0 @@
import { ipcRenderer } from 'electron';
import backoff = require('backoff');
import request = require('request');
import Logger = require('./logger-util');
import RequestUtil = require('./request-util');
import DomainUtil = require('./domain-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
class ReconnectUtil {
// TODO: TypeScript - Figure out how to annotate webview
// it should be WebView; maybe make it a generic so we can
// pass the class from main.ts
webview: any;
url: string;
alreadyReloaded: boolean;
fibonacciBackoff: any;
constructor(webview: any) {
this.webview = webview;
this.url = webview.props.url;
this.alreadyReloaded = false;
this.clearState();
}
clearState(): void {
this.fibonacciBackoff = backoff.fibonacci({
initialDelay: 5000,
maxDelay: 300000
});
}
isOnline(): Promise<boolean> {
return new Promise(resolve => {
try {
const ignoreCerts = DomainUtil.shouldIgnoreCerts(this.url);
if (ignoreCerts === null) {
return;
}
request(
{
url: `${this.url}/static/favicon.ico`,
...RequestUtil.requestOptions(this.url, ignoreCerts)
},
(error: Error, response: any) => {
const isValidResponse =
!error && response.statusCode >= 200 && response.statusCode < 400;
resolve(isValidResponse);
}
);
} catch (err) {
logger.log(err);
}
});
}
pollInternetAndReload(): void {
this.fibonacciBackoff.backoff();
this.fibonacciBackoff.on('ready', () => {
this._checkAndReload().then(status => {
if (status) {
this.fibonacciBackoff.reset();
} else {
this.fibonacciBackoff.backoff();
}
});
});
}
// TODO: Make this a async function
_checkAndReload(): Promise<boolean> {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
this.isOnline()
.then((online: boolean) => {
if (online) {
ipcRenderer.send('forward-message', 'reload-viewer');
logger.log('You\'re back online.');
return resolve(true);
}
logger.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = `
<div>Your internet connection doesn't seem to work properly!</div>
<div>Verify that it works and then click try again.</div>`;
}
return resolve(false);
});
} else {
return resolve(true);
}
});
}
}
export = ReconnectUtil;

View File

@@ -0,0 +1,62 @@
const fs = require('fs');
const Logger = require('./logger-util');
const CertificateUtil = require(__dirname + '/certificate-util.js');
const ProxyUtil = require(__dirname + '/proxy-util.js');
const ConfigUtil = require(__dirname + '/config-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const logger = new Logger({
file: `request-util.log`,
timestamp: true
});
let instance = null;
class RequestUtil {
constructor() {
if (!instance) {
instance = this;
}
return instance;
}
// ignoreCerts parameter helps in fetching server icon and
// other server details when user chooses to ignore certificate warnings
requestOptions(domain, ignoreCerts) {
domain = this.formatUrl(domain);
const certificate = CertificateUtil.getCertificate(
encodeURIComponent(domain)
);
let certificateLocation = '';
if (certificate) {
// To handle case where certificate has been moved from the location in certificates.json
try {
certificateLocation = fs.readFileSync(certificate);
} catch (err) {
logger.warn('Error while trying to get certificate: ' + err);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
// Add proxy as a parameter if it is being used.
return {
ca: certificateLocation ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : '',
ecdhCurve: 'auto',
headers: { 'User-Agent': SystemUtil.getUserAgent() },
rejectUnauthorized: !ignoreCerts
};
}
formatUrl(domain) {
const hasPrefix = (domain.indexOf('http') === 0);
if (hasPrefix) {
return domain;
} else {
return (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
}
}
}
module.exports = new RequestUtil();

View File

@@ -1,87 +0,0 @@
import { remote } from 'electron';
import fs = require('fs');
import path = require('path');
import ConfigUtil = require('./config-util');
import Logger = require('./logger-util');
import ProxyUtil = require('./proxy-util');
import CertificateUtil = require('./certificate-util');
import SystemUtil = require('./system-util');
const { app } = remote;
const logger = new Logger({
file: `request-util.log`,
timestamp: true
});
let instance: null | RequestUtil = null;
// TODO: TypeScript - Use ProxyRule for the proxy property
// we can do this now since we use export = ProxyUtil syntax
interface RequestUtilResponse {
ca: string;
proxy: string | void | object;
ecdhCurve: 'auto';
headers: { 'User-Agent': string };
rejectUnauthorized: boolean;
}
class RequestUtil {
constructor() {
if (!instance) {
instance = this;
}
return instance;
}
// ignoreCerts parameter helps in fetching server icon and
// other server details when user chooses to ignore certificate warnings
requestOptions(domain: string, ignoreCerts: boolean): RequestUtilResponse {
domain = this.formatUrl(domain);
const certificate = CertificateUtil.getCertificate(
encodeURIComponent(domain)
);
let certificateFile = null;
if (certificate && 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 (err) {
logger.warn(`Error while trying to get certificate: ${err}`);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
// Add proxy as a parameter if it is being used.
return {
ca: certificateLocation ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : '',
ecdhCurve: 'auto',
headers: { 'User-Agent': SystemUtil.getUserAgent() },
rejectUnauthorized: !ignoreCerts
};
}
formatUrl(domain: string): string {
const hasPrefix = domain.startsWith('http', 0);
if (hasPrefix) {
return domain;
} else {
return domain.includes('localhost:') ? `http://${domain}` : `https://${domain}`;
}
}
}
const requestUtil = new RequestUtil();
export = requestUtil;

View File

@@ -0,0 +1,20 @@
const { init, captureException } = require('@sentry/electron');
const isDev = require('electron-is-dev');
const sentryInit = () => {
if (!isDev) {
init({
dsn: 'SENTRY_DSN',
// We should ignore this error since it's harmless and we know the reason behind this
// This error mainly comes from the console logs.
// This is a temp solution until Sentry supports disabling the console logs
ignoreErrors: ['does not appear to be a valid Zulip server'],
sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
});
}
};
module.exports = {
sentryInit,
captureException
};

View File

@@ -1,21 +0,0 @@
import { init } from '@sentry/electron';
import isDev = require('electron-is-dev');
import path = require('path');
import dotenv = require('dotenv');
dotenv.config({ path: path.resolve(__dirname, '/../../../../.env') });
export const sentryInit = (): void => {
if (!isDev) {
init({
dsn: process.env.SENTRY_DSN,
// We should ignore this error since it's harmless and we know the reason behind this
// This error mainly comes from the console logs.
// This is a temp solution until Sentry supports disabling the console logs
ignoreErrors: ['does not appear to be a valid Zulip server']
// sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
});
}
};
export { captureException } from '@sentry/electron';

View File

@@ -0,0 +1,55 @@
'use strict';
const {app} = require('electron').remote;
const os = require('os');
let instance = null;
class SystemUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.connectivityERR = [
'ERR_INTERNET_DISCONNECTED',
'ERR_PROXY_CONNECTION_FAILED',
'ERR_CONNECTION_RESET',
'ERR_NOT_CONNECTED',
'ERR_NAME_NOT_RESOLVED',
'ERR_NETWORK_CHANGED'
];
this.userAgent = null;
return instance;
}
getOS() {
if (os.platform() === 'darwin') {
return 'Mac';
}
if (os.platform() === 'linux') {
return 'Linux';
}
if (os.platform() === 'win32' || os.platform() === 'win64') {
if (parseFloat(os.release()) < 6.2) {
return 'Windows 7';
} else {
return 'Windows 10';
}
}
}
setUserAgent(webViewUserAgent) {
this.userAgent = 'ZulipElectron/' + app.getVersion() + ' ' + webViewUserAgent;
}
getUserAgent() {
return this.userAgent;
}
}
module.exports = new SystemUtil();

View File

@@ -1,64 +0,0 @@
'use strict';
import { remote } from 'electron';
import os = require('os');
import ConfigUtil = require('./config-util');
const { app } = remote;
let instance: null | SystemUtil = null;
class SystemUtil {
connectivityERR: string[];
userAgent: string | null;
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.connectivityERR = [
'ERR_INTERNET_DISCONNECTED',
'ERR_PROXY_CONNECTION_FAILED',
'ERR_CONNECTION_RESET',
'ERR_NOT_CONNECTED',
'ERR_NAME_NOT_RESOLVED',
'ERR_NETWORK_CHANGED'
];
this.userAgent = null;
return instance;
}
getOS(): string {
const platform = os.platform();
if (platform === 'darwin') {
return 'Mac';
} else if (platform === 'linux') {
return 'Linux';
} else if (platform === 'win32') {
if (parseFloat(os.release()) < 6.2) {
return 'Windows 7';
} else {
return 'Windows 10';
}
} else {
return '';
}
}
setUserAgent(webViewUserAgent: string): void {
this.userAgent = `ZulipElectron/${app.getVersion()} ${webViewUserAgent}`;
}
getUserAgent(): string | null {
if (!this.userAgent) {
this.setUserAgent(ConfigUtil.getConfigItem('userAgent', null));
}
return this.userAgent;
}
}
export = new SystemUtil();

View File

@@ -1,35 +0,0 @@
'use strict';
import path = require('path');
import electron = require('electron');
import i18n = require('i18n');
let instance: TranslationUtil = null;
let app: Electron.App = null;
/* To make the util runnable in both main and renderer process */
if (process.type === 'renderer') {
app = electron.remote.app;
} else {
app = electron.app;
}
class TranslationUtil {
constructor() {
if (instance) {
return this;
}
instance = this;
i18n.configure({
directory: path.join(__dirname, '../../../translations/'),
register: this
});
}
__(phrase: string): string {
return i18n.__({ phrase, locale: app.getLocale() ? app.getLocale() : 'en' });
}
}
export = new TranslationUtil();

View File

@@ -32,7 +32,7 @@
<i class="material-icons md-48">refresh</i>
<span id="reload-tooltip" style="display:none">Reload</span>
</div>
<div class="action-button disable" id="loading-action">
<div class="action-button" id="loading-action">
<i class="refresh material-icons md-48">loop</i>
<span id="loading-tooltip" style="display:none">Loading</span>
</div>
@@ -55,11 +55,6 @@
<send-feedback></send-feedback>
</div>
</body>
<script>
// we don't use src='./js/main' in the script tag because
// it messes up require module path resolution
require('./js/main');
</script>
<script src="js/main.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

View File

@@ -9,21 +9,14 @@
<body>
<div id="content">
<div id="picture"><img src="img/zulip_network.png"></div>
<div id="title">We can't connect to this organization</div>
<div id="subtitle">This could be because</div>
<ul id="description">
<li>You're not online or your proxy is misconfigured.</li>
<li>There is no Zulip organization hosted at this URL.</li>
<li>This Zulip organization is temporarily unavailable.</li>
<li>This Zulip organization has been moved or deleted.</li>
</ul>
<div id="buttons">
<div id="reconnect" class="button">Reconnect</div>
<div id="settings" class="button">Settings</div>
<div id="title">Zulip can't connect</div>
<div id="description">
<div>Your computer seems to be offline.</div>
<div>We will keep trying to reconnect, or you can try now.</div>
</div>
<div id="reconnect">Try now</div>
</div>
</body>
<script>var exports = {};</script>
<script src="js/preload.js"></script>
<script src="js/pages/network.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

View File

@@ -12,8 +12,6 @@
<div id="settings-container"></div>
</div>
</body>
<script>
require('./js/pages/preference/preference.js');
require('./js/shared/preventdrag.js')
</script>
<script src="js/pages/preference/preference.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

View File

@@ -1,52 +1,27 @@
interface DialogBoxError {
title: string;
content: string;
}
class Messages {
invalidZulipServerError(domain: string): string {
invalidZulipServerError(domain) {
return `${domain} does not appear to be a valid Zulip server. Make sure that
\n You can connect to that URL in a web browser.\
\n If you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings.\
\n It's a Zulip server. (The oldest supported version is 1.6).\
\n The server has a valid certificate. (You can add custom certificates in Settings > Organizations). \
\n The SSL is correctly configured for the certificate. Check out the SSL troubleshooting guide -
\n https://zulip.readthedocs.io/en/stable/production/ssl-certificates.html`;
\n The server has a valid certificate. (You can add custom certificates in Settings > Organizations).`;
}
noOrgsError(domain: string): string {
noOrgsError(domain) {
return `${domain} does not have any organizations added.\
\nPlease contact your server administrator.`;
}
certErrorMessage(domain: string, error: string): string {
certErrorMessage(domain, error) {
return `Do you trust certificate from ${domain}? \n ${error}`;
}
certErrorDetail(): string {
certErrorDetail() {
return `The organization you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\nIf you have a valid certificate please add it from Settings>Organizations and try to add the organization again.
\nUnless you have a good reason to believe otherwise, you should not proceed.
\nYou can click here if you'd like to proceed with the connection.`;
}
enterpriseOrgError(length: number, domains: string[]): DialogBoxError {
let domainList = '';
for (const domain of domains) {
domainList += `${domain}\n`;
}
return {
title: `Could not add the following ${length === 1 ? `organization` : `organizations`}`,
content: `${domainList}\nPlease contact your system administrator.`
};
}
orgRemovalError(url: string): DialogBoxError {
return {
title: `Removing ${url} is a restricted operation.`,
content: `Please contact your system administrator.`
};
}
}
export = new Messages();
module.exports = new Messages();

View File

@@ -1,20 +0,0 @@
# How to help translate Zulip Desktop
These are *generated* files (*) that contain translations of the strings in
the app.
You can help translate Zulip Desktop into your language! We do our
translations in Transifex, which is a nice web app for collaborating on
translations; a maintainer then syncs those translations into this repo.
To help out, [join the Zulip project on
Transifex](https://www.transifex.com/zulip/zulip/) and enter translations
there. More details in the [Zulip contributor docs](https://zulip.readthedocs.io/en/latest/translating/translating.html#translators-workflow).
Within that Transifex project, if you'd like to focus on Zulip Desktop, look
at `desktop.json`. The other resources there are for the Zulip web/mobile
app, where translations are also very welcome.
(*) One file is an exception: `en.json` is manually maintained as a
list of (English) messages in the source code, and is used when we upload to
Transifex a list of strings to be translated. It doesn't contain any
translations.

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "За Zulip",
"Actual Size": "Действителен размер",
"Add Custom Certificates": "Добавяне на персонализирани сертификати",
"Add Organization": "Добавяне на организация",
"Add a Zulip organization": "Добавете организация Zulip",
"Add custom CSS": "Добавете персонализиран CSS",
"Advanced": "напреднал",
"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 за показване)",
"About Zulip": "За Зулип",
"Actual Size": "Реален размер",
"Back": "обратно",
"Bounce dock on new private message": "Прескочи док в новото лично съобщение",
"Certificate file": "Файл за сертификат",
"Change": "промяна",
"Check for Updates": "Провери за обновления",
"Close": "Близо",
"Connect": "Свържете",
"Connect to another organization": "Свържете се с друга организация",
"Connected organizations": "Свързани организации",
"Copy": "копие",
"Copy Zulip URL": "Копирайте URL адреса на Zulip",
"Create a new organization": "Създайте нова организация",
"Cut": "Разрез",
"Default download location": "Място на изтегляне по подразбиране",
"Delete": "Изтрий",
"Desktop App Settings": "Настройки на приложението за работния плот",
"Desktop Notifications": "Известия за работния плот",
"Desktop Settings": "Настройки на работния плот",
"Disconnect": "Прекъсване на връзката",
"Download App Logs": "Изтеглете регистрационни файлове на приложенията",
"Desktop App Settings": "Настройки за настолни приложения",
"Edit": "редактиране",
"Edit Shortcuts": "Редактиране на преки пътища",
"Enable auto updates": "Активиране на автоматичните актуализации",
"Enable error reporting (requires restart)": "Активиране на отчитането за грешки (изисква се рестартиране)",
"Enable spellchecker (requires restart)": "Активиране на проверката на правописа (изисква се рестартиране)",
"Factory Reset": "Фабрично нулиране",
"File": "досие",
"Find accounts": "Намерете профили",
"Find accounts by email": "Намерете профили по имейл",
"Flash taskbar on new message": "Flash лентата на задачите в новото съобщение",
"Forward": "напред",
"Functionality": "Функционалност",
"General": "Общ",
"Get beta updates": "Изтеглете бета актуализации",
"Hard Reload": "Hard Reload",
"Hard Reload": "Трудно презареждане",
"Help": "Помогне",
"Help Center": "Помощен център",
"History": "история",
"History Shortcuts": "Преки пътища в историята",
"Keyboard Shortcuts": "Комбинация от клавиши",
"Log Out": "Излез от профила си",
"Log Out of Organization": "Излезте от организацията",
"Manual proxy configuration": "Ръчна конфигурация на прокси",
"Minimize": "Минимизиране",
"Mute all sounds from Zulip": "Заглуши всички звуци от Zulip",
"NO": "НЕ",
"Network": "мрежа",
"OR": "ИЛИ",
"Organization URL": "URL адрес на организацията",
"Organizations": "организации",
"Paste": "паста",
"Paste and Match Style": "Поставяне и стил на съвпадение",
"Proxy": "пълномощник",
"Proxy bypass rules": "Правила за заобикаляне на прокси",
"Proxy rules": "Прокси правила",
"Paste and Match Style": "Поставяне и съвпадение на стила",
"Quit": "напускам",
"Quit Zulip": "Прекрати Zulip",
"Redo": "ремонтирам",
"Release Notes": "Бележки към изданието",
"Reload": "Презареди",
"Report an Issue": "Подаване на сигнал за проблем",
"Reset App Data": "Нулиране на данни за приложения",
"Reset App Settings": "Нулиране на настройките на приложението",
"Reset Application Data": "Нулиране на данните на приложението",
"Save": "Запази",
"Report an issue...": "Подаване на сигнал за проблем ...",
"Reset App Settings": "Нулирайте настройките на приложението",
"Select All": "Избери всички",
"Settings": "Настройки",
"Shortcuts": "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": "Тези клавишни комбинации за настолни приложения разширяват webapp на Zulip",
"This will delete all application data including all added accounts and preferences": "Това ще изтрие всички данни за приложения, включително всички добавени акаунти и предпочитания",
"Tip": "Бакшиш",
"Toggle DevTools for Active Tab": "Превключете DevTools за Active Tab",
"Show App Logs": "Показване на регистрационните файлове на приложения",
"Toggle DevTools for Active Tab": "Превключете DevTools за активен раздел",
"Toggle DevTools for Zulip App": "Превключете DevTools за Zulip App",
"Toggle Do Not Disturb": "Превключване Не безпокойте",
"Toggle Full Screen": "Превключване на цял екран",
"Toggle Sidebar": "Превключване на страничната лента",
"Toggle Tray Icon": "Превключете иконата на тава",
"Tools": "Инструменти",
"Toggle Tray Icon": "Превключване на иконата на тавата",
"Undo": "премахвам",
"Upload": "Качи",
"Use system proxy settings (requires restart)": "Използване на системните прокси настройки (изисква рестартиране)",
"View": "изглед",
"View Shortcuts": "Преглед на преки пътища",
"Window": "прозорец",
"Window Shortcuts": "Клавишни комбинации",
"YES": "ДА",
"Zoom In": "Увеличавам",
"Zoom Out": "Отдалечавам",
"Zulip Help": "Помощ за Zulip",
"keyboard shortcuts": "комбинация от клавиши",
"script": "писменост"
"Zulip Help": "Помощ за Zulip"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "O uživateli Zulip",
"About Zulip": "O společnosti Zulip",
"Actual Size": "Aktuální velikost",
"Add Custom Certificates": "Přidat vlastní certifikáty",
"Add Organization": "Přidat organizaci",
"Add a Zulip organization": "Přidejte organizaci Zulip",
"Add custom CSS": "Přidat vlastní CSS",
"Advanced": "Pokročilý",
"All the connected organizations will appear here": "Zde se zobrazí všechny připojené organizace",
"Always start minimized": "Vždy začněte minimalizovat",
"App Updates": "Aktualizace aplikací",
"Appearance": "Vzhled",
"Application Shortcuts": "Zkratky aplikací",
"Are you sure you want to disconnect this organization?": "Opravdu chcete tuto organizaci odpojit?",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Automaticky skrýt lištu menu (stiskněte klávesu Alt pro zobrazení)",
"Back": "Zadní",
"Bounce dock on new private message": "Bounce dock na nové soukromé zprávy",
"Certificate file": "Soubor certifikátu",
"Change": "Změna",
"Check for Updates": "Kontrola aktualizací",
"Close": "Zavřít",
"Connect": "Připojit",
"Connect to another organization": "Připojte se k jiné organizaci",
"Connected organizations": "Připojené organizace",
"Copy": "kopírovat",
"Copy Zulip URL": "Kopírovat adresu URL aplikace Zulip",
"Create a new organization": "Vytvořit novou organizaci",
"Cut": "Střih",
"Default download location": "Výchozí umístění stahování",
"Delete": "Smazat",
"Delete": "Vymazat",
"Desktop App Settings": "Nastavení aplikace Desktop",
"Desktop Notifications": "Oznámení o pracovní ploše",
"Desktop Settings": "Nastavení plochy",
"Disconnect": "Odpojit",
"Download App Logs": "Stáhnout App Logs",
"Edit": "Upravit",
"Edit Shortcuts": "Upravit zkratky",
"Enable auto updates": "Povolit automatické aktualizace",
"Enable error reporting (requires restart)": "Povolit hlášení chyb (vyžaduje restart)",
"Enable spellchecker (requires restart)": "Povolit kontrolu pravopisu (vyžaduje restartování)",
"Factory Reset": "Tovární nastavení",
"File": "Soubor",
"Find accounts": "Najít účty",
"Find accounts by email": "Najít účty e-mailem",
"Flash taskbar on new message": "Hlavní panel Flash na nové zprávě",
"Forward": "Vpřed",
"Functionality": "Funkčnost",
"General": "Všeobecné",
"Get beta updates": "Získejte aktualizace beta",
"Hard Reload": "Hard Reload",
"Help": "Pomoc",
"Help Center": "Centrum nápovědy",
"History": "Dějiny",
"History Shortcuts": "Zkratky historie",
"Keyboard Shortcuts": "Klávesové zkratky",
"Log Out": "Odhlásit se",
"Log Out of Organization": "Odhlásit se z organizace",
"Manual proxy configuration": "Ruční konfigurace proxy",
"Minimize": "Minimalizovat",
"Mute all sounds from Zulip": "Vypněte všechny zvuky ze Zulipu",
"NO": "NE",
"Network": "Síť",
"OR": "NEBO",
"Organization URL": "URL organizace",
"Organizations": "Organizace",
"Minimize": "Minimalizujte",
"Paste": "Vložit",
"Paste and Match Style": "Vložit a shodit styl",
"Proxy": "Proxy",
"Proxy bypass rules": "Proxy bypass pravidla",
"Proxy rules": "Proxy pravidla",
"Paste and Match Style": "Vložit a přizpůsobit styl",
"Quit": "Přestat",
"Quit Zulip": "Ukončete Zulip",
"Redo": "Předělat",
"Release Notes": "Poznámky k vydání",
"Reload": "Znovu načíst",
"Report an Issue": "Nahlásit problém",
"Reset App Data": "Resetovat data aplikace",
"Report an issue...": "Nahlásit problém...",
"Reset App Settings": "Obnovit nastavení aplikace",
"Reset Application Data": "Resetovat data aplikace",
"Save": "Uložit",
"Select All": "Vybrat vše",
"Settings": "Nastavení",
"Shortcuts": "Zkratky",
"Show App Logs": "Zobrazit protokoly aplikací",
"Show app icon in system tray": "Zobrazit ikonu aplikace v systémové liště",
"Show app unread badge": "Zobrazit nepřečtený odznak aplikace",
"Show desktop notifications": "Zobrazit oznámení na ploše",
"Show downloaded files in file manager": "Zobrazit stažené soubory ve správci souborů",
"Show sidebar": "Zobrazit postranní panel",
"Start app at login": "Spuštění aplikace při přihlášení",
"Switch to Next Organization": "Přepnout na další organizaci",
"Switch to Previous Organization": "Přepněte na předchozí organizaci",
"These desktop app shortcuts extend the Zulip webapp's": "Tyto klávesové zkratky pro aplikaci na ploše rozšiřují aplikaci Zulip webapp",
"This will delete all application data including all added accounts and preferences": "Tím odstraníte všechna data aplikace včetně všech přidaných účtů a předvoleb",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Přepnout DevTools pro Active Tab",
"Toggle DevTools for Zulip App": "Přepnout DevTools pro Zulip App",
"Toggle Do Not Disturb": "Přepnout Do Nerušit",
"Show App Logs": "Zobrazit protokoly aplikace",
"Toggle DevTools for Active Tab": "Přepínač DevTools pro aktivní kartu",
"Toggle DevTools for Zulip App": "Přepnout nástroj DevTools pro aplikaci Zulip App",
"Toggle Full Screen": "Přepnout na celou obrazovku",
"Toggle Sidebar": "Přepnout postranní panel",
"Toggle Tray Icon": "Přepnout ikonu zásobníku",
"Tools": "Nástroje",
"Undo": "vrátit",
"Upload": "nahrát",
"Use system proxy settings (requires restart)": "Použít nastavení proxy serveru (vyžaduje restart)",
"View": "Pohled",
"View Shortcuts": "Zobrazit zástupce",
"Window": "Okno",
"Window Shortcuts": "Klávesové zkratky",
"YES": "ANO",
"Zoom In": "Přiblížit",
"Zoom Out": "Oddálit",
"Zulip Help": "Nápověda Zulip",
"keyboard shortcuts": "klávesové zkratky",
"script": "skript"
"Zulip Help": "Zulip Nápověda"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "Über Zulip",
"Actual Size": "Tatsächliche Größe",
"Add Custom Certificates": "Benutzerdefinierte Zertifikate hinzufügen",
"Add Organization": "Organisation hinzufügen",
"Add a Zulip organization": "Fügen Sie eine Zulip-Organisation hinzu",
"Add custom CSS": "Fügen Sie benutzerdefiniertes CSS hinzu",
"Advanced": "Fortgeschritten",
"All the connected organizations will appear here": "Alle verbundenen Organisationen werden hier angezeigt",
"Always start minimized": "Immer minimiert anfangen",
"App Updates": "App-Updates",
"Appearance": "Aussehen",
"Application Shortcuts": "Anwendungsverknüpfungen",
"Are you sure you want to disconnect this organization?": "Möchten Sie diese Organisation wirklich trennen?",
"Auto hide Menu bar": "Menüleiste automatisch ausblenden",
"Auto hide menu bar (Press Alt key to display)": "Menüleiste automatisch ausblenden (Drücken Sie die Alt-Taste, um anzuzeigen)",
"Back": "Zurück",
"Bounce dock on new private message": "Bounce-Dock für neue private Nachricht",
"Certificate file": "Zertifikatsdatei",
"Change": "Veränderung",
"Check for Updates": "Auf Updates prüfen",
"Close": "Schließen",
"Connect": "Verbinden",
"Connect to another organization": "Stellen Sie eine Verbindung zu einer anderen Organisation her",
"Connected organizations": "Verbundene Organisationen",
"Copy": "Kopieren",
"Copy Zulip URL": "Zulip-URL kopieren",
"Create a new organization": "Erstellen Sie eine neue Organisation",
"Cut": "Schnitt",
"Default download location": "Standard-Download-Speicherort",
"Delete": "Löschen",
"Desktop App Settings": "Desktop App-Einstellungen",
"Desktop Notifications": "Desktop-Benachrichtigungen",
"Desktop Settings": "Desktop-Einstellungen",
"Disconnect": "Trennen",
"Download App Logs": "App-Protokolle herunterladen",
"Desktop App Settings": "Desktop App Einstellungen",
"Edit": "Bearbeiten",
"Edit Shortcuts": "Verknüpfungen bearbeiten",
"Enable auto updates": "Automatische Updates aktivieren",
"Enable error reporting (requires restart)": "Fehlerberichterstattung aktivieren (Neustart erforderlich)",
"Enable spellchecker (requires restart)": "Rechtschreibprüfung aktivieren (Neustart erforderlich)",
"Factory Reset": "Werkseinstellungen zurückgesetzt",
"File": "Datei",
"Find accounts": "Konten suchen",
"Find accounts by email": "Finden Sie Konten per E-Mail",
"Flash taskbar on new message": "Flash-Taskleiste bei neuer Nachricht",
"Forward": "Nach vorne",
"Functionality": "Funktionalität",
"General": "Allgemeines",
"Get beta updates": "Erhalten Sie Beta-Updates",
"Hard Reload": "Festes Nachladen",
"Hard Reload": "Hard Reload",
"Help": "Hilfe",
"Help Center": "Hilfezentrum",
"History": "Geschichte",
"History Shortcuts": "Verlaufsverknüpfungen",
"Keyboard Shortcuts": "Tastatürkürzel",
"Log Out": "Ausloggen",
"Log Out of Organization": "Aus Organisation ausloggen",
"Manual proxy configuration": "Manuelle Proxy-Konfiguration",
"Minimize": "Minimieren",
"Mute all sounds from Zulip": "Schaltet alle Sounds von Zulip stumm",
"NO": "NEIN",
"Network": "Netzwerk",
"OR": "ODER",
"Organization URL": "Organisations-URL",
"Organizations": "Organisationen",
"Paste": "Einfügen",
"Paste and Match Style": "Einfügen und Format anpassen",
"Proxy": "Proxy",
"Proxy bypass rules": "Proxy-Umgehungsregeln",
"Proxy rules": "Proxy-Regeln",
"Paste and Match Style": "Einfügen und Anpassen von Stilen",
"Quit": "Verlassen",
"Quit Zulip": "Beenden Sie Zulip",
"Redo": "Redo",
"Release Notes": "Versionshinweise",
"Redo": "Wiederholen",
"Reload": "Neu laden",
"Report an Issue": "Ein Problem melden",
"Reset App Data": "App-Daten zurücksetzen",
"Report an issue...": "Ein Problem melden...",
"Reset App Settings": "App-Einstellungen zurücksetzen",
"Reset Application Data": "Anwendungsdaten zurücksetzen",
"Save": "sparen",
"Select All": "Wählen Sie Alle",
"Settings": "die Einstellungen",
"Shortcuts": "Verknüpfungen",
"Show App Logs": "App-Protokolle anzeigen",
"Show app icon in system tray": "App-Symbol in der Taskleiste anzeigen",
"Show app unread badge": "Zeige App ungelesenen Badge",
"Show desktop notifications": "Desktop-Benachrichtigungen anzeigen",
"Show downloaded files in file manager": "Heruntergeladene Dateien im Dateimanager anzeigen",
"Show sidebar": "Seitenleiste anzeigen",
"Start app at login": "App beim Login starten",
"Switch to Next Organization": "Wechseln Sie zur nächsten Organisation",
"Switch to Previous Organization": "Zur vorherigen Organisation wechseln",
"These desktop app shortcuts extend the Zulip webapp's": "Diese Desktop-App-Verknüpfungen erweitern die Zulip-Webanwendungen",
"This will delete all application data including all added accounts and preferences": "Dadurch werden alle Anwendungsdaten einschließlich aller hinzugefügten Konten und Einstellungen gelöscht",
"Tip": "Spitze",
"Toggle DevTools for Active Tab": "DevTools für aktive Registerkarte umschalten",
"Toggle DevTools for Zulip App": "DevTools für Zulip App umschalten",
"Toggle Do Not Disturb": "Nicht stören umschalten",
"Show App Logs": "App-Logs anzeigen",
"Toggle DevTools for Active Tab": "Schalten Sie DevTools für Active Tab ein",
"Toggle DevTools for Zulip App": "Umschalt DevTools für Zulip App",
"Toggle Full Screen": "Vollbild umschalten",
"Toggle Sidebar": "Seitenleiste umschalten",
"Toggle Tray Icon": "Taskleistensymbol umschalten",
"Tools": "Werkzeuge",
"Toggle Sidebar": "Toggle Seitenleiste",
"Toggle Tray Icon": "Toggle Tray-Symbol",
"Undo": "Rückgängig machen",
"Upload": "Hochladen",
"Use system proxy settings (requires restart)": "System-Proxy-Einstellungen verwenden (Neustart erforderlich)",
"View": "Aussicht",
"View Shortcuts": "Verknüpfungen anzeigen",
"Window": "Fenster",
"Window Shortcuts": "Fensterverknüpfungen",
"YES": "JA",
"Zoom In": "Hineinzoomen",
"Zoom Out": "Rauszoomen",
"Zulip Help": "Zulip-Hilfe",
"keyboard shortcuts": "Tastatürkürzel",
"script": "Skript"
"Zulip Help": "Zulip Hilfe"
}

View File

@@ -1,118 +0,0 @@
{
"Add Organization": "Add Organization",
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
"Desktop Settings": "Desktop Settings",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Copy Zulip URL": "Copy Zulip URL",
"Log Out of Organization": "Log Out of Organization",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"Minimize": "Minimize",
"Close": "Close",
"Quit": "Quit",
"Edit": "Edit",
"Undo": "Undo",
"Redo": "Redo",
"Cut": "Cut",
"Copy": "Copy",
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Select All": "Select All",
"View": "View",
"Reload": "Reload",
"Hard Reload": "Hard Reload",
"Toggle Full Screen": "Toggle Full Screen",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Actual Size": "Actual Size",
"Toggle Tray Icon": "Toggle Tray Icon",
"Toggle Sidebar": "Toggle Sidebar",
"Auto hide Menu bar": "Auto hide Menu bar",
"History": "History",
"Back": "Back",
"Forward": "Forward",
"Window": "Window",
"Tools": "Tools",
"Check for Updates": "Check for Updates",
"Release Notes": "Release Notes",
"Factory Reset": "Factory Reset",
"Download App Logs": "Download App Logs",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Help": "Help",
"About Zulip": "About Zulip",
"Help Center": "Help Center",
"Report an Issue": "Report an Issue",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"General": "General",
"Network": "Network",
"AddServer": "AddServer",
"Organizations": "Organizations",
"Shortcuts": "Shortcuts",
"Settings": "Settings",
"Add a Zulip organization": "Add a Zulip organization",
"Organization URL": "Organization URL",
"Connect": "Connect",
"OR": "OR",
"Create a new organization": "Create a new organization",
"Proxy": "Proxy",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"Manual proxy configuration": "Manual proxy configuration",
"script": "script",
"Proxy rules": "Proxy rules",
"Proxy bypass rules": "Proxy bypass rules",
"Save": "Save",
"Appearance": "Appearance",
"Show app icon in system tray": "Show app icon in system tray",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Show sidebar": "Show sidebar",
"Show app unread badge": "Show app unread badge",
"Bounce dock on new private message": "Bounce dock on new private message",
"Flash taskbar on new message": "Flash taskbar on new message",
"Desktop Notifications": "Desktop Notifications",
"Show desktop notifications": "Show desktop notifications",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"App Updates": "App Updates",
"Enable auto updates": "Enable auto updates",
"Get beta updates": "Get beta updates",
"Functionality": "Functionality",
"Start app at login": "Start app at login",
"Always start minimized": "Always start minimized",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Advanced": "Advanced",
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Add custom CSS": "Add custom CSS",
"Upload": "Upload",
"Delete": "Delete",
"Default download location": "Default download location",
"Change": "Change",
"Reset Application Data": "Reset Application Data",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Reset App Data": "Reset App Data",
"Connected organizations": "Connected organizations",
"All the connected orgnizations will appear here.": "All the connected orgnizations will appear here.",
"Connect to another organization": "Connect to another organization",
"Add Custom Certificates": "Add Custom Certificates",
"Find accounts by email": "Find accounts by email",
"All the connected orgnizations will appear here": "All the connected orgnizations will appear here",
"Disconnect": "Disconnect",
"Certificate file": "Certificate file",
"Find accounts": "Find accounts",
"Tip": "Tip",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"keyboard shortcuts": "keyboard shortcuts",
"Application Shortcuts": "Application Shortcuts",
"Reset App Settings": "Reset App Settings",
"Log Out": "Log Out",
"Hide Zulip": "Hide Zulip",
"Quit Zulip": "Quit Zulip",
"Edit Shortcuts": "Edit Shortcuts",
"Emoji & Symbols": "Emoji & Symbols",
"View Shortcuts": "View Shortcuts",
"Enter Full Screen": "Enter Full Screen",
"History Shortcuts": "History Shortcuts"
}

View File

@@ -1,108 +0,0 @@
{
"File": "File",
"Add Organization": "Add Organization",
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
"Desktop Settings": "Desktop Settings",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Copy Zulip URL": "Copy Zulip URL",
"Log Out of Organization": "Log Out of Organization",
"Minimize": "Minimize",
"Close": "Close",
"Quit": "Quit",
"Edit": "Edit",
"Undo": "Undo",
"Redo": "Redo",
"Cut": "Cut",
"Copy": "Copy",
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Select All": "Select All",
"View": "View",
"Reload": "Reload",
"Hard Reload": "Hard Reload",
"Toggle Full Screen": "Toggle Full Screen",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Actual Size": "Actual Size",
"Toggle Tray Icon": "Toggle Tray Icon",
"Toggle Sidebar": "Toggle Sidebar",
"Auto hide Menu bar": "Auto hide Menu bar",
"History": "History",
"Back": "Back",
"Forward": "Forward",
"Window": "Window",
"Tools": "Tools",
"Check for Updates": "Check for Updates",
"Release Notes": "Release Notes",
"Factory Reset": "Factory Reset",
"Download App Logs": "Download App Logs",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Help": "Help",
"About Zulip": "About Zulip",
"Help Center": "Help Center",
"Report an Issue": "Report an Issue",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"General": "General",
"Network": "Network",
"AddServer": "AddServer",
"Organizations": "Organizations",
"Shortcuts": "Shortcuts",
"Appearance": "Appearance",
"Show app icon in system tray": "Show app icon in system tray",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Show sidebar": "Show sidebar",
"Show app unread badge": "Show app unread badge",
"Bounce dock on new private message": "Bounce dock on new private message",
"Flash taskbar on new message": "Flash taskbar on new message",
"Desktop Notifications": "Desktop Notifications",
"Show desktop notifications": "Show desktop notifications",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"App Updates": "App Updates",
"Enable auto updates": "Enable auto updates",
"Get beta updates": "Get beta updates",
"Functionality": "Functionality",
"Start app at login": "Start app at login",
"Always start minimized": "Always start minimized",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Advanced": "Advanced",
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Add custom CSS": "Add custom CSS",
"Upload": "Upload",
"Delete": "Delete",
"Default download location": "Default download location",
"Change": "Change",
"Reset Application Data": "Reset Application Data",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Reset App Data": "Reset App Data",
"Proxy": "Proxy",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"Manual proxy configuration": "Manual proxy configuration",
"script": "script",
"Proxy rules": "Proxy rules",
"Proxy bypass rules": "Proxy bypass rules",
"Save": "Save",
"Connected organizations": "Connected organizations",
"All the connected orgnizations will appear here.": "All the connected orgnizations will appear here.",
"Connect to another organization": "Connect to another organization",
"Add Custom Certificates": "Add Custom Certificates",
"Find accounts by email": "Find accounts by email",
"All the connected orgnizations will appear here": "All the connected orgnizations will appear here",
"Disconnect": "Disconnect",
"Organization URL": "Organization URL",
"Certificate file": "Certificate file",
"Find accounts": "Find accounts",
"Tip": "Tip",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"keyboard shortcuts": "keyboard shortcuts",
"Application Shortcuts": "Application Shortcuts",
"Settings": "Settings",
"Log Out": "Log Out",
"Quit Zulip": "Quit Zulip",
"Edit Shortcuts": "Edit Shortcuts",
"View Shortcuts": "View Shortcuts",
"History Shortcuts": "History Shortcuts",
"Window Shortcuts": "Window Shortcuts"
}

View File

@@ -1,118 +1,39 @@
{
"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",
"OR": "OR",
"Organization URL": "Organization URL",
"Organizations": "Organizations",
"Paste": "Paste",
"Paste and Match Style": "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",
"Reset App Data": "Reset App Data",
"Report an issue...": "Report an issue...",
"Reset App Settings": "Reset App Settings",
"Reset Application Data": "Reset Application Data",
"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",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed"
"Zulip Help": "Zulip Help"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "Acerca de Zulip",
"Actual Size": "Tamaño real",
"Add Custom Certificates": "Añadir certificados personalizados",
"Add Organization": "Añadir organización",
"Add a Zulip organization": "Añadir una organización Zulip",
"Add custom CSS": "Añadir CSS personalizado",
"Advanced": "Avanzado",
"All the connected organizations will appear here": "Todas las organizaciones conectadas aparecerán aquí.",
"Always start minimized": "Siempre empezar minimizado",
"App Updates": "Actualizaciones de la aplicación",
"Appearance": "Apariencia",
"Application Shortcuts": "Atajos de aplicación",
"Are you sure you want to disconnect this organization?": "¿Estás seguro de que quieres desconectar esta organización?",
"Auto hide Menu bar": "Ocultar automáticamente la barra de menú",
"Auto hide menu bar (Press Alt key to display)": "Ocultar automáticamente la barra de menú (presionar la tecla Alt para mostrar)",
"Back": "Espalda",
"Bounce dock on new private message": "Bounce dock en nuevo mensaje privado",
"Certificate file": "Archivo de certificado",
"Change": "Cambio",
"Check for Updates": "Buscar actualizaciones",
"Close": "Cerrar",
"Connect": "Conectar",
"Connect to another organization": "Conectarse a otra organización",
"Connected organizations": "Organizaciones conectadas",
"Close": "Cerca",
"Copy": "Dupdo",
"Copy Zulip URL": "Copiar URL de Zulip",
"Create a new organization": "Crear una nueva organización",
"Cut": "Cortar",
"Default download location": "Ubicación de descarga predeterminada",
"Delete": "Borrar",
"Desktop App Settings": "Configuración de la aplicación de escritorio",
"Desktop Notifications": "Notificaciones de escritorio",
"Desktop Settings": "Configuraciones de escritorio",
"Disconnect": "Desconectar",
"Download App Logs": "Descargar App Logs",
"Edit": "Editar",
"Edit Shortcuts": "Editar accesos directos",
"Enable auto updates": "Habilitar actualizaciones automáticas",
"Enable error reporting (requires restart)": "Habilitar informes de errores (requiere reinicio)",
"Enable spellchecker (requires restart)": "Habilitar el corrector ortográfico (requiere reinicio)",
"Factory Reset": "Restablecimiento de fábrica",
"File": "Expediente",
"Find accounts": "Encontrar cuentas",
"Find accounts by email": "Encuentra cuentas por correo electrónico",
"Flash taskbar on new message": "Flash barra de tareas en nuevo mensaje",
"File": "Archivo",
"Forward": "Adelante",
"Functionality": "Funcionalidad",
"General": "General",
"Get beta updates": "Recibe actualizaciones beta",
"Hard Reload": "Recarga dura",
"Hard Reload": "Recargar duro",
"Help": "Ayuda",
"Help Center": "Centro de ayuda",
"History": "Historia",
"History Shortcuts": "Atajos de historia",
"Keyboard Shortcuts": "Atajos de teclado",
"Log Out": "Cerrar sesión",
"Log Out of Organization": "Salir de la organización",
"Manual proxy configuration": "Configuración de proxy manual",
"Minimize": "Minimizar",
"Mute all sounds from Zulip": "Silencia todos los sonidos de Zulip",
"NO": "NO",
"Network": "Red",
"OR": "O",
"Organization URL": "URL de la organización",
"Organizations": "Organizaciones",
"Paste": "Pegar",
"Paste and Match Style": "Pegar y combinar estilo",
"Proxy": "Apoderado",
"Proxy bypass rules": "Reglas de omisión de proxy",
"Proxy rules": "Reglas de proxy",
"Quit": "Dejar",
"Quit Zulip": "Dejar Zulip",
"Redo": "Rehacer",
"Release Notes": "Notas de lanzamiento",
"Reload": "Recargar",
"Report an Issue": "Reportar un problema",
"Reset App Data": "Restablecer datos de la aplicación",
"Reset App Settings": "Restablecer la configuración de la aplicación",
"Reset Application Data": "Restablecer datos de aplicación",
"Save": "Salvar",
"Report an issue...": "Reportar un problema...",
"Reset App Settings": "Restablecer configuraciones",
"Select All": "Seleccionar todo",
"Settings": "Ajustes",
"Shortcuts": "Atajos",
"Show App Logs": "Mostrar registros de aplicaciones",
"Show app icon in system tray": "Mostrar icono de la aplicación en la bandeja del sistema",
"Show app unread badge": "Mostrar la aplicación de placa sin leer",
"Show desktop notifications": "Mostrar notificaciones de escritorio",
"Show downloaded files in file manager": "Mostrar los archivos descargados en el administrador de archivos",
"Show sidebar": "Mostrar barra lateral",
"Start app at login": "Iniciar la aplicación al iniciar sesión",
"Switch to Next Organization": "Cambiar a la siguiente organización",
"Switch to Previous Organization": "Cambiar a la organización anterior",
"These desktop app shortcuts extend the Zulip webapp's": "Estos accesos directos de aplicaciones de escritorio extienden las aplicaciones web de Zulip.",
"This will delete all application data including all added accounts and preferences": "Esto eliminará todos los datos de la aplicación, incluidas todas las cuentas y preferencias agregadas.",
"Tip": "Propina",
"Toggle DevTools for Active Tab": "Alternar DevTools para Active Tab",
"Toggle DevTools for Zulip App": "Alternar DevTools para Zulip App",
"Toggle Do Not Disturb": "Alternar No molestar",
"Toggle DevTools for Zulip App": "Alternar DevTools para la aplicación Zulip",
"Toggle Full Screen": "Alternar pantalla completa",
"Toggle Sidebar": "Alternar barra lateral",
"Toggle Tray Icon": "Icono de bandeja de palanca",
"Tools": "Herramientas",
"Toggle Tray Icon": "Alternar el icono de la bandeja",
"Undo": "Deshacer",
"Upload": "Subir",
"Use system proxy settings (requires restart)": "Usar la configuración del proxy del sistema (requiere reinicio)",
"View": "Ver",
"View Shortcuts": "Ver accesos directos",
"Window": "Ventana",
"Window Shortcuts": "Atajos de ventana",
"YES": "SÍ",
"Zoom In": "Acercarse",
"Zoom Out": "Disminuir el zoom",
"Zulip Help": "Ayuda de Zulip",
"keyboard shortcuts": "atajos de teclado",
"script": "guión"
"Zulip Help": "Ayuda de Zulip"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "À propos de Zulip",
"Actual Size": "Taille actuelle",
"Add Custom Certificates": "Ajouter des certificats personnalisés",
"Add Organization": "Ajouter une organisation",
"Add a Zulip organization": "Ajouter une organisation Zulip",
"Add custom CSS": "Ajouter un CSS personnalisé",
"Advanced": "Avancée",
"All the connected organizations will appear here": "Toutes les organisations connectées apparaîtront ici",
"Always start minimized": "Toujours commencer minimisé",
"App Updates": "Mises à jour de l'application",
"Appearance": "Apparence",
"Application Shortcuts": "Raccourcis d'application",
"Are you sure you want to disconnect this organization?": "Êtes-vous sûr de vouloir déconnecter cette organisation?",
"Auto hide Menu bar": "Masquer automatiquement la barre de menus",
"Auto hide menu bar (Press Alt key to display)": "Masquer automatiquement la barre de menu (appuyez sur la touche Alt pour afficher)",
"Back": "Retour",
"Bounce dock on new private message": "Bounce Dock sur un nouveau message privé",
"Certificate file": "Dossier de certificat",
"Change": "Changement",
"Check for Updates": "Vérifier les mises à jour",
"Back": "Arrière",
"Close": "Fermer",
"Connect": "Relier",
"Connect to another organization": "Se connecter à une autre organisation",
"Connected organizations": "Organisations connectées",
"Copy": "Copie",
"Copy Zulip URL": "Copier l'URL Zulip",
"Create a new organization": "Créer une nouvelle organisation",
"Cut": "Couper",
"Default download location": "Emplacement de téléchargement par défaut",
"Delete": "Effacer",
"Desktop App Settings": "Paramètres de l'application de bureau",
"Desktop Notifications": "Notifications de bureau",
"Desktop Settings": "Paramètres du bureau",
"Disconnect": "Déconnecter",
"Download App Logs": "Télécharger les journaux d'application",
"Edit": "modifier",
"Edit Shortcuts": "Modifier les raccourcis",
"Enable auto updates": "Activer les mises à jour automatiques",
"Enable error reporting (requires restart)": "Activer le signalement des erreurs (nécessite un redémarrage)",
"Enable spellchecker (requires restart)": "Activer le correcteur orthographique (nécessite un redémarrage)",
"Factory Reset": "Retour aux paramètres d'usine",
"File": "Fichier",
"Find accounts": "Trouver des comptes",
"Find accounts by email": "Trouver des comptes par email",
"Flash taskbar on new message": "Barre de tâches Flash sur un nouveau message",
"Forward": "Vers l'avant",
"Functionality": "La fonctionnalité",
"General": "Général",
"Get beta updates": "Obtenir les mises à jour bêta",
"Hard Reload": "Rechargement dur",
"Help": "Aidez-moi",
"Help Center": "Centre d'aide",
"History": "L'histoire",
"History Shortcuts": "Raccourcis Histoire",
"History": "Histoire",
"Keyboard Shortcuts": "Raccourcis clavier",
"Log Out": "Connectez - Out",
"Log Out of Organization": "Déconnexion de l'organisation",
"Manual proxy configuration": "Configuration manuelle du proxy",
"Minimize": "Minimiser",
"Mute all sounds from Zulip": "Couper tous les sons de Zulip",
"NO": "NON",
"Network": "Réseau",
"OR": "OU",
"Organization URL": "URL de l'organisation",
"Organizations": "Les organisations",
"Paste": "Coller",
"Paste and Match Style": "Coller et assortir le style",
"Proxy": "Procuration",
"Proxy bypass rules": "Règles de contournement des procurations",
"Proxy rules": "Règles de procuration",
"Quit": "Quitter",
"Quit Zulip": "Quittez Zulip",
"Redo": "Refaire",
"Release Notes": "Notes de version",
"Reload": "Recharger",
"Report an Issue": "Signaler un problème",
"Reset App Data": "Réinitialiser les données de l'application",
"Reset App Settings": "Réinitialiser les paramètres de l'application",
"Reset Application Data": "Réinitialiser les données d'application",
"Save": "sauvegarder",
"Report an issue...": "Signaler un problème...",
"Reset App Settings": "Réinitialiser les paramètres",
"Select All": "Tout sélectionner",
"Settings": "Réglages",
"Shortcuts": "Raccourcis",
"Show App Logs": "Afficher les journaux d'application",
"Show app icon in system tray": "Afficher l'icône de l'application dans la barre d'état système",
"Show app unread badge": "Afficher le badge non lu de l'application",
"Show desktop notifications": "Afficher les notifications du bureau",
"Show downloaded files in file manager": "Afficher les fichiers téléchargés dans le gestionnaire de fichiers",
"Show sidebar": "Afficher la barre latérale",
"Start app at login": "Lancer l'application à la connexion",
"Switch to Next Organization": "Passer à l'organisation suivante",
"Switch to Previous Organization": "Passer à l'organisation précédente",
"These desktop app shortcuts extend the Zulip webapp's": "Ces raccourcis dapplications de bureau étendent les applications Web de Zulip.",
"This will delete all application data including all added accounts and preferences": "Cela supprimera toutes les données de l'application, y compris tous les comptes et préférences ajoutés.",
"Tip": "Pointe",
"Show App Logs": "Afficher les journaux d'applications",
"Toggle DevTools for Active Tab": "Basculer DevTools pour l'onglet actif",
"Toggle DevTools for Zulip App": "Basculer DevTools pour Zulip App",
"Toggle Do Not Disturb": "Basculer ne pas déranger",
"Toggle DevTools for Zulip App": "Basculer DevTools pour l'application Zulip",
"Toggle Full Screen": "Basculer en plein écran",
"Toggle Sidebar": "Basculer la barre latérale",
"Toggle Tray Icon": "Basculer l'icône du plateau",
"Tools": "Outils",
"Toggle Tray Icon": "Toggle Tray Icône",
"Undo": "annuler",
"Upload": "Télécharger",
"Use system proxy settings (requires restart)": "Utiliser les paramètres proxy du système (nécessite un redémarrage)",
"View": "Vue",
"View Shortcuts": "Afficher les raccourcis",
"Window": "La fenêtre",
"Window Shortcuts": "Raccourcis de la fenêtre",
"YES": "OUI",
"Window": "Fenêtre",
"Zoom In": "Agrandir",
"Zoom Out": "Dézoomer",
"Zulip Help": "Aide Zulip",
"keyboard shortcuts": "Raccourcis clavier",
"script": "scénario"
"Zulip Help": "Aide Zulip"
}

View File

@@ -1,118 +1,39 @@
{
"About Zulip": "जूलिप के बारे में",
"About Zulip": "Zulip के बारे में",
"Actual Size": "वास्तविक आकार",
"Add Custom Certificates": "कस्टम प्रमाण पत्र जोड़ें",
"Add Organization": "संगठन जोड़ें",
"Add a Zulip organization": "एक जूलिप संगठन जोड़ें",
"Add custom CSS": "कस्टम CSS जोड़ें",
"Advanced": "उन्नत",
"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": "नए निजी संदेश पर बाउंस डॉक",
"Certificate file": "प्रमाणपत्र फ़ाइल",
"Change": "परिवर्तन",
"Check for Updates": "अद्यतन के लिए जाँच",
"Close": "बंद करे",
"Connect": "जुडिये",
"Connect to another organization": "किसी अन्य संगठन से कनेक्ट करें",
"Connected organizations": "जुड़े हुए संगठन",
"Copy": "प्रतिलिपि",
"Copy Zulip URL": "Zulip URL को कॉपी करें",
"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": "ईमेल द्वारा खाते ढूंढें",
"Flash taskbar on new message": "नए संदेश पर फ्लैश टास्कबार",
"Forward": "आगे",
"Functionality": "कार्यक्षमता",
"General": "सामान्य",
"Get beta updates": "बीटा अपडेट प्राप्त करें",
"Hard Reload": "हार्ड रीलोड",
"Hard Reload": "कठिन पुन: लोड करें",
"Help": "मदद",
"Help Center": "सहायता केंद्र",
"History": "इतिहास",
"History Shortcuts": "इतिहास शॉर्टकट",
"Keyboard Shortcuts": "कुंजीपटल अल्प मार्ग",
"Log Out": "लोग आउट",
"Log Out of Organization": "संगठन से बाहर प्रवेश करें",
"Manual proxy configuration": "मैनुअल प्रॉक्सी कॉन्फ़िगरेशन",
"Minimize": "छोटा करना",
"Mute all sounds from Zulip": "ज़ूलिप से सभी ध्वनियों को म्यूट करें",
"NO": "नहीं",
"Network": "नेटवर्क",
"OR": "या",
"Organization URL": "संगठन का URL",
"Organizations": "संगठन",
"Paste": "चिपकाएं",
"Paste and Match Style": "पेस्ट और मैच स्टाइल",
"Proxy": "प्रतिनिधि",
"Proxy bypass rules": "प्रॉक्सी बायपास नियम",
"Proxy rules": "प्रॉक्सी नियम",
"Paste and Match Style": "चिपकाएं और शैली का मिलान करें",
"Quit": "छोड़ना",
"Quit Zulip": "ज़ुल्फ़ छोड़ो",
"Redo": "फिर से करना",
"Release Notes": "रिलीज नोट्स",
"Reload": "सीमा से अधिक लादना",
"Report an Issue": "मामले की रिपोर्ट करें",
"Reset App Data": "ऐप ेट रीसेट करें",
"Reset App Settings": "ऐप सेटिंग रीसेट करें",
"Reset Application Data": "एप्लिकेशन डेटा रीसेट करें",
"Save": "बचाना",
"Report an issue...": "मामले की रिपोर्ट करें...",
"Reset App Settings": "ऐप ेटिंग्स रीसेट करें",
"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 webapp's का विस्तार करते हैं",
"This will delete all application data including all added accounts and preferences": "यह सभी जोड़े गए खातों और वरीयताओं सहित सभी एप्लिकेशन डेटा को हटा देगा",
"Tip": "टिप",
"Toggle DevTools for Active Tab": "सक्रिय टैब के लिए DevTools टॉगल करें",
"Toggle DevTools for Zulip App": "Zulip App के लिए DevTools टॉगल करें",
"Toggle Do Not Disturb": "टॉगल डू नॉट डिस्टर्ब",
"Toggle DevTools for Zulip App": "Zulip ऐप के लिए DevTools टॉगल करें",
"Toggle Full Screen": "पूर्णस्क्रीन चालू करें",
"Toggle Sidebar": "टॉगल साइडबार",
"Toggle Tray Icon": "टॉगल ट्रे आइकन",
"Tools": "उपकरण",
"Toggle Sidebar": "साइडबार टॉगल करें",
"Toggle Tray Icon": "ट्रे आइकन टॉगल करें",
"Undo": "पूर्ववत करें",
"Upload": "अपलोड",
"Use system proxy settings (requires restart)": "सिस्टम प्रॉक्सी सेटिंग्स का उपयोग करें (पुनः आरंभ करने की आवश्यकता है)",
"View": "राय",
"View Shortcuts": "शॉर्टकट देखें",
"Window": "खिड़की",
"Window Shortcuts": "विंडो शॉर्टकट",
"YES": "हाँ",
"Zoom In": "ज़ूम इन",
"Zoom Out": "ज़ूम आउट",
"Zulip Help": "Zulip मदद",
"keyboard shortcuts": "कुंजीपटल अल्प मार्ग",
"script": "लिपि",
"AddServer": "AddServer"
"Zulip Help": "Zulip सहायता"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "Zulipról",
"About Zulip": "A Zulipról",
"Actual Size": "Valódi méret",
"Add Custom Certificates": "Egyéni tanúsítványok hozzáadása",
"Add Organization": "Szervezet hozzáadása",
"Add a Zulip organization": "Adjon hozzá egy Zulip szervezetet",
"Add custom CSS": "Egyéni CSS hozzáadása",
"Advanced": "Fejlett",
"All the connected organizations will appear here": "Itt megjelenik az összes kapcsolódó szervezet",
"Always start minimized": "Mindig kezdjen minimálisra",
"App Updates": "Alkalmazásfrissítések",
"Appearance": "Megjelenés",
"Application Shortcuts": "Alkalmazás parancsikonok",
"Are you sure you want to disconnect this organization?": "Biztosan le szeretné szüntetni ezt a szervezetet?",
"Auto hide Menu bar": "Automatikus elrejtése Menüsor",
"Auto hide menu bar (Press Alt key to display)": "Automatikus elrejtés a menüsorban (nyomja meg az Alt billentyűt a megjelenítéshez)",
"Back": "Hát",
"Bounce dock on new private message": "Ugrás a dokkolót az új privát üzenetre",
"Certificate file": "Tanúsítványfájl",
"Change": "változás",
"Check for Updates": "Frissítések keresése",
"Close": "Bezárás",
"Connect": "Csatlakozás",
"Connect to another organization": "Csatlakozás egy másik szervezethez",
"Connected organizations": "Kapcsolódó szervezetek",
"Copy": "Másolat",
"Copy Zulip URL": "Zulip URL másolása",
"Create a new organization": "Hozzon létre egy új szervezetet",
"Cut": "Vágott",
"Default download location": "Alapértelmezett letöltési hely",
"Delete": "Töröl",
"Desktop App Settings": "Asztali alkalmazás beállításai",
"Desktop Notifications": "Asztali értesítések",
"Desktop Settings": "Asztali beállítások",
"Disconnect": "szétkapcsol",
"Download App Logs": "Alkalmazásnaplók letöltése",
"Desktop App Settings": "Asztali alkalmazások beállításai",
"Edit": "szerkesztése",
"Edit Shortcuts": "Gyorsbillentyűk szerkesztése",
"Enable auto updates": "Automatikus frissítések engedélyezése",
"Enable error reporting (requires restart)": "Hibajelentés engedélyezése (újraindítás szükséges)",
"Enable spellchecker (requires restart)": "Helyesírás-ellenőrző engedélyezése (újraindítás szükséges)",
"Factory Reset": "Gyári beállítások visszaállítása",
"File": "fájl",
"Find accounts": "Fiókok keresése",
"Find accounts by email": "Fiókok keresése e-mailben",
"Flash taskbar on new message": "Flash-tálca az új üzeneten",
"Forward": "Előre",
"Functionality": "funkcionalitás",
"General": "Tábornok",
"Get beta updates": "Béta frissítéseket kap",
"Hard Reload": "Hard Reload",
"Help": "Segítség",
"Help Center": "Segítség Központ",
"History": "Történelem",
"History Shortcuts": "Előzmények parancsikonjai",
"Keyboard Shortcuts": "Gyorsbillentyűket",
"Log Out": "Kijelentkezés",
"Log Out of Organization": "Jelentkezzen ki a szervezetből",
"Manual proxy configuration": "Manuális proxy konfiguráció",
"Minimize": "Kis méret",
"Mute all sounds from Zulip": "A Zulip összes hangjának elnémítása",
"NO": "NEM",
"Network": "Hálózat",
"OR": "VAGY",
"Organization URL": "A szervezet URL-címe",
"Organizations": "szervezetek",
"Paste": "Paszta",
"Paste and Match Style": "Beillesztés és egyezés stílusa",
"Proxy": "Meghatalmazott",
"Proxy bypass rules": "Proxy bypass szabályok",
"Proxy rules": "Proxy szabályok",
"Paste and Match Style": "Beillesztés és stílusok illesztése",
"Quit": "quit",
"Quit Zulip": "Lépjen ki Zulipból",
"Redo": "Újra",
"Release Notes": "Kiadási megjegyzések",
"Reload": "Reload",
"Report an Issue": "Jelentés bejelentése",
"Reset App Data": "Az App Data visszaállítása",
"Reset App Settings": "Alkalmazásbeállítások visszaállítása",
"Reset Application Data": "Alkalmazásadatok visszaállítása",
"Save": "Mentés",
"Report an issue...": "Hibajelentés ...",
"Reset App Settings": "Az alkalmazás beállításainak visszaállítása",
"Select All": "Mindet kiválaszt",
"Settings": "Beállítások",
"Shortcuts": "parancsikonok",
"Show App Logs": "Alkalmazásnaplók megjelenítése",
"Show app icon in system tray": "Az alkalmazás ikonjának megjelenítése a tálcán",
"Show app unread badge": "Az alkalmazás olvasatlan jelvényének megjelenítése",
"Show desktop notifications": "Az asztali értesítések megjelenítése",
"Show downloaded files in file manager": "A letöltött fájlok megjelenítése a fájlkezelőben",
"Show sidebar": "Az oldalsáv megjelenítése",
"Start app at login": "Indítsa el az alkalmazást bejelentkezéskor",
"Switch to Next Organization": "Váltás a következő szervezetre",
"Switch to Previous Organization": "Váltás az előző szervezetre",
"These desktop app shortcuts extend the Zulip webapp's": "Ezek az asztali alkalmazás gyorsbillentyűk kiterjesztik a Zulip webapp-ot",
"This will delete all application data including all added accounts and preferences": "Ez törli az összes alkalmazásadatot, beleértve az összes hozzáadott fiókot és beállítást",
"Tip": "Tipp",
"Toggle DevTools for Active Tab": "Váltás DevTools az aktív fülre",
"Toggle DevTools for Zulip App": "A DevTools váltása a Zulip App számára",
"Toggle Do Not Disturb": "Váltás Ne zavarja",
"Toggle DevTools for Active Tab": "A DevTools aktiválása az Aktív laphoz",
"Toggle DevTools for Zulip App": "Kapcsolja a DevTools-ot a Zulip App-hoz",
"Toggle Full Screen": "Teljes képernyőre váltás",
"Toggle Sidebar": "Az oldalsáv átkapcsolása",
"Toggle Tray Icon": "Átváltás a tálca ikonjára",
"Tools": "Eszközök",
"Toggle Sidebar": "Oldalsáv átkapcsolása",
"Toggle Tray Icon": "Tálcaikon átkapcsolása",
"Undo": "kibont",
"Upload": "Feltöltés",
"Use system proxy settings (requires restart)": "Rendszer proxy beállításainak használata (újraindítás szükséges)",
"View": "Kilátás",
"View Shortcuts": "Gyorsbillentyűk megtekintése",
"Window": "Ablak",
"Window Shortcuts": "Ablak gyorsbillentyűk",
"YES": "IGEN",
"Zoom In": "Ráközelíteni",
"Zoom In": "Nagyítás",
"Zoom Out": "Kicsinyítés",
"Zulip Help": "Zulip Súgó",
"keyboard shortcuts": "gyorsbillentyűket",
"script": "forgatókönyv"
"Zulip Help": "Zulip Súgó"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "Tentang Zulip",
"Actual Size": "Ukuran sebenarnya",
"Add Custom Certificates": "Tambahkan Sertifikat Kustom",
"Add Organization": "Tambahkan Organisasi",
"Add a Zulip organization": "Tambahkan organisasi Zulip",
"Add custom CSS": "Tambahkan CSS khusus",
"Advanced": "Maju",
"All the connected organizations will appear here": "Semua organisasi yang terhubung akan muncul di sini",
"Always start minimized": "Selalu mulai diminimalkan",
"App Updates": "Pembaruan Aplikasi",
"Appearance": "Penampilan",
"Application Shortcuts": "Pintasan aplikasi",
"Are you sure you want to disconnect this organization?": "Apakah Anda yakin ingin memutuskan koneksi organisasi ini?",
"Auto hide Menu bar": "Sembunyikan bilah Menu secara otomatis",
"Auto hide menu bar (Press Alt key to display)": "Sembunyikan bilah menu secara otomatis (Tekan tombol Alt untuk menampilkan)",
"Back": "Kembali",
"Bounce dock on new private message": "Bounce dock pada pesan pribadi baru",
"Certificate file": "File sertifikat",
"Change": "Perubahan",
"Check for Updates": "Periksa Pembaruan",
"Close": "Dekat",
"Connect": "Menghubungkan",
"Connect to another organization": "Terhubung ke organisasi lain",
"Connected organizations": "Organisasi yang terhubung",
"Copy": "Salinan",
"Copy Zulip URL": "Salin URL Zulip",
"Create a new organization": "Buat organisasi baru",
"Cut": "Memotong",
"Default download location": "Lokasi unduhan default",
"Delete": "Menghapus",
"Desktop App Settings": "Pengaturan Aplikasi Desktop",
"Desktop Notifications": "Pemberitahuan Desktop",
"Desktop Settings": "Pengaturan Desktop",
"Disconnect": "Memutuskan",
"Download App Logs": "Unduh Log Aplikasi",
"Desktop App Settings": "Setelan Aplikasi Desktop",
"Edit": "Edit",
"Edit Shortcuts": "Edit Pintasan",
"Enable auto updates": "Aktifkan pembaruan otomatis",
"Enable error reporting (requires restart)": "Aktifkan pelaporan kesalahan (membutuhkan restart)",
"Enable spellchecker (requires restart)": "Aktifkan pemeriksa ejaan (memerlukan mulai ulang)",
"Factory Reset": "Reset Pabrik",
"File": "Mengajukan",
"Find accounts": "Temukan akun",
"Find accounts by email": "Temukan akun melalui email",
"Flash taskbar on new message": "Taskbar flash pada pesan baru",
"Forward": "Meneruskan",
"Functionality": "Fungsionalitas",
"General": "Umum",
"Get beta updates": "Dapatkan pembaruan beta",
"Hard Reload": "Hard Reload",
"Help": "Membantu",
"Help Center": "Pusat Bantuan",
"History": "Sejarah",
"History Shortcuts": "Pintasan Riwayat",
"Keyboard Shortcuts": "Pintasan keyboard",
"Keyboard Shortcuts": "Cara pintas keyboard",
"Log Out": "Keluar",
"Log Out of Organization": "Keluar dari Organisasi",
"Manual proxy configuration": "Konfigurasi proxy manual",
"Minimize": "Memperkecil",
"Mute all sounds from Zulip": "Bungkam semua suara dari Zulip",
"NO": "TIDAK",
"Network": "Jaringan",
"OR": "ATAU",
"Organization URL": "URL organisasi",
"Organizations": "Organisasi",
"Paste": "Pasta",
"Paste and Match Style": "Tempel dan Cocokkan Gaya",
"Proxy": "Proksi",
"Proxy bypass rules": "Aturan memotong proxy",
"Proxy rules": "Aturan proxy",
"Quit": "Berhenti",
"Quit Zulip": "Berhenti Zulip",
"Redo": "Mengulangi",
"Release Notes": "Catatan Rilis",
"Reload": "Muat ulang",
"Report an Issue": "Laporkan sebuah Masalah",
"Reset App Data": "Setel Ulang Data Aplikasi",
"Reset App Settings": "Setel Ulang Pengaturan Aplikasi",
"Reset Application Data": "Reset Data Aplikasi",
"Save": "Menyimpan",
"Reload": "Reload",
"Report an issue...": "Laporkan masalah ...",
"Reset App Settings": "Setel ulang Pengaturan Aplikasi",
"Select All": "Pilih Semua",
"Settings": "Pengaturan",
"Shortcuts": "Pintasan",
"Show App Logs": "Tampilkan Log Aplikasi",
"Show app icon in system tray": "Tampilkan ikon aplikasi di baki sistem",
"Show app unread badge": "Tampilkan lencana aplikasi yang belum dibaca",
"Show desktop notifications": "Tampilkan pemberitahuan desktop",
"Show downloaded files in file manager": "Tampilkan file yang diunduh dalam pengelola file",
"Show sidebar": "Tampilkan bilah sisi",
"Start app at login": "Mulai aplikasi saat masuk",
"Switch to Next Organization": "Beralih ke Organisasi Selanjutnya",
"Switch to Previous Organization": "Beralih ke Organisasi Sebelumnya",
"These desktop app shortcuts extend the Zulip webapp's": "Pintasan aplikasi desktop ini memperpanjang aplikasi web Zulip",
"This will delete all application data including all added accounts and preferences": "Ini akan menghapus semua data aplikasi termasuk semua akun dan preferensi yang ditambahkan",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Beralih DevTools untuk Tab Aktif",
"Toggle DevTools for Zulip App": "Beralih DevTools untuk Aplikasi Zulip",
"Toggle Do Not Disturb": "Toggle Jangan Ganggu",
"Toggle Full Screen": "Matikan Layar Penuh",
"Toggle Sidebar": "Beralih Bilah Samping",
"Toggle Tray Icon": "Beralih Ikon Baki",
"Tools": "Alat",
"Undo": "Batalkan",
"Upload": "Unggah",
"Use system proxy settings (requires restart)": "Gunakan pengaturan proxy sistem (perlu dihidupkan ulang)",
"Show App Logs": "Tampilkan log aplikasi",
"Toggle DevTools for Active Tab": "Toggle DevTools untuk Tab Aktif",
"Toggle DevTools for Zulip App": "Toggle DevTools untuk Aplikasi Zulip",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Undo": "Membuka",
"View": "Melihat",
"View Shortcuts": "Lihat Pintasan",
"Window": "Jendela",
"Window Shortcuts": "Pintasan Jendela",
"YES": "IYA NIH",
"Zoom In": "Perbesar",
"Zoom Out": "Perkecil",
"Zulip Help": "Bantuan Zulip",
"keyboard shortcuts": "pintasan keyboard",
"script": "naskah"
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "Zulipについて",
"Actual Size": "実",
"Add Custom Certificates": "カスタム証明書を追加する",
"Add Organization": "組織を追加",
"Add a Zulip organization": "Zulip組織を追加する",
"Add custom CSS": "カスタムCSSを追加する",
"Advanced": "高度な",
"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キーを押して表示",
"About Zulip": "チューリップについて",
"Actual Size": "実際のサイズ",
"Back": "バック",
"Bounce dock on new private message": "新しいプライベートメッセージでバウンスドック",
"Certificate file": "証明書ファイル",
"Change": "変化する",
"Check for Updates": "アップデートを確認",
"Close": "閉じる",
"Connect": "接続する",
"Connect to another organization": "他の組織に接続する",
"Connected organizations": "関連組織",
"Copy": "コピーする",
"Copy Zulip URL": "Zulip URLをコピー",
"Create a new organization": "新しい組織を作る",
"Copy": "コピー",
"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": "工場リセット",
"Delete": "削除",
"Desktop App Settings": "デスクトップアプリケーションの設定",
"Edit": "編集",
"File": "ファイル",
"Find accounts": "アカウントを探す",
"Find accounts by email": "メールでアカウントを探す",
"Flash taskbar on new message": "新しいメッセージのフラッシュタスクバー",
"Forward": "進む",
"Functionality": "機能性",
"General": "一般的な",
"Get beta updates": "ベータ版のアップデートを入手",
"Forward": "フォワード",
"Hard Reload": "ハードリロード",
"Help": "助けて",
"Help Center": "ヘルプセンター",
"History": "歴史",
"History Shortcuts": "履歴ショートカット",
"Keyboard Shortcuts": "キーボードショートカット",
"Log Out": "ログアウト",
"Log Out of Organization": "組織からログアウトする",
"Manual proxy configuration": "手動プロキシ設定",
"Minimize": "最小化",
"Mute all sounds from Zulip": "Zulipからのすべてのサウンドをミュート",
"NO": "いいえ",
"Network": "ネットワーク",
"OR": "または",
"Organization URL": "組織のURL",
"Organizations": "組織",
"Minimize": "最小化する",
"Paste": "ペースト",
"Paste and Match Style": "貼り付けスタイル",
"Proxy": "代理人",
"Proxy bypass rules": "プロキシバイパスルール",
"Proxy rules": "プロキシルール",
"Paste and Match Style": "スタイルの貼り付けと一致",
"Quit": "終了する",
"Quit Zulip": "Zulipを終了します",
"Redo": "やり直し",
"Release Notes": "リリースノート",
"Reload": "リロード",
"Report an Issue": "問題を報告する",
"Reset App Data": "アプリデータをリセット",
"Reset App Settings": "アプリ設定をリセット",
"Reset Application Data": "アプリケーションデータのリセット",
"Save": "保存する",
"Report an issue...": "問題を報告する...",
"Reset App Settings": "アプリの設定をリセットする",
"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を切り替える",
"Toggle DevTools for Zulip App": "Zulip App用DevToolsの切り替え",
"Toggle Do Not Disturb": "邪魔しないでください",
"Show App Logs": "アプリケーションログを表示する",
"Toggle DevTools for Active Tab": "DevTools for Activeタブを切り替える",
"Toggle DevTools for Zulip App": "Zulip AppのDevToolsの切り替え",
"Toggle Full Screen": "フルスクリーン切り替え",
"Toggle Sidebar": "サイドバーの切り替え",
"Toggle Tray Icon": "トレイアイコン切り替え",
"Tools": "道具",
"Toggle Tray Icon": "トレイアイコン切り替え",
"Undo": "元に戻す",
"Upload": "アップロードする",
"Use system proxy settings (requires restart)": "システムプロキシ設定を使用する(再起動が必要)",
"View": "見る",
"View Shortcuts": "ショートカットを見る",
"View": "ビュー",
"Window": "窓",
"Window Shortcuts": "ウィンドウショートカット",
"YES": "はい",
"Zoom In": "ズームイン",
"Zoom Out": "ズームアウトする",
"Zulip Help": "Zulipヘルプ",
"keyboard shortcuts": "キーボードショートカット",
"script": "スクリプト"
"Zulip Help": "チューリップヘルプ"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "튤립에 관하여",
"About Zulip": "튤립 소개",
"Actual Size": "실제 크기",
"Add Custom Certificates": "사용자 지정 인증서 추가",
"Add Organization": "조직 추가",
"Add a Zulip organization": "튤립 조직 추가",
"Add custom CSS": "맞춤 CSS 추가",
"Advanced": "많은",
"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": "새 개인 메시지에 도약",
"Certificate file": "인증서 파일",
"Change": "변화",
"Check for Updates": "업데이트 확인",
"Close": "닫기",
"Connect": "잇다",
"Connect to another organization": "다른 조직에 연결",
"Connected organizations": "연결된 조직",
"Copy": "부",
"Copy Zulip URL": "튤립 URL 복사",
"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": "이메일로 계정 찾기",
"Flash taskbar on new message": "새 메시지의 Flash 작업 표시 줄",
"Forward": "앞으로",
"Functionality": "기능",
"General": "일반",
"Get beta updates": "베타 업데이트 받기",
"Hard Reload": "하드 다시로드",
"Help": "도움",
"Help Center": "지원 센터",
"History": "역사",
"History Shortcuts": "연혁 단축키",
"Keyboard Shortcuts": "키보드 단축키",
"Log Out": "로그 아웃",
"Log Out of Organization": "조직에서 로그 아웃",
"Manual proxy configuration": "수동 프록시 구성",
"Minimize": "최소화",
"Mute all sounds from Zulip": "튤립에서 모든 소리를 음소거합니다.",
"NO": "아니",
"Network": "회로망",
"OR": "또는",
"Organization URL": "조직 URL",
"Organizations": "단체",
"Paste": "풀",
"Paste and Match Style": "붙여 넣기 및 스타일 일치",
"Proxy": "대리",
"Proxy bypass rules": "프록시 우회 규칙",
"Proxy rules": "프록시 규칙",
"Quit": "떠나다",
"Quit Zulip": "튤립을 종료하십시오.",
"Redo": "다시 하다",
"Release Notes": "릴리즈 노트",
"Reload": "다시로드",
"Report an Issue": "문제 신고",
"Reset App Data": "앱 데이터 재설정",
"Report an issue...": "문제 신고 ...",
"Reset App Settings": "앱 설정 재설정",
"Reset Application Data": "응용 프로그램 데이터 재설정",
"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 웹 앱을 확장합니다.",
"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": "Zulip App 용 DevTools 토글",
"Toggle Do Not Disturb": "방해 금지 전환",
"Toggle Full Screen": "전체 화면 토글",
"Toggle Sidebar": "사이드 바 전환",
"Toggle Tray Icon": "트레이 아이콘 토글",
"Tools": "도구들",
"Undo": "끄르다",
"Upload": "업로드",
"Use system proxy settings (requires restart)": "시스템 프록시 설정 사용 (다시 시작해야 함)",
"View": "전망",
"View Shortcuts": "바로 가기보기",
"Window": "창문",
"Window Shortcuts": "창 바로 가기",
"YES": "예",
"Zoom In": "확대",
"Zoom Out": "축소",
"Zulip Help": "튤립 도움말",
"keyboard shortcuts": "키보드 단축키",
"script": "스크립트"
"Zulip Help": "튤립 도움말"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "സുലിപ്പിനെക്കുറിച്ച്",
"About Zulip": "സുലിപ്",
"Actual Size": "യഥാർത്ഥ വലുപ്പം",
"Add Custom Certificates": "ഇഷ്‌ടാനുസൃത സർട്ടിഫിക്കറ്റുകൾ ചേർക്കുക",
"Add Organization": "ഓർഗനൈസേഷൻ ചേർക്കുക",
"Add a Zulip organization": "ഒരു സുലിപ്പ് ഓർഗനൈസേഷൻ ചേർക്കുക",
"Add custom CSS": "ഇഷ്‌ടാനുസൃത CSS ചേർക്കുക",
"Advanced": "വിപുലമായത്",
"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": "പുതിയ സ്വകാര്യ സന്ദേശത്തിൽ ഡോക്ക് ബൗൺസ് ചെയ്യുക",
"Certificate file": "സർട്ടിഫിക്കറ്റ് ഫയൽ",
"Change": "മാറ്റുക",
"Check for Updates": "അപ്‌ഡേറ്റുകൾക്കായി പരിശോധിക്കുക",
"Close": "അടയ്‌ക്കുക",
"Connect": "ബന്ധിപ്പിക്കുക",
"Connect to another organization": "മറ്റൊരു ഓർഗനൈസേഷനിലേക്ക് കണക്റ്റുചെയ്യുക",
"Connected organizations": "ബന്ധിപ്പിച്ച ഓർഗനൈസേഷനുകൾ",
"Close": "അടയ്ക്കുക",
"Copy": "പകർത്തുക",
"Copy Zulip URL": "Zulip URL പകർത്തുക",
"Create a new organization": "ഒരു പുതിയ ഓർഗനൈസേഷൻ സൃഷ്ടിക്കുക",
"Cut": "മുറിക്കുക",
"Default download location": "സ്ഥിരസ്ഥിതി ഡ download ൺ‌ലോഡ് സ്ഥാനം",
"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": "ഇമെയിൽ വഴി അക്കൗണ്ടുകൾ കണ്ടെത്തുക",
"Flash taskbar on new message": "പുതിയ സന്ദേശത്തിൽ ഫ്ലാഷ് ടാസ്‌ക്ബാർ",
"Forward": "ഫോർവേഡ് ചെയ്യുക",
"Functionality": "പ്രവർത്തനം",
"General": "ജനറൽ",
"Get beta updates": "ബീറ്റ അപ്‌ഡേറ്റുകൾ നേടുക",
"Forward": "മുന്നോട്ട്",
"Hard Reload": "ഹാർഡ് റീലോഡ്",
"Help": "സഹായിക്കൂ",
"Help Center": "സഹായകേന്ദ്രം",
"History": "ചരിത്രം",
"History Shortcuts": "ചരിത്രം കുറുക്കുവഴികൾ",
"Keyboard Shortcuts": "കീബോർഡ് കുറുക്കുവഴികൾ",
"Log Out": "ലോഗ് .ട്ട് ചെയ്യുക",
"Log Out of Organization": "ഓർഗനൈസേഷനിൽ നിന്ന് പുറത്തുകടക്കുക",
"Manual proxy configuration": "സ്വമേധയാലുള്ള പ്രോക്സി കോൺഫിഗറേഷൻ",
"Log Out": "ലോഗ് ട്ട്",
"Minimize": "ചെറുതാക്കുക",
"Mute all sounds from Zulip": "സുലിപ്പിൽ നിന്നുള്ള എല്ലാ ശബ്ദങ്ങളും നിശബ്ദമാക്കുക",
"NO": "ഇല്ല",
"Network": "നെറ്റ്‌വർക്ക്",
"OR": "അഥവാ",
"Organization URL": "ഓർ‌ഗനൈസേഷൻ‌ URL",
"Organizations": "ഓർഗനൈസേഷനുകൾ",
"Paste": "പേസ്റ്റ്",
"Paste and Match Style": "ഒട്ടിച്ച് പൊരുത്തപ്പെടുന്ന ശൈലി",
"Proxy": "പ്രോക്സി",
"Proxy bypass rules": "പ്രോക്സി ബൈപാസ് നിയമങ്ങൾ",
"Proxy rules": "പ്രോക്സി നിയമങ്ങൾ",
"Quit": "ഉപേക്ഷിക്കുക",
"Quit Zulip": "സുലിപ്പ് ഉപേക്ഷിക്കുക",
"Paste and Match Style": "ശൈലി ഒട്ടിക്കുകയും പൊരുത്തപ്പെടുത്തുകയും ചെയ്യുക",
"Quit": "പുറത്തുകടക്കുക",
"Redo": "വീണ്ടും ചെയ്യുക",
"Release Notes": "പ്രകാശന കുറിപ്പുകൾ",
"Reload": "വീണ്ടും ലോഡുചെയ്യുക",
"Report an Issue": "ഒരു പ്രശ്നം റിപ്പോർട്ടചെയ്യുക",
"Reset App Data": "അപ്ലിക്കേഷൻ ഡാറ്റ പുന et സജ്ജമാക്കുക",
"Reset App Settings": "അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങൾ പുന et സജ്ജമാക്കുക",
"Reset Application Data": "അപ്ലിക്കേഷൻ ഡാറ്റ പുന et സജ്ജമാക്കുക",
"Save": "രക്ഷിക്കും",
"Report an issue...": "ഒരു പ്രശ്നം റിപ്പോർട്ടചെയ്യുക ...",
"Reset App Settings": "അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങൾ പുനഃസജ്ജമാക്കുക",
"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": "ഫയൽ മാനേജറിൽ ഡ download ൺലോഡ് ചെയ്ത ഫയലുകൾ കാണിക്കുക",
"Show sidebar": "സൈഡ്‌ബാർ കാണിക്കുക",
"Start app at login": "ലോഗിൻ ചെയ്യുമ്പോൾ അപ്ലിക്കേഷൻ ആരംഭിക്കുക",
"Switch to Next Organization": "അടുത്ത ഓർഗനൈസേഷനിലേക്ക് മാറുക",
"Switch to Previous Organization": "മുമ്പത്തെ ഓർഗനൈസേഷനിലേക്ക് മാറുക",
"These desktop app shortcuts extend the Zulip webapp's": "ഈ ഡെസ്ക്ടോപ്പ് അപ്ലിക്കേഷൻ കുറുക്കുവഴികൾ സുലിപ് വെബ്‌അപ്പിനെ വിപുലീകരിക്കുന്നു",
"This will delete all application data including all added accounts and preferences": "ചേർത്ത എല്ലാ അക്കൗണ്ടുകളും മുൻ‌ഗണനകളും ഉൾപ്പെടെ എല്ലാ ആപ്ലിക്കേഷൻ ഡാറ്റയും ഇത് ഇല്ലാതാക്കും",
"Tip": "നുറുങ്ങ്",
"Toggle DevTools for Active Tab": "സജീവ ടാബിനായി DevTools ടോഗിൾ ചെയ്യുക",
"Toggle DevTools for Zulip App": "Zulip അപ്ലിക്കേഷനായി DevTools ടോഗിൾ ചെയ്യുക",
"Toggle Do Not Disturb": "ശല്യപ്പെടുത്തരുത് ടോഗിൾ ചെയ്യുക",
"Toggle Full Screen": "പൂർണ്ണ സ്‌ക്രീൻ ടോഗിൾ ചെയ്യുക",
"Toggle Sidebar": "സൈഡ്‌ബാർ ടോഗിൾ ചെയ്യുക",
"Toggle DevTools for Zulip App": "സുലിപ്പ് ആപ്ലിക്കേഷനായി DevTools ടോഗിൾ ചെയ്യുക",
"Toggle Full Screen": "പൂർണ്ണ സ്ക്രീൻ ടോഗിൾ ചെയ്യുക",
"Toggle Sidebar": "സൈഡ്ബാർ ടോഗിൾ ചെയ്യുക",
"Toggle Tray Icon": "ട്രേ ഐക്കൺ ടോഗിൾ ചെയ്യുക",
"Tools": "ഉപകരണങ്ങൾ",
"Undo": "പഴയപടിയാക്കുക",
"Upload": "അപ്‌ലോഡുചെയ്യുക",
"Use system proxy settings (requires restart)": "സിസ്റ്റം പ്രോക്സി ക്രമീകരണങ്ങൾ ഉപയോഗിക്കുക (പുനരാരംഭിക്കേണ്ടതുണ്ട്)",
"View": "കാണുക",
"View Shortcuts": "കുറുക്കുവഴികൾ കാണുക",
"Window": "ജാലകം",
"Window Shortcuts": "വിൻഡോ കുറുക്കുവഴികൾ",
"YES": "അതെ",
"Zoom In": "വലുതാക്കുക",
"Zoom Out": "സൂം .ട്ട് ചെയ്യുക",
"Zulip Help": "സുലിപ് സഹായം",
"keyboard shortcuts": "കീബോർഡ് കുറുക്കുവഴികൾ",
"script": "സ്ക്രിപ്റ്റ്"
"Zoom Out": "സൂം ട്ട്",
"Zulip Help": "സുലിപ്പ് സഹായം"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "Over Zulip",
"Actual Size": "Ware grootte",
"Add Custom Certificates": "Aangepaste certificaten toevoegen",
"Add Organization": "Voeg organisatie toe",
"Add a Zulip organization": "Voeg een Zulip-organisatie toe",
"Add custom CSS": "Voeg aangepaste CSS toe",
"Advanced": "gevorderd",
"All the connected organizations will appear here": "Alle verbonden organisaties verschijnen hier",
"Always start minimized": "Begin altijd geminimaliseerd",
"App Updates": "App-updates",
"Appearance": "Verschijning",
"Application Shortcuts": "Applicatiesnelkoppelingen",
"Are you sure you want to disconnect this organization?": "Weet je zeker dat je deze organisatie wilt ontkoppelen?",
"Auto hide Menu bar": "Menubalk automatisch verbergen",
"Auto hide menu bar (Press Alt key to display)": "Menubalk automatisch verbergen (druk op de Alt-toets om weer te geven)",
"Actual Size": "Daadwerkelijke grootte",
"Back": "Terug",
"Bounce dock on new private message": "Bounce dock op nieuw privébericht",
"Certificate file": "Certificaatbestand",
"Change": "Verandering",
"Check for Updates": "Controleer op updates",
"Close": "Dichtbij",
"Connect": "Aansluiten",
"Connect to another organization": "Maak verbinding met een andere organisatie",
"Connected organizations": "Verbonden organisaties",
"Copy": "Kopiëren",
"Copy Zulip URL": "Kopieer Zulip-URL",
"Create a new organization": "Maak een nieuwe organisatie",
"Cut": "Besnoeiing",
"Default download location": "Standaard downloadlocatie",
"Delete": "Verwijder",
"Desktop App Settings": "Desktop-app-instellingen",
"Desktop Notifications": "Bureaublad notificaties",
"Desktop Settings": "Desktop-instellingen",
"Disconnect": "Loskoppelen",
"Download App Logs": "Applogs downloaden",
"Edit": "Bewerk",
"Edit Shortcuts": "Bewerk snelkoppelingen",
"Enable auto updates": "Schakel automatische updates in",
"Enable error reporting (requires restart)": "Foutrapportage inschakelen (opnieuw opstarten vereist)",
"Enable spellchecker (requires restart)": "Spellingcontrole inschakelen (opnieuw opstarten vereist)",
"Factory Reset": "Fabrieksinstellingen",
"File": "het dossier",
"Find accounts": "Vind accounts",
"Find accounts by email": "Vind accounts per e-mail",
"Flash taskbar on new message": "Flash-taakbalk voor nieuw bericht",
"Forward": "Vooruit",
"Functionality": "functionaliteit",
"General": "Algemeen",
"Get beta updates": "Ontvang bèta-updates",
"Hard Reload": "Harde herladen",
"Hard Reload": "Moeilijke herladen",
"Help": "Helpen",
"Help Center": "Helpcentrum",
"History": "Geschiedenis",
"History Shortcuts": "Geschiedenis Sneltoetsen",
"Keyboard Shortcuts": "Toetsenbord sneltoetsen",
"Log Out": "Uitloggen",
"Log Out of Organization": "Uitloggen van organisatie",
"Manual proxy configuration": "Handmatige proxyconfiguratie",
"Minimize": "verkleinen",
"Mute all sounds from Zulip": "Demp alle geluiden van Zulip",
"NO": "NEE",
"Network": "Netwerk",
"OR": "OF",
"Organization URL": "Organisatie-URL",
"Organizations": "organisaties",
"Paste": "Pasta",
"Paste": "Plakken",
"Paste and Match Style": "Plak en match stijl",
"Proxy": "volmacht",
"Proxy bypass rules": "Proxy-bypassregels",
"Proxy rules": "Proxy-regels",
"Quit": "ophouden",
"Quit Zulip": "Sluit Zulip",
"Redo": "Opnieuw doen",
"Release Notes": "Releaseopmerkingen",
"Reload": "vernieuwen",
"Report an Issue": "Een probleem melden",
"Reset App Data": "App-gegevens opnieuw instellen",
"Report an issue...": "Een probleem melden...",
"Reset App Settings": "App-instellingen opnieuw instellen",
"Reset Application Data": "Herstel toepassingsgegevens",
"Save": "Opslaan",
"Select All": "Selecteer alles",
"Settings": "instellingen",
"Shortcuts": "shortcuts",
"Show App Logs": "App-logs weergeven",
"Show app icon in system tray": "App-pictogram weergeven in systeemvak",
"Show app unread badge": "App ongelezen badge weergeven",
"Show desktop notifications": "Toon bureaubladmeldingen",
"Show downloaded files in file manager": "Laat gedownloade bestanden zien in bestandsbeheer",
"Show sidebar": "Toon zijbalk",
"Start app at login": "Start de app bij inloggen",
"Switch to Next Organization": "Schakel over naar volgende organisatie",
"Switch to Previous Organization": "Schakel over naar vorige organisatie",
"These desktop app shortcuts extend the Zulip webapp's": "Deze sneltoetsen voor bureaubladapp breiden de Zulip-webapp's uit",
"This will delete all application data including all added accounts and preferences": "Hiermee worden alle applicatiegegevens verwijderd, inclusief alle toegevoegde accounts en voorkeuren",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "DevTools voor actieve tabblad omschakelen",
"Toggle DevTools for Zulip App": "DevTools voor Zulip-app omschakelen",
"Toggle Do Not Disturb": "Schakel Niet storen in",
"Toggle Full Screen": "Volledig scherm activeren",
"Toggle Sidebar": "Zijbalk verschuiven",
"Toggle Tray Icon": "Pictogram Lade wisselen",
"Tools": "Hulpmiddelen",
"Undo": "ongedaan maken",
"Upload": "Uploaden",
"Use system proxy settings (requires restart)": "Systeem proxy-instellingen gebruiken (opnieuw opstarten vereist)",
"View": "Uitzicht",
"View Shortcuts": "Bekijk snelkoppelingen",
"Window": "Venster",
"Window Shortcuts": "Venster snelkoppelingen",
"YES": "JA",
"Zoom In": "In zoomen",
"Zoom Out": "Uitzoomen",
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "Toetsenbord sneltoetsen",
"script": "script"
"Zulip Help": "Zulip Help"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "O Zulipie",
"About Zulip": "O Zulip",
"Actual Size": "Rzeczywisty rozmiar",
"Add Custom Certificates": "Dodaj niestandardowe certyfikaty",
"Add Organization": "Dodaj organizację",
"Add a Zulip organization": "Dodaj organizację Zulip",
"Add custom CSS": "Dodaj niestandardowy CSS",
"Advanced": "zaawansowane",
"All the connected organizations will appear here": "Tutaj pojawią się wszystkie połączone organizacje",
"Always start minimized": "Zawsze zaczynaj zminimalizowany",
"App Updates": "Aktualizacje aplikacji",
"Appearance": "Wygląd",
"Application Shortcuts": "Skróty do aplikacji",
"Are you sure you want to disconnect this organization?": "Czy na pewno chcesz odłączyć tę organizację?",
"Auto hide Menu bar": "Auto hide Pasek menu",
"Auto hide menu bar (Press Alt key to display)": "Pasek menu automatycznego ukrywania (naciśnij klawisz Alt, aby wyświetlić)",
"Back": "Z powrotem",
"Bounce dock on new private message": "Dok odbijania na nowej prywatnej wiadomości",
"Certificate file": "Plik certyfikatu",
"Change": "Zmiana",
"Check for Updates": "Sprawdź aktualizacje",
"Back": "Plecy",
"Close": "Blisko",
"Connect": "Połączyć",
"Connect to another organization": "Połącz się z inną organizacją",
"Connected organizations": "Związane organizacje",
"Copy": "Kopiuj",
"Copy Zulip URL": "Skopiuj adres URL Zulip",
"Create a new organization": "Utwórz nową organizację",
"Cut": "Ciąć",
"Default download location": "Domyślna lokalizacja pobierania",
"Delete": "Kasować",
"Desktop App Settings": "Ustawienia aplikacji komputerowej",
"Desktop Notifications": "Powiadomienia na pulpicie",
"Desktop Settings": "Ustawienia pulpitu",
"Disconnect": "Rozłączyć się",
"Download App Logs": "Pobierz dzienniki aplikacji",
"Desktop App Settings": "Ustawienia aplikacji na komputer",
"Edit": "Edytować",
"Edit Shortcuts": "Edytuj skróty",
"Enable auto updates": "Włącz automatyczne aktualizacje",
"Enable error reporting (requires restart)": "Włącz raportowanie błędów (wymaga ponownego uruchomienia)",
"Enable spellchecker (requires restart)": "Włącz sprawdzanie pisowni (wymaga ponownego uruchomienia)",
"Factory Reset": "Przywrócenie ustawień fabrycznych",
"File": "Plik",
"Find accounts": "Znajdź konta",
"Find accounts by email": "Znajdź konta pocztą e-mail",
"Flash taskbar on new message": "Pasek zadań Flash w nowej wiadomości",
"Forward": "Naprzód",
"Functionality": "Funkcjonalność",
"General": "Generał",
"Get beta updates": "Pobierz aktualizacje beta",
"Hard Reload": "Hard Reload",
"Hard Reload": "Trudne przeładowanie",
"Help": "Wsparcie",
"Help Center": "Centrum pomocy",
"History": "Historia",
"History Shortcuts": "Skróty historii",
"Keyboard Shortcuts": "Skróty klawiszowe",
"Log Out": "Wyloguj",
"Log Out of Organization": "Wyloguj się z organizacji",
"Manual proxy configuration": "Ręczna konfiguracja proxy",
"Minimize": "Zminimalizować",
"Mute all sounds from Zulip": "Wycisz wszystkie dźwięki z Zulip",
"NO": "NIE",
"Network": "Sieć",
"OR": "LUB",
"Organization URL": "Adres URL organizacji",
"Organizations": "Organizacje",
"Paste": "Pasta",
"Paste and Match Style": "Wklej i dopasuj styl",
"Proxy": "Pełnomocnik",
"Proxy bypass rules": "Zasady omijania proxy",
"Proxy rules": "Zasady proxy",
"Quit": "Porzucić",
"Quit Zulip": "Zamknij Zulipa",
"Redo": "Przerobić",
"Release Notes": "Informacje o wydaniu",
"Reload": "Przeładować",
"Report an Issue": "Zgłoś problem",
"Reset App Data": "Zresetuj dane aplikacji",
"Report an issue...": "Zgłoś problem...",
"Reset App Settings": "Zresetuj ustawienia aplikacji",
"Reset Application Data": "Zresetuj dane aplikacji",
"Save": "Zapisać",
"Select All": "Zaznacz wszystko",
"Settings": "Ustawienia",
"Shortcuts": "Skróty",
"Show App Logs": "Pokaż dzienniki aplikacji",
"Show app icon in system tray": "Pokaż ikonę aplikacji w zasobniku systemowym",
"Show app unread badge": "Pokaż nieprzeczytaną odznakę aplikacji",
"Show desktop notifications": "Pokaż powiadomienia na pulpicie",
"Show downloaded files in file manager": "Pokaż pobrane pliki w menedżerze plików",
"Show sidebar": "Pokaż pasek boczny",
"Start app at login": "Uruchom aplikację przy logowaniu",
"Switch to Next Organization": "Przełącz na następną organizację",
"Switch to Previous Organization": "Przełącz na poprzednią organizację",
"These desktop app shortcuts extend the Zulip webapp's": "Te skróty do aplikacji na komputery stacjonarne rozszerzają aplikacje Zulip Webapp",
"This will delete all application data including all added accounts and preferences": "Spowoduje to usunięcie wszystkich danych aplikacji, w tym wszystkich dodanych kont i preferencji",
"Tip": "Wskazówka",
"Toggle DevTools for Active Tab": "Przełącz DevTools dla Active Tab",
"Toggle DevTools for Zulip App": "Przełącz DevTools dla aplikacji Zulip",
"Toggle Do Not Disturb": "Przełącz nie przeszkadzać",
"Toggle Full Screen": "Przełącz tryb pełnoekranowy",
"Toggle Sidebar": "Przełącz pasek boczny",
"Toggle Sidebar": "Przełącz boczny pasek",
"Toggle Tray Icon": "Przełącz ikonę tacy",
"Tools": "Przybory",
"Undo": "Cofnij",
"Upload": "Przekazać plik",
"Use system proxy settings (requires restart)": "Użyj ustawień serwera proxy (wymaga ponownego uruchomienia)",
"View": "Widok",
"View Shortcuts": "Wyświetl skróty",
"Window": "Okno",
"Window Shortcuts": "Skróty do okien",
"YES": "TAK",
"Zoom In": "Zbliżenie",
"Zoom Out": "Pomniejsz",
"Zulip Help": "Pomoc Zulip",
"keyboard shortcuts": "Skróty klawiszowe",
"script": "scenariusz"
"Zulip Help": "Zulip Help"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "Sobre o Zulip",
"About Zulip": "Sobre Zulip",
"Actual Size": "Tamanho atual",
"Add Custom Certificates": "Adicionar certificados personalizados",
"Add Organization": "Adicionar Organização",
"Add a Zulip organization": "Adicione uma organização Zulip",
"Add custom CSS": "Adicionar CSS personalizado",
"Advanced": "Avançado",
"All the connected organizations will appear here": "Todas as organizações conectadas aparecerão aqui",
"Always start minimized": "Começar sempre minimizado",
"App Updates": "Atualizações de aplicativos",
"Appearance": "Aparência",
"Application Shortcuts": "Atalhos de aplicativos",
"Are you sure you want to disconnect this organization?": "Tem certeza de que deseja desconectar essa organização?",
"Auto hide Menu bar": "Ocultar automaticamente barra de menu",
"Auto hide menu bar (Press Alt key to display)": "Ocultar barra de menu automaticamente (pressione a tecla Alt para exibir)",
"Back": "De volta",
"Bounce dock on new private message": "Bounce doca em nova mensagem privada",
"Certificate file": "Arquivo de certificado",
"Change": "mudança",
"Check for Updates": "Verificar se há atualizações",
"Close": "Perto",
"Connect": "Conectar",
"Connect to another organization": "Conecte-se a outra organização",
"Connected organizations": "Organizações conectadas",
"Back": "Costas",
"Close": "Fechar",
"Copy": "cópia de",
"Copy Zulip URL": "Copiar URL do Zulip",
"Create a new organization": "Crie uma nova organização",
"Cut": "Cortar",
"Default download location": "Local de download padrão",
"Delete": "Excluir",
"Desktop App Settings": "Configurações do aplicativo de desktop",
"Desktop Notifications": "Notificações da área de trabalho",
"Desktop Settings": "Configurações da área de trabalho",
"Disconnect": "desconectar",
"Download App Logs": "Download de registros de aplicativos",
"Desktop App Settings": "Configurações da aplicação Desktop",
"Edit": "Editar",
"Edit Shortcuts": "Editar atalhos",
"Enable auto updates": "Ativar atualizações automáticas",
"Enable error reporting (requires restart)": "Ativar relatório de erros (requer reinicialização)",
"Enable spellchecker (requires restart)": "Ativar verificação ortográfica (requer reinicialização)",
"Factory Reset": "Restauração de fábrica",
"File": "Arquivo",
"Find accounts": "Encontrar contas",
"Find accounts by email": "Encontre contas por email",
"Flash taskbar on new message": "Barra de tarefas em Flash na nova mensagem",
"Forward": "frente",
"Functionality": "Funcionalidade",
"General": "Geral",
"Get beta updates": "Receba atualizações beta",
"Hard Reload": "Hard Reload",
"Hard Reload": "Hard Recargar",
"Help": "Socorro",
"Help Center": "Centro de ajuda",
"History": "História",
"History Shortcuts": "Atalhos da História",
"Keyboard Shortcuts": "Atalhos do teclado",
"Log Out": "Sair",
"Log Out of Organization": "Sair da organização",
"Manual proxy configuration": "Configuração manual de proxy",
"Minimize": "Minimizar",
"Mute all sounds from Zulip": "Silencie todos os sons de Zulip",
"NO": "NÃO",
"Network": "Rede",
"OR": "OU",
"Organization URL": "URL da organização",
"Organizations": "Organizações",
"Paste": "Colar",
"Paste and Match Style": "Colar e combinar estilo",
"Proxy": "Procuração",
"Proxy bypass rules": "Regras de desvio de proxy",
"Proxy rules": "Regras de proxy",
"Paste and Match Style": "Estilo de colar e combinar",
"Quit": "Sair",
"Quit Zulip": "Saia de Zulip",
"Redo": "Refazer",
"Release Notes": "Notas de Lançamento",
"Reload": "recarregar",
"Report an Issue": "Comunicar um problema",
"Reset App Data": "Redefinir dados do aplicativo",
"Reset App Settings": "Redefinir configurações do aplicativo",
"Reset Application Data": "Redefinir dados do aplicativo",
"Save": "Salve ",
"Report an issue...": "Relatar um problema ...",
"Reset App Settings": "Redefinir as configurações da aplicação",
"Select All": "Selecionar tudo",
"Settings": "Definições",
"Shortcuts": "Atalhos",
"Show App Logs": "Mostrar registros do aplicativo",
"Show app icon in system tray": "Mostrar ícone do aplicativo na bandeja do sistema",
"Show app unread badge": "Mostrar crachá não lido do aplicativo",
"Show desktop notifications": "Mostrar notificações da área de trabalho",
"Show downloaded files in file manager": "Mostrar arquivos baixados no gerenciador de arquivos",
"Show sidebar": "Mostrar barra lateral",
"Start app at login": "Inicie o aplicativo no login",
"Switch to Next Organization": "Mudar para a próxima organização",
"Switch to Previous Organization": "Mudar para a organização anterior",
"These desktop app shortcuts extend the Zulip webapp's": "Esses atalhos para aplicativos de desktop estendem o aplicativo da web do Zulip",
"This will delete all application data including all added accounts and preferences": "Isso excluirá todos os dados do aplicativo, incluindo todas as contas e preferências adicionadas",
"Tip": "Gorjeta",
"Toggle DevTools for Active Tab": "Alternar DevTools para a guia ativa",
"Toggle DevTools for Zulip App": "Alternar DevTools para Zulip App",
"Toggle Do Not Disturb": "Alternar não perturbe",
"Show App Logs": "Mostrar logs de aplicativos",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Full Screen": "Alternar para o modo tela cheia",
"Toggle Sidebar": "Alternar Barra Lateral",
"Toggle Tray Icon": "Alternar ícone da bandeja",
"Tools": "Ferramentas",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Ícone Toggle Tray",
"Undo": "Desfazer",
"Upload": "Envio",
"Use system proxy settings (requires restart)": "Use as configurações de proxy do sistema (requer reinicialização)",
"View": "Visão",
"View Shortcuts": "Exibir atalhos",
"Window": "Janela",
"Window Shortcuts": "Atalhos de janela",
"YES": "SIM",
"Zoom In": "Mais Zoom",
"Zoom Out": "Reduzir o zoom",
"Zulip Help": "Zulip Ajuda",
"keyboard shortcuts": "atalhos do teclado",
"script": "roteiro"
"Zulip Help": "Zulip Help"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "О Zulip",
"About Zulip": "О пользователе Zulip",
"Actual Size": "Фактический размер",
"Add Custom Certificates": "Добавить пользовательские сертификаты",
"Add Organization": "Добавить организацию",
"Add a Zulip organization": "Добавить организацию Zulip",
"Add custom CSS": "Добавить собственный CSS",
"Advanced": "Расширенные",
"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": "Прыгающий док на новое личное сообщение",
"Certificate file": "Файл сертификата",
"Change": "+ Изменить",
"Check for Updates": "Проверить наличие обновлений",
"Back": "назад",
"Close": "Закрыть",
"Connect": "Подключиться",
"Connect to another organization": "Подключиться к другой организации",
"Connected organizations": "Связанные организации",
"Copy": "Копировать",
"Copy Zulip URL": "Скопируйте Zulip URL",
"Create a new organization": "Создать новую организацию",
"Cut": "Вырезать",
"Default download location": "Местоположение загрузок по умолчанию",
"Copy": "копия",
"Cut": "Порез",
"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": "Найти аккаунты по электронной почте",
"Flash taskbar on new message": "Flash-панель задач на новое сообщение",
"Desktop App Settings": "Настройки настольных приложений",
"Edit": "редактировать",
"File": "файл",
"Forward": "Вперед",
"Functionality": "Функциональность",
"General": "Общий",
"Get beta updates": "Получить бета-обновления",
"Hard Reload": "Принудительная перезагрузка",
"Help": "Помощь",
"Help Center": "Центр помощи",
"History": "История",
"History Shortcuts": "Горячие клавиши истории",
"Hard Reload": "Hard Reload",
"Help": "Помогите",
"History": "история",
"Keyboard Shortcuts": "Горячие клавиши",
"Log Out": "Выйти",
"Log Out of Organization": "Выйти из организации",
"Manual proxy configuration": "Ручная настройка прокси",
"Minimize": "Минимизировать",
"Mute all sounds from Zulip": "Отключить все звуки от Zulip",
"NO": "НЕТ",
"Network": "сеть",
"OR": "ИЛИ",
"Organization URL": "URL организации",
"Organizations": "Организации",
"Paste": "Вставить",
"Paste and Match Style": "Вставить и соответствовать стилю",
"Proxy": "Прокси",
"Proxy bypass rules": "Правила обхода прокси",
"Proxy rules": "Правила проксирования",
"Quit": "Выйти",
"Quit Zulip": "Выйти из Zulip",
"Redo": "Повторить",
"Release Notes": "Примечания к выпуску",
"Reload": "Перезагрузить",
"Report an Issue": "Сообщить о проблеме",
"Reset App Data": "Сбросить данные приложения",
"Paste and Match Style": "Стиль вставки и совпадения",
"Quit": "Уволиться",
"Redo": "переделывать",
"Reload": "перезагружать",
"Report an issue...": "Сообщить о проблеме...",
"Reset App Settings": "Сбросить настройки приложения",
"Reset Application Data": "Сбросить данные приложения",
"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.",
"This will delete all application data including all added accounts and preferences": "Это удалит все данные приложения, включая все добавленные учетные записи и настройки",
"Tip": "Совет",
"Toggle DevTools for Active Tab": "Переключить DevTools для активной вкладки",
"Toggle DevTools for Zulip App": "Переключить DevTools для приложения Zulip",
"Toggle Do Not Disturb": "Переключить в режим не беспокоить",
"Show App Logs": "Показать журналы приложений",
"Toggle DevTools for Active Tab": "Toggle DevTools для активной вкладки",
"Toggle DevTools for Zulip App": "Toggle DevTools для приложения Zulip",
"Toggle Full Screen": "Включить полноэкранный режим",
"Toggle Sidebar": "Переключить боковую панель",
"Toggle Tray Icon": "Переключить Значок в трее",
"Tools": "Инструменты",
"Undo": "Повторить",
"Upload": "Загрузить",
"Use system proxy settings (requires restart)": "Использовать настройки системного прокси (требуется перезагрузка)",
"View": "Вид",
"View Shortcuts": "Посмотреть горячие клавиши окна",
"Toggle Tray Icon": "Иконка Toggle Tray",
"Undo": "расстегивать",
"View": "Посмотреть",
"Window": "Окно",
"Window Shortcuts": "Горячие клавиши окна",
"YES": "ДА",
"Zoom In": "Увеличить",
"Zoom In": "Приблизить",
"Zoom Out": "Уменьшить",
"Zulip Help": "Zulip Помощь",
"keyboard shortcuts": "горячие клавиши",
"script": "скрипт"
"Zulip Help": "Zulip Help"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "О Зулипу",
"Actual Size": "Стварна величина",
"Add Custom Certificates": "Додајте прилагођене цертификате",
"Add Organization": "Додај организацију",
"Add a Zulip organization": "Додајте Зулип организацију",
"Add custom CSS": "Додајте прилагођени ЦСС",
"Advanced": "Напредно",
"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)": "Аутоматско скривање траке менија (притисните тастер Алт да бисте приказали)",
"Back": "Назад",
"Bounce dock on new private message": "Одскочите у нову приватну поруку",
"Certificate file": "Датотека сертификата",
"Change": "Цханге",
"Check for Updates": "Провери ажурирања",
"Close": "Близу",
"Connect": "Повежи",
"Connect to another organization": "Повежите се са другом организацијом",
"Connected organizations": "Повезане организације",
"Copy": "Копирај",
"Copy Zulip URL": "Цопи Зулип УРЛ",
"Create a new organization": "Направите нову организацију",
"Cut": "Цут",
"Default download location": "Дефаулт довнлоад лоцатион",
"Delete": "Обриши",
"Desktop App Settings": "Подешавања апликације за десктоп рачунаре",
"Desktop Notifications": "Обавештења о радној површини",
"Desktop Settings": "Десктоп Сеттингс",
"Disconnect": "Дисцоннецт",
"Download App Logs": "Довнлоад Апп Логс",
"Delete": "Избриши",
"Desktop App Settings": "Подешавања за десктоп апликације",
"Edit": "Уредити",
"Edit Shortcuts": "Уреди пречице",
"Enable auto updates": "Омогући аутоматско ажурирање",
"Enable error reporting (requires restart)": "Омогући извештавање о грешкама (захтева поновно покретање)",
"Enable spellchecker (requires restart)": "Омогући провјеру правописа (захтијева поновно покретање)",
"Factory Reset": "Фацтори Ресет",
"File": "Филе",
"Find accounts": "Нађи рачуне",
"Find accounts by email": "Пронађите рачуне путем е-поште",
"Flash taskbar on new message": "Фласх трака задатака у новој поруци",
"Forward": "Напријед",
"Functionality": "Функционалност",
"General": "Генерал",
"Get beta updates": "Набавите бета ажурирања",
"Hard Reload": "Хард Релоад",
"Help": "Помоћ",
"Help Center": "Центар за помоћ",
"History": "Хистори",
"History Shortcuts": "Историјске пречице",
"History": "Историја",
"Keyboard Shortcuts": "Пречице на тастатури",
"Log Out": "Одјавити се",
"Log Out of Organization": "Одјавите се из организације",
"Manual proxy configuration": "Мануал проки цонфигуратион",
"Minimize": "Минимизе",
"Mute all sounds from Zulip": "Искључите све звукове из Зулипа",
"NO": "НЕ",
"Network": "Мрежа",
"OR": "ОР",
"Organization URL": "УРЛ организације",
"Organizations": "Организације",
"Minimize": "Минимизирај",
"Paste": "Пасте",
"Paste and Match Style": "Залепите и подесите стил",
"Proxy": "Заступник",
"Proxy bypass rules": "Проки бипасс правила",
"Proxy rules": "Проки рулес",
"Paste and Match Style": "Пасте и Матцх Стиле",
"Quit": "Одустати",
"Quit Zulip": "Куит Зулип",
"Redo": "Редо",
"Release Notes": "Релеасе Нотес",
"Redo": "Поново",
"Reload": "Освежи",
"Report an Issue": "Пријавите проблем",
"Reset App Data": "Ресетирај податке о апликацији",
"Reset App Settings": "Ресетуј поставке апликације",
"Reset Application Data": "Ресетуј податке апликације",
"Save": "сачувати",
"Report an issue...": "Пријавите проблем...",
"Reset App Settings": "Поново поставите подешавања апликације",
"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": "Пречице за десктоп апликације проширују Зулип вебаппове",
"This will delete all application data including all added accounts and preferences": "Ово ће избрисати све податке о апликацији, укључујући све додатне налоге и поставке",
"Tip": "Савет",
"Toggle DevTools for Active Tab": "Пребаци ДевТоолс за Ацтиве Таб",
"Toggle DevTools for Zulip App": "Пребаци ДевТоолс за Зулип Апп",
"Toggle Do Not Disturb": "Тоггле До Нот Дистурб",
"Toggle Full Screen": "Тоггле Фулл Сцреен",
"Toggle Sidebar": "Тоггле Сидебар",
"Show App Logs": рикажи евиденције апликација",
"Toggle DevTools for Active Tab": "Пребаците ДевТоолс за Ацтиве Таб",
"Toggle DevTools for Zulip App": "Пребаците ДевТоолс за Зулип Апп",
"Toggle Full Screen": "Пребаците цео екран",
"Toggle Sidebar": "Пребаците бочну траку",
"Toggle Tray Icon": "Тоггле Траи Ицон",
"Tools": "Алати",
"Undo": "Ундо",
"Upload": "Отпремити",
"Use system proxy settings (requires restart)": "Користи поставке системског прокија (потребно је поново покренути)",
"View": "Поглед",
"View Shortcuts": "Прикажи пречице",
"Window": "Прозор",
"Window Shortcuts": "Пречице за прозор",
"YES": "ДА",
"Zoom In": "Увеличати",
"Zoom Out": "Зоом Оут",
"Zulip Help": "Зулип Хелп",
"keyboard shortcuts": "пречице на тастатури",
"script": "скрипта"
"Zoom Out": "Зумирај",
"Zulip Help": "Зулип Хелп"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "ஜலிப் பற்றி",
"About Zulip": "ஜலிப் பற்றி",
"Actual Size": "உண்மையான அளவு",
"Add Custom Certificates": "தனிப்பயன் சான்றிதழ்களைச் சேர்க்கவும்",
"Add Organization": "அமைப்பைச் சேர்",
"Add a Zulip organization": "ஒரு ஜூலிப் அமைப்பைச் சேர்க்கவும்",
"Add custom CSS": "தனிப்பயன் CSS ஐச் சேர்க்கவும்",
"Advanced": "மேம்பட்ட",
"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": "புதிய தனிப்பட்ட செய்தியில் கப்பல்துறை பவுன்ஸ்",
"Certificate file": "சான்றிதழ் கோப்பு",
"Change": "மாற்றம்",
"Check for Updates": "புதுப்பிப்புகளைச் சரிபார்க்கவும்",
"Close": "நெருக்கமான",
"Connect": "இணைக்கவும்",
"Connect to another organization": "வேறொரு நிறுவனத்துடன் இணைக்கவும்",
"Connected organizations": "இணைக்கப்பட்ட நிறுவனங்கள்",
"Copy": "நகல்",
"Copy Zulip URL": "ஜூலிப் URL ஐ நகலெடுக்கவும்",
"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": "மின்னஞ்சல் மூலம் கணக்குகளைக் கண்டறியவும்",
"Flash taskbar on new message": "புதிய செய்தியில் ஃபிளாஷ் பணிப்பட்டி",
"Forward": "முன்னோக்கி",
"Functionality": "செயல்பாடு",
"General": "பொது",
"Get beta updates": "பீட்டா புதுப்பிப்புகளைப் பெறுங்கள்",
"Hard Reload": "கடின மறுஏற்றம்",
"Hard Reload": "கடினமாக மீண்டும் ஏற்றவும்",
"Help": "உதவி",
"Help Center": "உதவி மையம்",
"History": "வரலாறு",
"History Shortcuts": "வரலாறு குறுக்குவழிகள்",
"Keyboard Shortcuts": "விசைப்பலகை குறுக்குவழிகள்",
"Log Out": "வெளியேறு",
"Log Out of Organization": "நிறுவனத்திலிருந்து வெளியேறு",
"Manual proxy configuration": "கையேடு ப்ராக்ஸி உள்ளமைவு",
"Minimize": "குறைத்தல்",
"Mute all sounds from Zulip": "ஜூலிப்பிலிருந்து எல்லா ஒலிகளையும் முடக்கு",
"NO": "இல்லை",
"Network": "வலைப்பின்னல்",
"OR": "அல்லது",
"Organization URL": "அமைப்பு URL",
"Organizations": "அமைப்புக்கள்",
"Paste": "ஒட்டு",
"Paste and Match Style": "ஒட்டு மற்றும் போட்டி நடை",
"Proxy": "பதிலாள்",
"Proxy bypass rules": "ப்ராக்ஸி பைபாஸ் விதிகள்",
"Proxy rules": "ப்ராக்ஸி விதிகள்",
"Paste and Match Style": "உடை ஒட்டு மற்றும் பொருந்தும்",
"Quit": "விட்டுவிட",
"Quit Zulip": "ஜூலிப்பை விட்டு வெளியேறு",
"Redo": "மீண்டும் செய்",
"Release Notes": "வெளியீட்டு குறிப்புகள்",
"Reload": "ஏற்றவும்",
"Report an Issue": "ஒரு சிக்கலைப் புகாரளிக்கவும்",
"Reset App Data": "பயன்பாட்டு தரவை மீட்டமைக்கவும்",
"Reset App Settings": "பயன்பாட்டு அமைப்புகளை மீட்டமை",
"Reset Application Data": "பயன்பாட்டு தரவை மீட்டமைக்கவும்",
"Save": "சேமி",
"Report an issue...": "சிக்கலைப் புகார ...",
"Reset App Settings": "பயன்பாட்டு அமைப்புகளை மீட்டமைக்கவும்",
"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": "இந்த டெஸ்க்டாப் பயன்பாட்டு குறுக்குவழிகள் ஜூலிப் வெப்ஆப்பை நீட்டிக்கின்றன",
"This will delete all application data including all added accounts and preferences": "இது அனைத்து சேர்க்கப்பட்ட கணக்குகள் மற்றும் விருப்பத்தேர்வுகள் உட்பட அனைத்து பயன்பாட்டு தரவையும் நீக்கும்",
"Tip": "குறிப்பு",
"Show App Logs": "பயன்பாட்டு பதிவுகள் காட்டு",
"Toggle DevTools for Active Tab": "செயலில் தாவலுக்கு DevTools ஐ மாற்று",
"Toggle DevTools for Zulip App": "ஜூலிப் பயன்பாட்டிற்கான DevTools ஐ மாற்று",
"Toggle Do Not Disturb": "தொந்தரவு செய்ய வேண்டாம் என்பதை நிலைமாற்று",
"Toggle DevTools for Zulip App": "Zulip பயன்பாட்டிற்கான DevTools ஐ மாற்று",
"Toggle Full Screen": "மாற்று முழுத்திரை",
"Toggle Sidebar": "பக்கப்பட்டியை நிலைமாற்று",
"Toggle Tray Icon": "ட்டு ஐகானை மாற்று",
"Tools": "கருவிகள்",
"Toggle Sidebar": "பக்கப்பட்டி மாறு",
"Toggle Tray Icon": "ட்ரே ஐகானை மாற்று",
"Undo": "செயல்தவிர்",
"Upload": "பதிவேற்றம்",
"Use system proxy settings (requires restart)": "கணினி ப்ராக்ஸி அமைப்புகளைப் பயன்படுத்தவும் (மறுதொடக்கம் தேவை)",
"View": "காண்க",
"View Shortcuts": "குறுக்குவழிகளைக் காண்க",
"Window": "ஜன்னல்",
"Window Shortcuts": "சாளர குறுக்குவழிகள்",
"YES": "ஆம்",
"Zoom In": "பெரிதாக்க",
"Zoom Out": "பெரிதாக்கு",
"Zulip Help": "ஜூலிப் உதவி",
"keyboard shortcuts": "விசைப்பலகை குறுக்குவழிகள்",
"script": "ஸ்கிரிப்ட்"
"Zoom Out": "பெரிதாக்கு",
"Zulip Help": "Zulip உதவி"
}

View File

@@ -1,117 +1,39 @@
{
"About Zulip": "Zulip hakkında",
"About Zulip": "Zulip Hakkinda",
"Actual Size": "Gerçek boyutu",
"Add Custom Certificates": "Özel Sertifika Ekleme",
"Add Organization": "Organizasyon ekle",
"Add a Zulip organization": "Bir Zulip organizasyonu ekleyin",
"Add custom CSS": "Özel CSS ekle",
"Advanced": "ileri",
"All the connected organizations will appear here": "Tüm bağlı kuruluşlar burada görünecek",
"Always start minimized": "Her zaman simge durumuna küçültülmüş olarak başla",
"App Updates": "Uygulama Güncellemeleri",
"Appearance": "Görünüm",
"Application Shortcuts": "Uygulama Kısayolları",
"Are you sure you want to disconnect this organization?": "Bu kuruluşun bağlantısını kesmek istediğinize emin misiniz?",
"Auto hide Menu bar": "Menü çubuğunu otomatik gizle",
"Auto hide menu bar (Press Alt key to display)": "Menü çubuğunu otomatik gizle (görüntülemek için Alt tuşuna basın)",
"Back": "Geri",
"Bounce dock on new private message": "Yeni özel mesaja dock atla",
"Certificate file": "Sertifika dosyası",
"Change": "Değişiklik",
"Check for Updates": "Güncellemeleri kontrol et",
"Close": "Kapat",
"Connect": "bağlamak",
"Connect to another organization": "Başka bir kuruma bağlan",
"Connected organizations": "Bağlı kuruluşlar",
"Copy": "kopya",
"Copy Zulip URL": "Zulip URL'sini kopyala",
"Create a new organization": "Yeni bir organizasyon oluştur",
"Cut": "Kesmek",
"Default download location": "Varsayılan indirme yeri",
"Delete": "silmek",
"Desktop App Settings": "Masaüstü Uygulaması Ayarları",
"Desktop Notifications": "Masaüstü bildirimleri",
"Desktop Settings": "Masaüstü Ayarları",
"Disconnect": "kesmek",
"Download App Logs": "Uygulama Günlüklerini İndir",
"Desktop App Settings": "Masaüstü Uygulama Ayarları",
"Edit": "Düzenle",
"Edit Shortcuts": "Kısayolları Düzenle",
"Enable auto updates": "Otomatik güncellemeleri etkinleştir",
"Enable error reporting (requires restart)": "Hata raporlamayı etkinleştir (yeniden başlatma gerektirir)",
"Enable spellchecker (requires restart)": "Yazım denetleyicisini etkinleştir (yeniden başlatma gerektirir)",
"Factory Reset": "Fabrika ayarları",
"File": "Dosya",
"Find accounts": "Hesapları bulun",
"Find accounts by email": "E-postayla hesap bulun",
"Flash taskbar on new message": "Yeni mesajda Flash görev çubuğu",
"Forward": "ileri",
"Functionality": "İşlevsellik",
"General": "Genel",
"Get beta updates": "Beta güncellemelerini alın",
"Hard Reload": "Sabit Yeniden Yükleme",
"Hard Reload": "Sert Yeniden Yükle",
"Help": "yardım et",
"Help Center": "Yardım Merkezi",
"History": "Tarihçe",
"History Shortcuts": "Tarihçe Kısayolları",
"Keyboard Shortcuts": "Klavye kısayolları",
"Log Out": ıkış Yap",
"Log Out of Organization": "Kuruluştan Çıkma",
"Manual proxy configuration": "Manuel proxy yapılandırması",
"Minimize": "küçültmek",
"Mute all sounds from Zulip": "Zulip'teki tüm sesleri kapat",
"NO": "YOK HAYIR",
"Network": "Ağ",
"OR": "VEYA",
"Organization URL": "Kuruluş URLsi",
"Organizations": "Organizasyonlar",
"Paste": "Yapıştırmak",
"Paste and Match Style": "Yapıştır ve Stili Eşleştir",
"Proxy": "vekil",
"Proxy bypass rules": "Proxy atlama kuralları",
"Proxy rules": "Proxy kuralları",
"Paste and Match Style": "Stili Yapıştır ve Eşleştir",
"Quit": ıkmak",
"Quit Zulip": "Zulip'i bırak",
"Redo": "yeniden yapmak",
"Release Notes": "Sürüm notları",
"Reload": "Tekrar yükle",
"Report an Issue": "Bir Sorunu Bildirin",
"Reset App Data": "Uygulama Verilerini Sıfırla",
"Report an issue...": "Bir sorun bildir ...",
"Reset App Settings": "Uygulama Ayarlarını Sıfırla",
"Reset Application Data": "Uygulama Verilerini Sıfırla",
"Save": "Kayıt etmek",
"Select All": "Hepsini seç",
"Settings": "Ayarlar",
"Shortcuts": "Kısayollar",
"Show App Logs": "Uygulama Günlüklerini Göster",
"Show app icon in system tray": "Sistem tepsisinde uygulama simgesini göster",
"Show app unread badge": "Uygulamaya okunmamış rozeti göster",
"Show desktop notifications": "Masaüstü bildirimlerini göster",
"Show downloaded files in file manager": "İndirilen dosyaları dosya yöneticisinde göster",
"Show sidebar": "Kenar çubuğunu göster",
"Start app at login": "Giriş yaparken uygulamayı başlat",
"Switch to Next Organization": "Sonraki Organizasyona Geç",
"Switch to Previous Organization": "Önceki Organizasyona Geç",
"These desktop app shortcuts extend the Zulip webapp's": "Bu masaüstü uygulaması kısayolları, Zulip webappın",
"This will delete all application data including all added accounts and preferences": "Bu, eklenen tüm hesaplar ve tercihler dahil tüm uygulama verilerini siler",
"Tip": "Bahşiş",
"Toggle DevTools for Active Tab": "Etkin Sekme için DevTools'u değiştirin",
"Toggle DevTools for Zulip App": "Zulip App için DevTools Toggle",
"Toggle Do Not Disturb": "Geçiş Rahatsız Etmeyin",
"Show App Logs": "Uygulama Günlüğlerini Göster",
"Toggle DevTools for Active Tab": "Aktif Sekme İçin DevTools'a Geçiş Yap",
"Toggle DevTools for Zulip App": "Zulip App için DevTools'a Geçiş Yap",
"Toggle Full Screen": "Tam ekrana geç",
"Toggle Sidebar": "Kenar Çubuğunu Değiştir",
"Toggle Tray Icon": "Tepsi İkonunu Aç / Kapat",
"Tools": "Araçlar",
"Toggle Sidebar": "Kenar Çubuğunu Aç / Kapat",
"Toggle Tray Icon": "Tepsi Simgesini Aç / Kapat",
"Undo": "Geri alma",
"Upload": "Yükleme",
"Use system proxy settings (requires restart)": "Sistem proxy ayarlarını kullan (yeniden başlatmayı gerektirir)",
"View": "Görünüm",
"View Shortcuts": "Kısayolları Göster",
"Window": "pencere",
"Window Shortcuts": "Pencere Kısayolları",
"YES": "EVET",
"Zoom In": "Yakınlaştır",
"Zoom Out": "Uzaklaştırmak",
"Zulip Help": "Zulip Yardım",
"keyboard shortcuts": "Klavye kısayolları",
"script": "senaryo"
"Zoom Out": "Uzaklaştır",
"Zulip Help": "Zulip Yardımı"
}

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>

View File

@@ -2,61 +2,6 @@
All notable changes to the Zulip desktop app are documented in this file.
### v4.0.0 --2019-08-08
**New features**:
* Add enterprise support using a custom config file for all Zulip users on a given machine. Documentation can be found [here](https://github.com/zulip/zulip-desktop/blob/master/docs/Enterprise.md).
* Support specification of preset organizations and automatic update preferences.
* Show setting tooltip when trying to change an admin-locked setting.
* Change translation API to handle Google Translate's rate limits.
* Change menu and language of all settings pages based on system locale.
* Disable the Window sub-menu.
**Fixes**:
* Use newer Darwin notification API in `electron_bridge`.
* Revert to fallback character icon for an organization only when the icon is not available either on the Zulip server or stored offline on the disk.
* Fix issues with the Zoom In shortcut.
* Sync the sidebar loading indicator with the loading GIF in the main view.
* Fix shortcut symbol for Zoom In.
**Development**:
* Add meta key for ⌘ on macOS.
### v3.1.0-beta --2019-07-19
**New features**:
* Add option to find accounts by email.
* Add option to hide Menu bar to View menu.
* Show a loading indicator in the sidebar.
* Update Help Center to open help page of the currently active server.
* Improve auto-detection of spellchecker language.
* Disable menu items on non-server pages.
* Support dark mode on macOS.
**Fixes**:
* Updated, more robust server validation logic.
* Fix JSON DB errors observed when switching tabs.
* Remove unused `isLoading` function from `Tab`.
* Remove unused `defaultId` parameter.
* Fix syntax error in `proxy-util.js`.
* Fix issue with creation of large `.node` files in the `Temp` folder on Windows machines.
* Fix issue where drafts were not saved properly.
**Development**:
* Migrate codebase to TypeScript.
* Set the indent_size in `.editconfig` to 4.
* Use `.env` file for reading Sentry DSN.
**Documentation**:
* Improve development guide.
**Module updates**:
* Upgrade xo to v0.24.0.
* Upgrade node-json-db to v0.9.2.
* Upgrade electron to v3.1.10.
* Add missing transitive dependencies.
### v3.0.0 --2019-05-20
**New features**:

Some files were not shown because too many files have changed in this diff Show More