mirror of
				https://github.com/zulip/zulip-desktop.git
				synced 2025-10-31 03:53:34 +00:00 
			
		
		
		
	Compare commits
	
		
			152 Commits
		
	
	
		
			v1.3.0-bet
			...
			v1.6.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | dcd2abca6e | ||
|  | 2fb9efb981 | ||
|  | 7245b6a110 | ||
|  | bcb8ffb55f | ||
|  | 77094596a5 | ||
|  | 06ad44bdd7 | ||
|  | e719ba139c | ||
|  | 9853e9226c | ||
|  | f2c76b5ca3 | ||
|  | e6dbff995b | ||
|  | 4578d4a5f7 | ||
|  | 4b895a2312 | ||
|  | 53c0428a3a | ||
|  | 0a1866abb5 | ||
|  | ce862a4890 | ||
|  | 1b1ad2cd61 | ||
|  | ead7a06308 | ||
|  | 6659dd5097 | ||
|  | ed1f0f6d5b | ||
|  | 79acf8a6e1 | ||
|  | 8e0033f03e | ||
|  | 9144c2630d | ||
|  | fae05fc3b1 | ||
|  | 73603a4fd2 | ||
|  | a498ffc7d6 | ||
|  | 7afcf13401 | ||
|  | 89a292559d | ||
|  | be14517caf | ||
|  | 3b6c5ae532 | ||
|  | 40e3ed0f2f | ||
|  | 5d988858b0 | ||
|  | 3a974136a3 | ||
|  | 6ed5a5309c | ||
|  | 80c37fabb8 | ||
|  | 79366e19df | ||
|  | f409bb0449 | ||
|  | 45bdde951f | ||
|  | 6b627780f0 | ||
|  | 6f67553da5 | ||
|  | 2e710a9322 | ||
|  | 91f3afa8fe | ||
|  | f784345495 | ||
|  | 67da435154 | ||
|  | c89733610d | ||
|  | 8f272a67b5 | ||
|  | f6c4a76138 | ||
|  | b90a4c5254 | ||
|  | a06e09e565 | ||
|  | ad5bef821e | ||
|  | 58bbd7bf30 | ||
|  | 90d080dc96 | ||
|  | ad3fcf585e | ||
|  | 4b8f216bab | ||
|  | e620e0c428 | ||
|  | 50b3151b5d | ||
|  | 0c32756485 | ||
|  | 0c0835e364 | ||
|  | 9e962a5c44 | ||
|  | a218f7ea64 | ||
|  | 13a7f7475a | ||
|  | 48b17a1549 | ||
|  | 653598fd9e | ||
|  | ddbc282f49 | ||
|  | 992d92b06d | ||
|  | 45867ef15e | ||
|  | 6572c90d49 | ||
|  | 1ed0011c88 | ||
|  | 6dd79b205c | ||
|  | 538c18fa90 | ||
|  | 29e347c511 | ||
|  | ad37a5e0a6 | ||
|  | 352b775e27 | ||
|  | 38cec25680 | ||
|  | f77ab92202 | ||
|  | e843a29316 | ||
|  | 85837242e7 | ||
|  | 9f6da5712e | ||
|  | 158685a869 | ||
|  | 288b1cb3f2 | ||
|  | e24a966d48 | ||
|  | 306feb2eff | ||
|  | f426c932b0 | ||
|  | 26172d8508 | ||
|  | 99da0a338f | ||
|  | 9599249b31 | ||
|  | 4bdd2564b7 | ||
|  | 4dcf22a53c | ||
|  | 0dc97648a0 | ||
|  | 787f097cf3 | ||
|  | 5481d55c66 | ||
|  | 2a052b2c38 | ||
|  | 3ed253d2e1 | ||
|  | 5f672fe404 | ||
|  | 10372787ac | ||
|  | bcabb615b4 | ||
|  | 68acf2ec64 | ||
|  | afd24035f4 | ||
|  | 4f28f6b935 | ||
|  | 52bd600690 | ||
|  | c7ce2a8a99 | ||
|  | 9760b1bf98 | ||
|  | d579c267f0 | ||
|  | 21f01d268a | ||
|  | 33782e0492 | ||
|  | 8d20568b7a | ||
|  | 06f38e92ce | ||
|  | 855e96e40e | ||
|  | bb68720ab7 | ||
|  | 044d007a0c | ||
|  | 4ea95fe8e8 | ||
|  | d17c685e4d | ||
|  | bb174da59a | ||
|  | a028b80adb | ||
|  | 3683511e60 | ||
|  | bf359db7f4 | ||
|  | 949d786f1f | ||
|  | 5cd0c1ca4e | ||
|  | 86674991c1 | ||
|  | 8492cda092 | ||
|  | 647303c001 | ||
|  | fe34f8adad | ||
|  | f142a2eb4e | ||
|  | 43eaa3dd63 | ||
|  | a3142713f7 | ||
|  | 876936125a | ||
|  | 7261fb6cef | ||
|  | fba52e6dff | ||
|  | 23ac347fb9 | ||
|  | 082aebb1e0 | ||
|  | 3b18dc4df0 | ||
|  | 39d30b92fa | ||
|  | 733209e04e | ||
|  | a68d697fc5 | ||
|  | e52ece57df | ||
|  | 65681c7da9 | ||
|  | 52b6209905 | ||
|  | 0e37925418 | ||
|  | 1a31d2e431 | ||
|  | c2fab2c7bc | ||
|  | 965b55ba5f | ||
|  | 2c7bb36f8e | ||
|  | 48b829b771 | ||
|  | f13c28db73 | ||
|  | 19157c77e4 | ||
|  | 161cd80d38 | ||
|  | 65db4c8f39 | ||
|  | d600d3a6ed | ||
|  | 5fdd971966 | ||
|  | d77d39d6ad | ||
|  | bf35db26bd | ||
|  | 4d8c8e08a0 | ||
|  | b34681ece8 | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -23,4 +23,6 @@ yarn-error.log* | ||||
|  | ||||
| # miscellaneous | ||||
| .idea | ||||
| config.gypi | ||||
| config.gypi | ||||
| .python-version | ||||
|  | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| 2.7.9 | ||||
| @@ -8,7 +8,7 @@ Desktop client for Zulip. Available for Mac, Linux and Windows. | ||||
| <img src="http://i.imgur.com/ChzTq4F.png"/> | ||||
|  | ||||
| # Download | ||||
| You can download the latest version from the [Releases](https://github.com/zulip/zulip-electron/releases/latest) page. | ||||
| Please see [installation guide](https://zulipchat.com/help/desktop-app-install-guide). | ||||
|  | ||||
| # Features | ||||
| * Sign in to multiple teams | ||||
|   | ||||
| @@ -1,30 +1,51 @@ | ||||
| 'use strict'; | ||||
| const {app, dialog} = require('electron'); | ||||
| const {autoUpdater} = require('electron-updater'); | ||||
| const fs = require('fs'); | ||||
| const { app, dialog } = require('electron'); | ||||
| const { autoUpdater } = require('electron-updater'); | ||||
| const isDev = require('electron-is-dev'); | ||||
|  | ||||
| const ConfigUtil = require('./../renderer/js/utils/config-util.js'); | ||||
|  | ||||
| function appUpdater() { | ||||
| 	// Don't initiate auto-updates in development | ||||
| 	if (isDev) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Create Logs directory | ||||
| 	const LogsDir = `${app.getPath('userData')}/Logs`; | ||||
|  | ||||
| 	if (!fs.existsSync(LogsDir)) { | ||||
| 		fs.mkdirSync(LogsDir); | ||||
| 	} | ||||
|  | ||||
| 	// Log whats happening | ||||
| 	const log = require('electron-log'); | ||||
|  | ||||
| 	log.transports.file.file = `${LogsDir}/updates.log`; | ||||
| 	log.transports.file.level = 'info'; | ||||
| 	autoUpdater.logger = log; | ||||
|  | ||||
| 	// Handle auto updates for beta/pre releases | ||||
| 	autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false; | ||||
|  | ||||
| 	// Ask the user if update is available | ||||
| 	// eslint-disable-next-line no-unused-vars | ||||
| 	autoUpdater.on('update-downloaded', (event, info) => { | ||||
| 	autoUpdater.on('update-downloaded', event => { | ||||
| 		// Ask user to update the app | ||||
| 		dialog.showMessageBox({ | ||||
| 			type: 'question', | ||||
| 			buttons: ['Install and Relaunch', 'Later'], | ||||
| 			buttons: ['Install and Relaunch', 'Install Later'], | ||||
| 			defaultId: 0, | ||||
| 			message: 'A new version of ' + app.getName() + ' has been downloaded', | ||||
| 			message: `A new update ${event.version} has been downloaded`, | ||||
| 			detail: 'It will be installed the next time you restart the application' | ||||
| 		}, response => { | ||||
| 			if (response === 0) { | ||||
| 				setTimeout(() => autoUpdater.quitAndInstall(), 1); | ||||
| 				setTimeout(() => { | ||||
| 					autoUpdater.quitAndInstall(); | ||||
| 					// force app to quit. This is just a workaround, ideally autoUpdater.quitAndInstall() should relaunch the app. | ||||
| 					app.quit(); | ||||
| 				}, 1000); | ||||
| 			} | ||||
| 		}); | ||||
| 	}); | ||||
|   | ||||
							
								
								
									
										16
									
								
								app/main/crash-reporter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/main/crash-reporter.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { crashReporter } = require('electron'); | ||||
|  | ||||
| const crashHandler = () => { | ||||
| 	crashReporter.start({ | ||||
| 		productName: 'zulip-electron', | ||||
| 		companyName: 'Kandra Labs, Inc.', | ||||
| 		submitURL: 'https://zulip-sentry.herokuapp.com/crashreport', | ||||
| 		autoSubmit: true | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
| 	crashHandler | ||||
| }; | ||||
| @@ -1,19 +1,27 @@ | ||||
| 'use strict'; | ||||
| const path = require('path'); | ||||
| const electron = require('electron'); | ||||
| const {app} = require('electron'); | ||||
| const ipc = require('electron').ipcMain; | ||||
| const electronLocalshortcut = require('electron-localshortcut'); | ||||
| const isDev = require('electron-is-dev'); | ||||
| const windowStateKeeper = require('electron-window-state'); | ||||
| const isDev = require('electron-is-dev'); | ||||
| const appMenu = require('./menu'); | ||||
| const {appUpdater} = require('./autoupdater'); | ||||
| const { appUpdater } = require('./autoupdater'); | ||||
| const { crashHandler } = require('./crash-reporter'); | ||||
|  | ||||
| const { setAutoLaunch } = require('./startup'); | ||||
|  | ||||
| const { app, ipcMain } = electron; | ||||
|  | ||||
| const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js'); | ||||
|  | ||||
| // Adds debug features like hotkeys for triggering dev tools and reload | ||||
| require('electron-debug')(); | ||||
| // in development mode | ||||
| if (isDev) { | ||||
| 	require('electron-debug')(); | ||||
| } | ||||
|  | ||||
| // Prevent window being garbage collected | ||||
| let mainWindow; | ||||
| let badgeCount; | ||||
|  | ||||
| let isQuitting = false; | ||||
|  | ||||
| @@ -34,10 +42,6 @@ if (isAlreadyRunning) { | ||||
| 	return app.quit(); | ||||
| } | ||||
|  | ||||
| function isWindowsOrmacOS() { | ||||
| 	return process.platform === 'darwin' || process.platform === 'win32'; | ||||
| } | ||||
|  | ||||
| const APP_ICON = path.join(__dirname, '../resources', 'Icon'); | ||||
|  | ||||
| const iconPath = () => { | ||||
| @@ -50,6 +54,10 @@ function createMainWindow() { | ||||
| 		defaultWidth: 1000, | ||||
| 		defaultHeight: 600 | ||||
| 	}); | ||||
|  | ||||
| 	// Let's keep the window position global so that we can access it in other process | ||||
| 	global.mainWindowState = mainWindowState; | ||||
|  | ||||
| 	const win = new electron.BrowserWindow({ | ||||
| 		// This settings needs to be saved in config | ||||
| 		title: 'Zulip', | ||||
| @@ -89,9 +97,6 @@ function createMainWindow() { | ||||
| 				win.hide(); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Unregister all the shortcuts so that they don't interfare with other apps | ||||
| 		electronLocalshortcut.unregisterAll(mainWindow); | ||||
| 	}); | ||||
|  | ||||
| 	win.setTitle('Zulip'); | ||||
| @@ -119,33 +124,12 @@ function createMainWindow() { | ||||
| 	return win; | ||||
| } | ||||
|  | ||||
| function registerLocalShortcuts(page) { | ||||
| 	// Somehow, reload action cannot be overwritten by the menu item | ||||
| 	electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => { | ||||
| 		page.send('reload-viewer'); | ||||
| 	}); | ||||
|  | ||||
| 	// Also adding these shortcuts because some users might want to use it instead of CMD/Left-Right | ||||
| 	electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => { | ||||
| 		page.send('back'); | ||||
| 	}); | ||||
|  | ||||
| 	electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => { | ||||
| 		page.send('forward'); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| // eslint-disable-next-line max-params | ||||
| app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { | ||||
| 	event.preventDefault(); | ||||
| 	callback(true); | ||||
| }); | ||||
|  | ||||
| app.on('window-all-closed', () => { | ||||
| 	// Unregister all the shortcuts so that they don't interfare with other apps | ||||
| 	electronLocalshortcut.unregisterAll(mainWindow); | ||||
| }); | ||||
|  | ||||
| app.on('activate', () => { | ||||
| 	if (!mainWindow) { | ||||
| 		mainWindow = createMainWindow(); | ||||
| @@ -160,39 +144,41 @@ app.on('ready', () => { | ||||
|  | ||||
| 	const page = mainWindow.webContents; | ||||
|  | ||||
| 	registerLocalShortcuts(page); | ||||
|  | ||||
| 	page.on('dom-ready', () => { | ||||
| 		mainWindow.show(); | ||||
| 	}); | ||||
|  | ||||
| 	page.once('did-frame-finish-load', () => { | ||||
| 		const checkOS = isWindowsOrmacOS(); | ||||
| 		if (checkOS && !isDev) { | ||||
| 			// Initate auto-updates on MacOS and Windows | ||||
| 			appUpdater(); | ||||
| 		} | ||||
| 		// Initate auto-updates on MacOS and Windows | ||||
| 		appUpdater(); | ||||
| 		crashHandler(); | ||||
| 	}); | ||||
|  | ||||
| 	electron.powerMonitor.on('resume', () => { | ||||
| 		page.send('reload-viewer'); | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('focus-app', () => { | ||||
| 	ipcMain.on('focus-app', () => { | ||||
| 		mainWindow.show(); | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('quit-app', () => { | ||||
| 	ipcMain.on('quit-app', () => { | ||||
| 		app.quit(); | ||||
| 	}); | ||||
|  | ||||
| 	// Reload full app not just webview, useful in debugging | ||||
| 	ipc.on('reload-full-app', () => { | ||||
| 	ipcMain.on('reload-full-app', () => { | ||||
| 		mainWindow.reload(); | ||||
| 		page.send('destroytray'); | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('toggle-app', () => { | ||||
| 	ipcMain.on('clear-app-settings', () => { | ||||
| 		global.mainWindowState.unmanage(mainWindow); | ||||
| 		app.relaunch(); | ||||
| 		app.exit(); | ||||
| 	}); | ||||
|  | ||||
| 	ipcMain.on('toggle-app', () => { | ||||
| 		if (mainWindow.isVisible()) { | ||||
| 			mainWindow.hide(); | ||||
| 		} else { | ||||
| @@ -200,57 +186,44 @@ app.on('ready', () => { | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('update-badge', (event, messageCount) => { | ||||
| 		if (process.platform === 'darwin') { | ||||
| 			app.setBadgeCount(messageCount); | ||||
| 		} | ||||
| 		if (process.platform === 'win32') { | ||||
| 			if (!mainWindow.isFocused()) { | ||||
| 				mainWindow.flashFrame(true); | ||||
| 			} | ||||
| 			if (messageCount === 0) { | ||||
| 				mainWindow.setOverlayIcon(null, ''); | ||||
| 			} else { | ||||
| 				page.send('render-taskbar-icon', messageCount); | ||||
| 			} | ||||
| 		} | ||||
| 	ipcMain.on('toggle-badge-option', () => { | ||||
| 		BadgeSettings.updateBadge(badgeCount, mainWindow); | ||||
| 	}); | ||||
|  | ||||
| 	ipcMain.on('update-badge', (event, messageCount) => { | ||||
| 		badgeCount = messageCount; | ||||
| 		BadgeSettings.updateBadge(badgeCount, mainWindow); | ||||
| 		page.send('tray', messageCount); | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('update-taskbar-icon', (event, data, text) => { | ||||
| 		const img = electron.nativeImage.createFromDataURL(data); | ||||
| 		mainWindow.setOverlayIcon(img, text); | ||||
| 	ipcMain.on('update-taskbar-icon', (event, data, text) => { | ||||
| 		BadgeSettings.updateTaskbarIcon(data, text, mainWindow); | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('forward-message', (event, listener, ...params) => { | ||||
| 	ipcMain.on('forward-message', (event, listener, ...params) => { | ||||
| 		page.send(listener, ...params); | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('update-menu', (event, props) => { | ||||
| 	ipcMain.on('update-menu', (event, props) => { | ||||
| 		appMenu.setMenu(props); | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('register-server-tab-shortcut', (event, index) => { | ||||
| 		electronLocalshortcut.register(mainWindow, `CommandOrControl+${index}`, () => { | ||||
| 			// Array index == Shown index - 1 | ||||
| 			page.send('switch-server-tab', index - 1); | ||||
| 		}); | ||||
| 	ipcMain.on('register-server-tab-shortcut', (event, index) => { | ||||
| 		// Array index == Shown index - 1 | ||||
| 		page.send('switch-server-tab', index - 1); | ||||
| 	}); | ||||
|  | ||||
| 	ipc.on('local-shortcuts', (event, enable) => { | ||||
| 		if (enable) { | ||||
| 			registerLocalShortcuts(page); | ||||
| 		} else { | ||||
| 			electronLocalshortcut.unregisterAll(mainWindow); | ||||
| 		} | ||||
| 	ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => { | ||||
| 		setAutoLaunch(AutoLaunchValue); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| app.on('will-quit', () => { | ||||
| 	// Unregister all the shortcuts so that they don't interfare with other apps | ||||
| 	electronLocalshortcut.unregisterAll(mainWindow); | ||||
| }); | ||||
|  | ||||
| app.on('before-quit', () => { | ||||
| 	isQuitting = true; | ||||
| }); | ||||
|  | ||||
| // Send crash reports | ||||
| process.on('uncaughtException', err => { | ||||
| 	console.error(err); | ||||
| 	console.error(err.stack); | ||||
| }); | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| 'use strict'; | ||||
| const os = require('os'); | ||||
| const {dialog, app, shell, BrowserWindow, Menu} = require('electron'); | ||||
| const path = require('path'); | ||||
|  | ||||
| const { app, shell, BrowserWindow, Menu } = require('electron'); | ||||
|  | ||||
| const fs = require('fs-extra'); | ||||
|  | ||||
| const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js'); | ||||
|  | ||||
| @@ -33,7 +37,7 @@ class AppMenu { | ||||
| 			accelerator: 'CommandOrControl+R', | ||||
| 			click(item, focusedWindow) { | ||||
| 				if (focusedWindow) { | ||||
| 					AppMenu.sendAction('reload-viewer'); | ||||
| 					AppMenu.sendAction('reload-current-viewer'); | ||||
| 				} | ||||
| 			} | ||||
| 		}, { | ||||
| @@ -86,9 +90,9 @@ class AppMenu { | ||||
| 			accelerator: 'CommandOrControl+S', | ||||
| 			click(item, focusedWindow) { | ||||
| 				if (focusedWindow) { | ||||
| 					const newValue = !ConfigUtil.getConfigItem('show-sidebar'); | ||||
| 					const newValue = !ConfigUtil.getConfigItem('showSidebar'); | ||||
| 					focusedWindow.webContents.send('toggle-sidebar', newValue); | ||||
| 					ConfigUtil.setConfigItem('show-sidebar', newValue); | ||||
| 					ConfigUtil.setConfigItem('showSidebar', newValue); | ||||
| 				} | ||||
| 			} | ||||
| 		}, { | ||||
| @@ -164,12 +168,12 @@ class AppMenu { | ||||
| 	} | ||||
|  | ||||
| 	getDarwinTpl(props) { | ||||
| 		const {tabs, activeTabIndex} = props; | ||||
| 		const { tabs, activeTabIndex } = props; | ||||
|  | ||||
| 		return [{ | ||||
| 			label: `${app.getName()}`, | ||||
| 			submenu: [{ | ||||
| 				label: 'Zulip Desktop', | ||||
| 				label: 'About Zulip', | ||||
| 				click(item, focusedWindow) { | ||||
| 					if (focusedWindow) { | ||||
| 						AppMenu.sendAction('open-about'); | ||||
| @@ -196,9 +200,10 @@ class AppMenu { | ||||
| 			}, { | ||||
| 				type: 'separator' | ||||
| 			}, { | ||||
| 				label: 'Clear Cache', | ||||
| 				label: 'Reset App Settings', | ||||
| 				accelerator: 'Command+Shift+D', | ||||
| 				click() { | ||||
| 					AppMenu.clearCache(); | ||||
| 					AppMenu.resetAppSettings(); | ||||
| 				} | ||||
| 			}, { | ||||
| 				label: 'Log Out', | ||||
| @@ -263,12 +268,12 @@ class AppMenu { | ||||
| 	} | ||||
|  | ||||
| 	getOtherTpl(props) { | ||||
| 		const {tabs, activeTabIndex} = props; | ||||
| 		const { tabs, activeTabIndex } = props; | ||||
|  | ||||
| 		return [{ | ||||
| 			label: 'File', | ||||
| 			submenu: [{ | ||||
| 				label: 'Zulip Desktop', | ||||
| 				label: 'About Zulip', | ||||
| 				click(item, focusedWindow) { | ||||
| 					if (focusedWindow) { | ||||
| 						AppMenu.sendAction('open-about'); | ||||
| @@ -297,9 +302,10 @@ class AppMenu { | ||||
| 			}, { | ||||
| 				type: 'separator' | ||||
| 			}, { | ||||
| 				label: 'Clear Cache', | ||||
| 				label: 'Reset App Settings', | ||||
| 				accelerator: 'Ctrl+Shift+D', | ||||
| 				click() { | ||||
| 					AppMenu.clearCache(); | ||||
| 					AppMenu.resetAppSettings(); | ||||
| 				} | ||||
| 			}, { | ||||
| 				label: 'Log Out', | ||||
| @@ -363,11 +369,21 @@ class AppMenu { | ||||
| 		win.webContents.send(action, ...params); | ||||
| 	} | ||||
|  | ||||
| 	static clearCache() { | ||||
| 		const win = BrowserWindow.getAllWindows()[0]; | ||||
| 		const ses = win.webContents.session; | ||||
| 		ses.clearCache(() => { | ||||
| 			dialog.showMessageBox({type: 'info', buttons: [], message: 'Cache cleared!'}); | ||||
| 	static resetAppSettings() { | ||||
| 		// We save App's settings/configurations in following files | ||||
| 		const settingFiles = ['window-state.json', 'domain.json', 'settings.json']; | ||||
|  | ||||
| 		settingFiles.forEach(settingFileName => { | ||||
| 			const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName); | ||||
| 			fs.access(getSettingFilesPath, error => { | ||||
| 				if (error) { | ||||
| 					console.log(error); | ||||
| 				} else { | ||||
| 					fs.unlink(getSettingFilesPath, () => { | ||||
| 						AppMenu.sendAction('clear-app-data'); | ||||
| 					}); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										34
									
								
								app/main/startup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/main/startup.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| 'use strict'; | ||||
| const { app } = require('electron'); | ||||
| const AutoLaunch = require('auto-launch'); | ||||
| const isDev = require('electron-is-dev'); | ||||
| const ConfigUtil = require('./../renderer/js/utils/config-util.js'); | ||||
|  | ||||
| const setAutoLaunch = AutoLaunchValue => { | ||||
| 	// Don't run this in development | ||||
| 	if (isDev) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// On Mac, work around a bug in auto-launch where it opens a Terminal window | ||||
| 	// See https://github.com/Teamwork/node-auto-launch/issues/28#issuecomment-222194437 | ||||
|  | ||||
| 	const appPath = process.platform === 'darwin' ? app.getPath('exe').replace(/\.app\/Content.*/, '.app') : undefined; // Use the default | ||||
|  | ||||
| 	const ZulipAutoLauncher = new AutoLaunch({ | ||||
| 		name: 'Zulip', | ||||
| 		path: appPath, | ||||
| 		isHidden: false | ||||
| 	}); | ||||
| 	const autoLaunchOption = ConfigUtil.getConfigItem('startAtLogin', AutoLaunchValue); | ||||
|  | ||||
| 	if (autoLaunchOption) { | ||||
| 		ZulipAutoLauncher.enable(); | ||||
| 	} else { | ||||
| 		ZulipAutoLauncher.disable(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
| 	setAutoLaunch | ||||
| }; | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "zulip", | ||||
|   "productName": "Zulip", | ||||
|   "version": "1.3.0-beta", | ||||
|   "version": "1.6.0-beta", | ||||
|   "description": "Zulip Desktop App", | ||||
|   "license": "Apache-2.0", | ||||
|   "email": "<svnitakash@gmail.com>", | ||||
| @@ -27,15 +27,14 @@ | ||||
|     "InstantMessaging" | ||||
|   ], | ||||
|   "dependencies": { | ||||
|     "electron-debug": "1.4.0", | ||||
|     "electron-is-dev": "0.3.0", | ||||
|     "electron-localshortcut": "2.0.2", | ||||
|     "electron-log": "2.2.7", | ||||
|     "electron-spellchecker": "1.2.0", | ||||
|     "electron-updater": "2.8.5", | ||||
|     "electron-spellchecker": "1.1.2", | ||||
|     "electron-updater": "2.16.2", | ||||
|     "node-json-db": "0.7.3", | ||||
|     "request": "2.81.0", | ||||
|     "wurl": "2.5.0", | ||||
|     "electron-window-state": "4.1.1" | ||||
|     "electron-window-state": "4.1.1", | ||||
|     "auto-launch": "5.0.1" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -18,7 +18,7 @@ body { | ||||
|     background-position: center; | ||||
| } | ||||
|  | ||||
| #sidebar { | ||||
| .toggle-sidebar { | ||||
|     background: #222c31; | ||||
|     width: 54px; | ||||
|     padding: 27px 0 20px 0; | ||||
| @@ -27,6 +27,21 @@ body { | ||||
|     flex-direction: column; | ||||
|     -webkit-app-region: drag; | ||||
|     overflow: hidden; | ||||
|     transition: all 0.5s ease; | ||||
| } | ||||
|  | ||||
| .toggle-sidebar div { | ||||
|     transition: all 0.5s ease-out; | ||||
| } | ||||
|  | ||||
| .sidebar-hide { | ||||
|     width: 0; | ||||
|     transition: all 0.8s ease; | ||||
| } | ||||
|  | ||||
| .sidebar-hide div { | ||||
|     transform: translateX(-100%); | ||||
|     transition: all 0.6s ease-out; | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
| @@ -38,8 +53,8 @@ body { | ||||
|  | ||||
|  | ||||
| /******************* | ||||
|  *   Left Sidebar  * | ||||
|  *******************/ | ||||
|   *   Left Sidebar  * | ||||
|   *******************/ | ||||
|  | ||||
| #tabs-container { | ||||
|     display: flex; | ||||
| @@ -197,8 +212,8 @@ body { | ||||
|  | ||||
|  | ||||
| /******************* | ||||
|  *   Webview Area  * | ||||
|  *******************/ | ||||
|   *   Webview Area  * | ||||
|   *******************/ | ||||
|  | ||||
| #webviews-container { | ||||
|     display: flex; | ||||
| @@ -258,6 +273,33 @@ webview:focus { | ||||
|     right: 68px; | ||||
| } | ||||
|  | ||||
| #add-server-tooltip, | ||||
| .server-tooltip { | ||||
|     font-family: 'arial'; | ||||
|     background: #222c31; | ||||
|     left: 56px; | ||||
|     padding: 10px 20px; | ||||
|     position: fixed; | ||||
|     margin-top: 8px; | ||||
|     z-index: 5000 !important; | ||||
|     color: #fff; | ||||
|     border-radius: 4px; | ||||
|     text-align: center; | ||||
|     width: max-content; | ||||
|     font-size: 14px; | ||||
| } | ||||
|  | ||||
| #add-server-tooltip:after, | ||||
| .server-tooltip:after { | ||||
|     content: " "; | ||||
|     border-top: 8px solid transparent; | ||||
|     border-bottom: 8px solid transparent; | ||||
|     border-right: 8px solid #222c31; | ||||
|     position: absolute; | ||||
|     top: 10px; | ||||
|     left: -5px; | ||||
| } | ||||
|  | ||||
| #collapse-button { | ||||
|     bottom: 30px; | ||||
|     left: 20px; | ||||
| @@ -285,7 +327,9 @@ webview:focus { | ||||
|     display: none !important; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Full screen Popup container  */ | ||||
|  | ||||
| .popup .popuptext { | ||||
|     visibility: hidden; | ||||
|     background-color: #555; | ||||
| @@ -318,4 +362,4 @@ webview:focus { | ||||
|         overflow: hidden; | ||||
|         opacity: 1; | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -3,11 +3,50 @@ body { | ||||
|     height: 100%; | ||||
|     margin: 0; | ||||
|     cursor: default; | ||||
|     user-select: none; | ||||
|     font-family: menu, "Helvetica Neue", sans-serif; | ||||
| 	-webkit-font-smoothing: antialiased; | ||||
|     font-size: 14px; | ||||
|     color: #333; | ||||
|     background: #efefef; | ||||
| } | ||||
|  | ||||
| kbd { | ||||
|     padding: 0.3em 0.8em; | ||||
|     border: 1px solid #ccc; | ||||
|     font-size: 15px; | ||||
|     font-family: Courier New, Courier, monospace; | ||||
|     background-color: #383430; | ||||
|     color: #ededed; | ||||
|     display: inline-block; | ||||
|     margin: 0 0.1em; | ||||
|     font-weight: bold; | ||||
|     white-space: nowrap; | ||||
| } | ||||
|  | ||||
| table, th, td { | ||||
|     border-collapse: collapse; | ||||
|     color: #383430; | ||||
| } | ||||
|  | ||||
| table {  | ||||
|     width: 100%; | ||||
|     margin-top: 18px; | ||||
|     margin-bottom: 18px; | ||||
|  } | ||||
|  | ||||
| table tr:nth-child(even) { background-color: #f7eee6; } | ||||
|  | ||||
| table tr:nth-child(odd) { background-color: #fff8ef; } | ||||
|  | ||||
|  | ||||
| td { padding: 5px; } | ||||
|  | ||||
| td:nth-child(odd) { | ||||
| 		text-align: right; | ||||
| 		width: 50%; | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|   font-family: 'Material Icons'; | ||||
|   font-style: normal; | ||||
| @@ -17,6 +56,11 @@ body { | ||||
|        url(../fonts/MaterialIcons-Regular.ttf) format('truetype'); | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|   font-family: 'Montserrat'; | ||||
|   src: url(../fonts/Montserrat-Regular.ttf) format('truetype'); | ||||
| } | ||||
|  | ||||
| .material-icons { | ||||
|   font-family: 'Material Icons'; | ||||
|   font-weight: normal; | ||||
| @@ -39,7 +83,7 @@ body { | ||||
| #content { | ||||
|     display: flex; | ||||
|     height: 100%; | ||||
|     font-family: sans-serif; | ||||
|     font-family: 'Montserrat'; | ||||
| } | ||||
|  | ||||
| #sidebar { | ||||
| @@ -77,7 +121,9 @@ body { | ||||
|  | ||||
| #settings-header { | ||||
|     font-size: 22px; | ||||
|     color: #5c6166; | ||||
|     color: #222c31; | ||||
|     font-weight: bold; | ||||
|     text-transform: uppercase; | ||||
| } | ||||
|  | ||||
| #settings-container { | ||||
| @@ -88,7 +134,6 @@ body { | ||||
| } | ||||
|  | ||||
| #new-server-container { | ||||
|     margin: 20px 0; | ||||
|     opacity: 1; | ||||
|     transition: opacity 0.3s; | ||||
| } | ||||
| @@ -96,7 +141,8 @@ body { | ||||
| .title { | ||||
|     padding: 4px 0 6px 0; | ||||
|     font-weight: bold; | ||||
|     color: #1e1e1e; | ||||
|     color: #222c31; | ||||
|     text-transform: uppercase; | ||||
| } | ||||
|  | ||||
| .sub-title { | ||||
| @@ -130,7 +176,17 @@ img.server-info-icon { | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| .server-info-url { | ||||
| .setting-input-key { | ||||
|     font-size: 14px; | ||||
|     height: 27px; | ||||
|     line-height: 27px; | ||||
|     font-weight: bold; | ||||
|     background: transparent; | ||||
|     flex-wrap: nowrap; | ||||
|     margin-right: 10px; | ||||
| } | ||||
|  | ||||
| .setting-input-value { | ||||
|     flex-grow: 1; | ||||
|     font-size: 14px; | ||||
|     height: 24px; | ||||
| @@ -141,8 +197,12 @@ img.server-info-icon { | ||||
|     max-width: 500px; | ||||
| } | ||||
|  | ||||
| .server-info-value:focus { | ||||
|     border-bottom: #388E3C 1px solid; | ||||
| .setting-input-value:focus { | ||||
|     border-bottom: #7cb980 1px solid; | ||||
| } | ||||
|  | ||||
| .setting-block { | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| .actions-container { | ||||
| @@ -158,7 +218,6 @@ img.server-info-icon { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     padding: 0 10px; | ||||
|     border-radius: 2px; | ||||
|     margin-right: 10px; | ||||
| } | ||||
|  | ||||
| @@ -186,12 +245,12 @@ img.server-info-icon { | ||||
|  | ||||
| .settings-card { | ||||
|     display: flex; | ||||
|     padding: 16px 30px; | ||||
|     flex-wrap: wrap; | ||||
|     padding: 12px 30px; | ||||
|     margin: 10px 0 20px 0; | ||||
|     background: #fff; | ||||
|     border-radius: 2px; | ||||
|     width: 540px; | ||||
|     box-shadow: 1px 2px 4px #bcbcbc; | ||||
|     width: 70%; | ||||
|     border-left: 8px solid #bcbcbc; | ||||
| } | ||||
|  | ||||
| .hidden { | ||||
| @@ -200,15 +259,19 @@ img.server-info-icon { | ||||
| } | ||||
|  | ||||
| .red { | ||||
|     color: #ef5350; | ||||
|     background: #ffebee; | ||||
|     border: 1px solid #ef5350; | ||||
|     color: #ffffff; | ||||
|     background: #ef5350; | ||||
|     padding: 3px; | ||||
|     padding-right: 10px; | ||||
|     padding-left: 10px; | ||||
| } | ||||
|  | ||||
| .green { | ||||
|     color: #388E3C; | ||||
|     background: #E8F5E9; | ||||
|     border: 1px solid #388E3C; | ||||
| .blue { | ||||
|     color: #ffffff; | ||||
|     background: #4EBFAC; | ||||
|     padding: 3px; | ||||
|     padding-right: 10px; | ||||
|     padding-left: 10px; | ||||
| } | ||||
|  | ||||
| .grey { | ||||
| @@ -219,8 +282,10 @@ img.server-info-icon { | ||||
|  | ||||
| .setting-row { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
|     width: 100%; | ||||
|     margin: 6px; | ||||
| } | ||||
|  | ||||
| .code { | ||||
| @@ -231,4 +296,94 @@ i.open-tab-button { | ||||
|     padding: 0 5px; | ||||
|     font-size: 18px; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| .reset-data-button { | ||||
|     display: inline-block; | ||||
|     border: none; | ||||
|     padding: 10px; | ||||
|     width: 120px; | ||||
|     cursor: pointer; | ||||
|     font-size: 13px; | ||||
|     transition: background-color 0.2s ease; | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| .reset-data-button:hover { | ||||
|     background-color: #3c9f8d; | ||||
|     color: #fff; | ||||
| } | ||||
|  | ||||
| #server-info-container { | ||||
| 		min-height: calc(100% - 235px); | ||||
| } | ||||
|  | ||||
| #create-organization-container { | ||||
| 		font-size: 1.15em; | ||||
| 		margin-bottom: 15px; | ||||
| } | ||||
|  | ||||
| #create-organization-container i { | ||||
| 	position: relative; | ||||
| 	top: 3px; | ||||
| } | ||||
|  | ||||
| #open-create-org-link { | ||||
| 		color: #666; | ||||
| 		cursor: pointer; | ||||
| 		text-decoration: none; | ||||
| } | ||||
|  | ||||
| #open-create-org-link:hover { | ||||
| 		color: #005580;; | ||||
| 		text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .toggle { | ||||
|   position: absolute; | ||||
|   margin-left: -9999px; | ||||
|   visibility: hidden; | ||||
| } | ||||
| .toggle + label { | ||||
|   display: block; | ||||
|   position: relative; | ||||
|   cursor: pointer; | ||||
|   outline: none; | ||||
|   user-select: none; | ||||
| } | ||||
|  | ||||
| input.toggle-round + label { | ||||
|   padding: 2px; | ||||
|   width: 50px; | ||||
|   height: 25px; | ||||
|   background-color: #dddddd; | ||||
|   border-radius: 25px; | ||||
| } | ||||
| input.toggle-round + label:before, | ||||
| input.toggle-round + label:after { | ||||
|   display: block; | ||||
|   position: absolute; | ||||
|   top: 2px; | ||||
|   left: 2px; | ||||
|   bottom: 2px; | ||||
|   content: ""; | ||||
| } | ||||
| input.toggle-round + label:before { | ||||
|   right: 2px; | ||||
|   background-color: #f1f1f1; | ||||
|   border-radius: 25px; | ||||
|   transition: background 0.4s; | ||||
| } | ||||
| input.toggle-round + label:after { | ||||
|   width: 25px; | ||||
|   height: 25px; | ||||
|   background-color: #fff; | ||||
|   border-radius: 100%; | ||||
|   transition: margin 0.4s; | ||||
| } | ||||
| input.toggle-round:checked + label:before { | ||||
|   background-color: #4EBFAC; | ||||
| } | ||||
| input.toggle-round:checked + label:after { | ||||
|   margin-left: 25px; | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								app/renderer/fonts/Montserrat-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/renderer/fonts/Montserrat-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -8,6 +8,7 @@ const {ipcRenderer} = require('electron'); | ||||
| class ServerTab extends Tab { | ||||
| 	template() { | ||||
| 		return `<div class="tab"> | ||||
| 					<div class="server-tooltip" style="display:none"></div> | ||||
| 					<div class="server-tab-badge"></div> | ||||
| 					<div class="server-tab"> | ||||
| 					<img class="server-icons" src='${this.props.icon}'/> | ||||
|   | ||||
| @@ -21,6 +21,8 @@ class Tab extends BaseComponent { | ||||
|  | ||||
| 	registerListeners() { | ||||
| 		this.$el.addEventListener('click', this.props.onClick); | ||||
| 		this.$el.addEventListener('mouseover', this.props.onHover); | ||||
| 		this.$el.addEventListener('mouseout', this.props.onHoverOut); | ||||
| 	} | ||||
|  | ||||
| 	isLoading() { | ||||
|   | ||||
| @@ -6,7 +6,7 @@ const fs = require('fs'); | ||||
| const DomainUtil = require(__dirname + '/../utils/domain-util.js'); | ||||
| const SystemUtil = require(__dirname + '/../utils/system-util.js'); | ||||
| const LinkUtil = require(__dirname + '/../utils/link-util.js'); | ||||
| const {shell} = require('electron').remote; | ||||
| const { shell, app } = require('electron').remote; | ||||
|  | ||||
| const BaseComponent = require(__dirname + '/../components/base.js'); | ||||
|  | ||||
| @@ -28,6 +28,7 @@ class WebView extends BaseComponent { | ||||
| 					${this.props.nodeIntegration ? 'nodeIntegration' : ''} | ||||
| 					disablewebsecurity | ||||
| 					${this.props.preload ? 'preload="js/preload.js"' : ''} | ||||
| 					partition="persist:webviewsession" | ||||
| 					webpreferences="allowRunningInsecureContent, javascript=yes"> | ||||
| 				</webview>`; | ||||
| 	} | ||||
| @@ -41,7 +42,7 @@ class WebView extends BaseComponent { | ||||
|  | ||||
| 	registerListeners() { | ||||
| 		this.$el.addEventListener('new-window', event => { | ||||
| 			const {url} = event; | ||||
| 			const { url } = event; | ||||
| 			const domainPrefix = DomainUtil.getDomain(this.props.index).url; | ||||
|  | ||||
| 			if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) { | ||||
| @@ -54,18 +55,30 @@ class WebView extends BaseComponent { | ||||
| 		}); | ||||
|  | ||||
| 		this.$el.addEventListener('page-title-updated', event => { | ||||
| 			const {title} = event; | ||||
| 			const { title } = event; | ||||
| 			this.badgeCount = this.getBadgeCount(title); | ||||
| 			this.props.onTitleChange(); | ||||
| 		}); | ||||
|  | ||||
| 		this.$el.addEventListener('page-favicon-updated', event => { | ||||
| 			const { favicons } = event; | ||||
| 			// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like | ||||
| 			// https://chat.zulip.org/static/images/favicon/favicon-pms.png | ||||
| 			if (favicons[0].indexOf('favicon-pms') > 0 && process.platform === 'darwin') { | ||||
| 				// This api is only supported on macOS | ||||
| 				app.dock.setBadge('●'); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		this.$el.addEventListener('dom-ready', () => { | ||||
| 			this.$el.classList.add('onload'); | ||||
| 			if (this.props.role === 'server') { | ||||
| 				this.$el.classList.add('onload'); | ||||
| 			} | ||||
| 			this.show(); | ||||
| 		}); | ||||
|  | ||||
| 		this.$el.addEventListener('did-fail-load', event => { | ||||
| 			const {errorDescription} = event; | ||||
| 			const { errorDescription } = event; | ||||
| 			const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0); | ||||
| 			if (hasConnectivityErr) { | ||||
| 				console.error('error', errorDescription); | ||||
| @@ -96,11 +109,13 @@ class WebView extends BaseComponent { | ||||
|  | ||||
| 		this.$el.classList.remove('disabled'); | ||||
| 		setTimeout(() => { | ||||
| 			this.$el.classList.remove('onload'); | ||||
| 			if (this.props.role === 'server') { | ||||
| 				this.$el.classList.remove('onload'); | ||||
| 			} | ||||
| 		}, 1000); | ||||
| 		this.focus(); | ||||
| 		this.loading = false; | ||||
| 		this.props.onTitleChange(this.$el.getTitle()); | ||||
| 		this.props.onTitleChange(); | ||||
| 		// Injecting preload css in webview to override some css rules | ||||
| 		this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8')); | ||||
| 	} | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| require(__dirname + '/js/tray.js'); | ||||
| const {ipcRenderer} = require('electron'); | ||||
| const { ipcRenderer, remote } = require('electron'); | ||||
|  | ||||
| const { session } = remote; | ||||
|  | ||||
| const DomainUtil = require(__dirname + '/js/utils/domain-util.js'); | ||||
| const WebView = require(__dirname + '/js/components/webview.js'); | ||||
| @@ -19,8 +21,11 @@ class ServerManagerView { | ||||
| 		this.$settingsButton = $actionsContainer.querySelector('#settings-action'); | ||||
| 		this.$webviewsContainer = document.getElementById('webviews-container'); | ||||
|  | ||||
| 		this.$addServerTooltip = document.getElementById('add-server-tooltip'); | ||||
| 		this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip'); | ||||
| 		this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip'); | ||||
| 		this.$serverIconTooltip = document.getElementsByClassName('server-tooltip'); | ||||
|  | ||||
| 		this.$sidebar = document.getElementById('sidebar'); | ||||
|  | ||||
| 		this.$fullscreenPopup = document.getElementById('fullscreen-popup'); | ||||
| @@ -33,14 +38,35 @@ class ServerManagerView { | ||||
| 	} | ||||
|  | ||||
| 	init() { | ||||
| 		this.initSidebar(); | ||||
| 		this.initTabs(); | ||||
| 		this.initActions(); | ||||
| 		this.registerIpcs(); | ||||
| 		this.loadProxy().then(() => { | ||||
| 			this.initSidebar(); | ||||
| 			this.initTabs(); | ||||
| 			this.initActions(); | ||||
| 			this.registerIpcs(); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	loadProxy() { | ||||
| 		return new Promise(resolve => { | ||||
| 			const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false); | ||||
| 			if (proxyEnabled) { | ||||
| 				session.fromPartition('persist:webviewsession').setProxy({ | ||||
| 					pacScript: ConfigUtil.getConfigItem('proxyPAC', ''), | ||||
| 					proxyRules: ConfigUtil.getConfigItem('proxyRules', ''), | ||||
| 					proxyBypassRules: ConfigUtil.getConfigItem('proxyBypass', '') | ||||
| 				}, resolve); | ||||
| 			} else { | ||||
| 				session.fromPartition('persist:webviewsession').setProxy({ | ||||
| 					pacScript: '', | ||||
| 					proxyRules: '', | ||||
| 					proxyBypassRules: '' | ||||
| 				}, resolve); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	initSidebar() { | ||||
| 		const showSidebar = ConfigUtil.getConfigItem('show-sidebar', true); | ||||
| 		const showSidebar = ConfigUtil.getConfigItem('showSidebar', true); | ||||
| 		this.toggleSidebar(showSidebar); | ||||
| 	} | ||||
|  | ||||
| @@ -49,13 +75,14 @@ class ServerManagerView { | ||||
| 		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); | ||||
| 			} | ||||
| 			this.activateTab(0); | ||||
| 			// Open last active tab | ||||
| 			this.activateTab(ConfigUtil.getConfigItem('lastActiveTab')); | ||||
| 		} else { | ||||
| 			this.openSettings('Servers'); | ||||
| 		} | ||||
|  | ||||
| 		ipcRenderer.send('local-shortcuts', true); | ||||
| 	} | ||||
|  | ||||
| 	initServer(server, index) { | ||||
| @@ -63,8 +90,10 @@ class ServerManagerView { | ||||
| 			role: 'server', | ||||
| 			icon: server.icon, | ||||
| 			$root: this.$tabsContainer, | ||||
| 			onClick: this.activateTab.bind(this, index), | ||||
| 			onClick: this.activateLastTab.bind(this, index), | ||||
| 			index, | ||||
| 			onHover: this.onHover.bind(this, index, server.alias), | ||||
| 			onHoverOut: this.onHoverOut.bind(this, index), | ||||
| 			webview: new WebView({ | ||||
| 				$root: this.$webviewsContainer, | ||||
| 				index, | ||||
| @@ -91,20 +120,30 @@ class ServerManagerView { | ||||
| 		this.$settingsButton.addEventListener('click', () => { | ||||
| 			this.openSettings('General'); | ||||
| 		}); | ||||
| 		this.$reloadButton.addEventListener('mouseover', () => { | ||||
| 			this.$reloadTooltip.removeAttribute('style'); | ||||
|  | ||||
| 		this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip); | ||||
| 		this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip); | ||||
| 		this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip); | ||||
| 	} | ||||
|  | ||||
| 	sidebarHoverEvent(SidebarButton, SidebarTooltip) { | ||||
| 		SidebarButton.addEventListener('mouseover', () => { | ||||
| 			SidebarTooltip.removeAttribute('style'); | ||||
| 		}); | ||||
| 		this.$reloadButton.addEventListener('mouseout', () => { | ||||
| 			this.$reloadTooltip.style.display = 'none'; | ||||
| 		}); | ||||
| 		this.$settingsButton.addEventListener('mouseover', () => { | ||||
| 			this.$settingsTooltip.removeAttribute('style'); | ||||
| 		}); | ||||
| 		this.$settingsButton.addEventListener('mouseout', () => { | ||||
| 			this.$settingsTooltip.style.display = 'none'; | ||||
| 		SidebarButton.addEventListener('mouseout', () => { | ||||
| 			SidebarTooltip.style.display = 'none'; | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	onHover(index, serverName) { | ||||
| 		this.$serverIconTooltip[index].innerHTML = serverName; | ||||
| 		this.$serverIconTooltip[index].removeAttribute('style'); | ||||
| 	} | ||||
|  | ||||
| 	onHoverOut(index) { | ||||
| 		this.$serverIconTooltip[index].style.display = 'none'; | ||||
| 	} | ||||
|  | ||||
| 	openFunctionalTab(tabProps) { | ||||
| 		if (this.functionalTabs[tabProps.name] !== undefined) { | ||||
| 			this.activateTab(this.functionalTabs[tabProps.name]); | ||||
| @@ -163,8 +202,15 @@ class ServerManagerView { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	activateLastTab(index) { | ||||
| 		// Open last active tab | ||||
| 		ConfigUtil.setConfigItem('lastActiveTab', index); | ||||
| 		// Open all the tabs in background | ||||
| 		this.activateTab(index); | ||||
| 	} | ||||
|  | ||||
| 	activateTab(index, hideOldTab = true) { | ||||
| 		if (this.tabs[index].webview.loading) { | ||||
| 		if (!this.tabs[index]) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| @@ -210,16 +256,24 @@ class ServerManagerView { | ||||
| 		// Clear DOM elements | ||||
| 		this.$tabsContainer.innerHTML = ''; | ||||
| 		this.$webviewsContainer.innerHTML = ''; | ||||
|  | ||||
| 		// Destroy shortcuts | ||||
| 		ipcRenderer.send('local-shortcuts', false); | ||||
| 	} | ||||
|  | ||||
| 	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); | ||||
|  | ||||
| 		// Destroy the current view and re-initiate it | ||||
| 		this.destroyView(); | ||||
| 		this.initTabs(); | ||||
| 	} | ||||
|  | ||||
| 	// This will trigger when pressed CTRL/CMD + R [WIP] | ||||
| 	// It won't reload the current view properly when you add/delete a server. | ||||
| 	reloadCurrentView() { | ||||
| 		this.$reloadButton.click(); | ||||
| 	} | ||||
|  | ||||
| 	updateBadge() { | ||||
| 		let messageCountAll = 0; | ||||
| 		for (let i = 0; i < this.tabs.length; i++) { | ||||
| @@ -235,9 +289,9 @@ class ServerManagerView { | ||||
|  | ||||
| 	toggleSidebar(show) { | ||||
| 		if (show) { | ||||
| 			this.$sidebar.classList.remove('hidden'); | ||||
| 			this.$sidebar.classList.remove('sidebar-hide'); | ||||
| 		} else { | ||||
| 			this.$sidebar.classList.add('hidden'); | ||||
| 			this.$sidebar.classList.add('sidebar-hide'); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -270,16 +324,30 @@ class ServerManagerView { | ||||
|  | ||||
| 		ipcRenderer.on('open-about', this.openAbout.bind(this)); | ||||
|  | ||||
| 		ipcRenderer.on('reload-viewer', this.reloadView.bind(this)); | ||||
| 		ipcRenderer.on('reload-viewer', this.reloadView.bind(this, this.tabs[this.activeTabIndex].props.index)); | ||||
|  | ||||
| 		ipcRenderer.on('reload-current-viewer', this.reloadCurrentView.bind(this)); | ||||
|  | ||||
| 		ipcRenderer.on('hard-reload', () => { | ||||
| 			ipcRenderer.send('reload-full-app'); | ||||
| 		}); | ||||
|  | ||||
| 		ipcRenderer.on('clear-app-data', () => { | ||||
| 			ipcRenderer.send('clear-app-settings'); | ||||
| 		}); | ||||
|  | ||||
| 		ipcRenderer.on('switch-server-tab', (event, index) => { | ||||
| 			this.activateTab(index); | ||||
| 		}); | ||||
|  | ||||
| 		ipcRenderer.on('reload-proxy', (event, showAlert) => { | ||||
| 			this.loadProxy().then(() => { | ||||
| 				if (showAlert) { | ||||
| 					alert('Proxy settings saved!'); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		ipcRenderer.on('toggle-sidebar', (event, show) => { | ||||
| 			this.toggleSidebar(show); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const {remote} = require('electron'); | ||||
| const { remote } = require('electron'); | ||||
|  | ||||
| const ConfigUtil = require(__dirname + '/utils/config-util.js'); | ||||
|  | ||||
| @@ -12,11 +12,19 @@ app.setAppUserModelId('org.zulip.zulip-electron'); | ||||
|  | ||||
| const NativeNotification = window.Notification; | ||||
|  | ||||
| class SilentNotification extends NativeNotification { | ||||
| class baseNotification extends NativeNotification { | ||||
| 	constructor(title, opts) { | ||||
| 		opts.silent = ConfigUtil.getConfigItem('silent') || false; | ||||
| 		super(title, opts); | ||||
| 	} | ||||
| 	static requestPermission() { | ||||
| 		return; // eslint-disable-line no-useless-return | ||||
| 	} | ||||
| 	// Override default Notification permission | ||||
| 	static get permission() { | ||||
| 		return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied'; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| window.Notification = SilentNotification; | ||||
| window.Notification = baseNotification; | ||||
|  | ||||
|   | ||||
							
								
								
									
										63
									
								
								app/renderer/js/pages/preference/badge-settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								app/renderer/js/pages/preference/badge-settings.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| 'use strict'; | ||||
| const electron = require('electron'); | ||||
| const { app } = require('electron'); | ||||
|  | ||||
| const ConfigUtil = require(__dirname + '/../../utils/config-util.js'); | ||||
|  | ||||
| let instance = null; | ||||
|  | ||||
| class BadgeSettings { | ||||
| 	constructor() { | ||||
| 		if (instance) { | ||||
| 			return instance; | ||||
| 		} else { | ||||
| 			instance = this; | ||||
| 		} | ||||
|  | ||||
| 		return instance; | ||||
| 	} | ||||
|  | ||||
| 	showBadgeCount(messageCount, mainWindow) { | ||||
| 		if (process.platform === 'darwin') { | ||||
| 			app.setBadgeCount(messageCount); | ||||
| 		} | ||||
| 		if (process.platform === 'win32') { | ||||
| 			this.updateOverlayIcon(messageCount, mainWindow); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	hideBadgeCount(mainWindow) { | ||||
| 		if (process.platform === 'darwin') { | ||||
| 			app.setBadgeCount(0); | ||||
| 		} | ||||
| 		if (process.platform === 'win32') { | ||||
| 			mainWindow.setOverlayIcon(null, ''); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	updateBadge(badgeCount, mainWindow) { | ||||
| 		if (ConfigUtil.getConfigItem('badgeOption', true)) { | ||||
| 			this.showBadgeCount(badgeCount, mainWindow); | ||||
| 		} else { | ||||
| 			this.hideBadgeCount(mainWindow); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	updateOverlayIcon(messageCount, mainWindow) { | ||||
| 		if (!mainWindow.isFocused()) { | ||||
| 			mainWindow.flashFrame(ConfigUtil.getConfigItem('flashTaskbarOnMessage')); | ||||
| 		} | ||||
| 		if (messageCount === 0) { | ||||
| 			mainWindow.setOverlayIcon(null, ''); | ||||
| 		} else { | ||||
| 			mainWindow.webContents.send('render-taskbar-icon', messageCount); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	updateTaskbarIcon(data, text, mainWindow) { | ||||
| 		const img = electron.nativeImage.createFromDataURL(data); | ||||
| 		mainWindow.setOverlayIcon(img, text); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = new BadgeSettings(); | ||||
							
								
								
									
										46
									
								
								app/renderer/js/pages/preference/base-section.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/renderer/js/pages/preference/base-section.js
									
									
									
									
									
										Normal 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; | ||||
							
								
								
									
										37
									
								
								app/renderer/js/pages/preference/create-new-org.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/renderer/js/pages/preference/create-new-org.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const BaseComponent = require(__dirname + '/../../components/base.js'); | ||||
| const shell = require('electron').shell; | ||||
|  | ||||
| class CreateOrganziation extends BaseComponent { | ||||
| 	constructor(props) { | ||||
| 		super(); | ||||
| 		this.props = props; | ||||
| 	} | ||||
|  | ||||
| 	template() { | ||||
| 		return ` | ||||
| 			<div class="setting-row"> | ||||
| 				<div class="setting-description"> | ||||
| 					<span id="open-create-org-link">Create a new organization on zulipchat.com<i class="material-icons open-tab-button">open_in_new</i></span> | ||||
| 				</div> | ||||
| 				<div class="setting-control"></div> | ||||
| 			</div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	init() { | ||||
| 		this.props.$root.innerHTML = this.template(); | ||||
| 		this.openCreateNewOrgExternalLink(); | ||||
| 	} | ||||
|  | ||||
| 	openCreateNewOrgExternalLink() { | ||||
| 		const link = 'https://zulipchat.com/beta/'; | ||||
| 		const externalCreateNewOrgEl = document.getElementById('open-create-org-link'); | ||||
| 		externalCreateNewOrgEl.addEventListener('click', () => { | ||||
| 			shell.openExternal(link); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = CreateOrganziation; | ||||
| @@ -1,11 +1,15 @@ | ||||
| 'use strict'; | ||||
| const path = require('path'); | ||||
|  | ||||
| const {ipcRenderer} = require('electron'); | ||||
| const { ipcRenderer } = require('electron'); | ||||
| const { app, dialog } = require('electron').remote; | ||||
|  | ||||
| const BaseComponent = require(__dirname + '/../../components/base.js'); | ||||
| const fs = require('fs-extra'); | ||||
|  | ||||
| const BaseSection = require(__dirname + '/base-section.js'); | ||||
| const ConfigUtil = require(__dirname + '/../../utils/config-util.js'); | ||||
|  | ||||
| class GeneralSection extends BaseComponent { | ||||
| class GeneralSection extends BaseSection { | ||||
| 	constructor(props) { | ||||
| 		super(); | ||||
| 		this.props = props; | ||||
| @@ -13,144 +17,223 @@ class GeneralSection extends BaseComponent { | ||||
|  | ||||
| 	template() { | ||||
| 		return ` | ||||
|             <div class="settings-pane" id="server-settings-pane"> | ||||
|                 <div class="title">Tray Options</div> | ||||
|                 <div id="tray-option-settings" class="settings-card"> | ||||
| 					<div class="setting-row"> | ||||
|             <div class="settings-pane"> | ||||
|                 <div class="title">Appearance</div> | ||||
|                 <div id="appearance-option-settings" class="settings-card"> | ||||
| 					<div class="setting-row" id="tray-option"> | ||||
| 						<div class="setting-description">Show app icon in system tray</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div class="title">App Updates</div> | ||||
|                 <div id="betaupdate-option-settings" class="settings-card"> | ||||
| 					<div class="setting-row"> | ||||
| 						<div class="setting-description">Get beta updates</div> | ||||
| 					<div class="setting-row" id="sidebar-option"> | ||||
| 						<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+S</span>)</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 					<div class="setting-row" id="badge-option"> | ||||
| 						<div class="setting-description">Show app unread badge</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">Flash taskbar on new message</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div class="title">Desktop Notification</div> | ||||
|                 <div id="silent-option-settings" class="settings-card"> | ||||
| 					<div class="setting-row"> | ||||
| 						<div class="setting-description">Mute all sounds from Zulip (requires reload)</div> | ||||
| 				<div class="settings-card"> | ||||
| 					<div class="setting-row" id="show-notification-option"> | ||||
| 						<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">Mute all sounds from Zulip</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div class="title">User Interface</div> | ||||
|                 <div id="ui-option-settings" class="settings-card"> | ||||
| 					<div class="setting-row" id="sidebar-option"> | ||||
| 						<div class="setting-description">Show sidebar (<span class="code">CmdOrCtrl+S</span>)</div> | ||||
| 				<div class="title">App Updates</div> | ||||
|                 <div class="settings-card"> | ||||
| 					<div class="setting-row" id="betaupdate-option"> | ||||
| 						<div class="setting-description">Get beta updates</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div class="title">Functionality</div> | ||||
|                 <div class="settings-card"> | ||||
| 					<div class="setting-row" id="startAtLogin-option"> | ||||
| 						<div class="setting-description">Start app at login</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 					<div class="setting-row" id="enable-spellchecker-option"> | ||||
| 					<div class="setting-description">Enable Spellchecker (requires restart)</div> | ||||
| 					<div class="setting-control"></div> | ||||
| 				</div> | ||||
| 				</div> | ||||
| 				<div class="title">Reset Application Data</div> | ||||
|                 <div class="settings-card"> | ||||
| 					<div class="setting-row" id="resetdata-option"> | ||||
| 						<div class="setting-description">This will delete all application data including all added accounts and preferences | ||||
| 						</div> | ||||
| 						<button class="reset-data-button blue">Reset App Data</button> | ||||
| 					</div> | ||||
| 				</div> | ||||
|             </div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	settingsOptionTemplate(settingOption) { | ||||
| 		if (settingOption) { | ||||
| 			return ` | ||||
| 				<div class="action green"> | ||||
| 					<span>On</span> | ||||
| 				</div> | ||||
| 			`; | ||||
| 		} else { | ||||
| 			return ` | ||||
| 				<div class="action red"> | ||||
| 					<span>Off</span> | ||||
| 				</div> | ||||
| 			`; | ||||
| 	init() { | ||||
| 		this.props.$root.innerHTML = this.template(); | ||||
| 		this.updateTrayOption(); | ||||
| 		this.updateBadgeOption(); | ||||
| 		this.updateSilentOption(); | ||||
| 		this.updateUpdateOption(); | ||||
| 		this.updateSidebarOption(); | ||||
| 		this.updateStartAtLoginOption(); | ||||
| 		this.updateResetDataOption(); | ||||
| 		this.showDesktopNotification(); | ||||
| 		this.enableSpellchecker(); | ||||
|  | ||||
| 		// Platform specific settings | ||||
| 		// Flashing taskbar on Windows | ||||
| 		if (process.platform === 'win32') { | ||||
| 			this.updateFlashTaskbar(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	trayOptionTemplate(trayOption) { | ||||
| 		this.settingsOptionTemplate(trayOption); | ||||
| 	} | ||||
|  | ||||
| 	updateOptionTemplate(updateOption) { | ||||
| 		this.settingsOptionTemplate(updateOption); | ||||
| 	} | ||||
|  | ||||
| 	silentOptionTemplate(silentOption) { | ||||
| 		this.settingsOptionTemplate(silentOption); | ||||
| 	} | ||||
|  | ||||
| 	sidebarToggleTemplate(toggleOption) { | ||||
| 		this.settingsOptionTemplate(toggleOption); | ||||
| 	} | ||||
|  | ||||
| 	init() { | ||||
| 		this.props.$root.innerHTML = this.template(); | ||||
| 		this.initTrayOption(); | ||||
| 		this.initUpdateOption(); | ||||
| 		this.initSilentOption(); | ||||
| 		this.initSidebarToggle(); | ||||
| 	} | ||||
|  | ||||
| 	initTrayOption() { | ||||
| 		this.$trayOptionSettings = document.querySelector('#tray-option-settings .setting-control'); | ||||
| 		this.$trayOptionSettings.innerHTML = ''; | ||||
|  | ||||
| 		const trayOption = ConfigUtil.getConfigItem('trayIcon', true); | ||||
| 		const $trayOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(trayOption)); | ||||
| 		this.$trayOptionSettings.appendChild($trayOption); | ||||
|  | ||||
| 		$trayOption.addEventListener('click', () => { | ||||
| 			const newValue = !ConfigUtil.getConfigItem('trayIcon'); | ||||
| 			ConfigUtil.setConfigItem('trayIcon', newValue); | ||||
| 			ipcRenderer.send('forward-message', 'toggletray'); | ||||
| 			this.initTrayOption(); | ||||
| 	updateTrayOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#tray-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('trayIcon', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('trayIcon'); | ||||
| 				ConfigUtil.setConfigItem('trayIcon', newValue); | ||||
| 				ipcRenderer.send('forward-message', 'toggletray'); | ||||
| 				this.updateTrayOption(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	initUpdateOption() { | ||||
| 		this.$updateOptionSettings = document.querySelector('#betaupdate-option-settings .setting-control'); | ||||
| 		this.$updateOptionSettings.innerHTML = ''; | ||||
|  | ||||
| 		const updateOption = ConfigUtil.getConfigItem('betaUpdate', false); | ||||
| 		const $updateOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(updateOption)); | ||||
| 		this.$updateOptionSettings.appendChild($updateOption); | ||||
|  | ||||
| 		$updateOption.addEventListener('click', () => { | ||||
| 			const newValue = !ConfigUtil.getConfigItem('betaUpdate'); | ||||
| 			ConfigUtil.setConfigItem('betaUpdate', newValue); | ||||
| 			this.initUpdateOption(); | ||||
| 	updateBadgeOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#badge-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('badgeOption', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('badgeOption'); | ||||
| 				ConfigUtil.setConfigItem('badgeOption', newValue); | ||||
| 				ipcRenderer.send('toggle-badge-option', newValue); | ||||
| 				this.updateBadgeOption(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	initSilentOption() { | ||||
| 		this.$silentOptionSettings = document.querySelector('#silent-option-settings .setting-control'); | ||||
| 		this.$silentOptionSettings.innerHTML = ''; | ||||
|  | ||||
| 		const silentOption = ConfigUtil.getConfigItem('silent', false); | ||||
| 		const $silentOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(silentOption)); | ||||
| 		this.$silentOptionSettings.appendChild($silentOption); | ||||
|  | ||||
| 		$silentOption.addEventListener('click', () => { | ||||
| 			const newValue = !ConfigUtil.getConfigItem('silent', true); | ||||
| 			ConfigUtil.setConfigItem('silent', newValue); | ||||
| 			this.initSilentOption(); | ||||
| 	updateFlashTaskbar() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#flash-taskbar-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('flashTaskbarOnMessage'); | ||||
| 				ConfigUtil.setConfigItem('flashTaskbarOnMessage', newValue); | ||||
| 				this.updateFlashTaskbar(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	initSidebarToggle() { | ||||
| 		this.$sidebarOptionSettings = document.querySelector('#ui-option-settings #sidebar-option .setting-control'); | ||||
| 		this.$sidebarOptionSettings.innerHTML = ''; | ||||
|  | ||||
| 		const sidebarOption = ConfigUtil.getConfigItem('show-sidebar', true); | ||||
| 		const $sidebarOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(sidebarOption)); | ||||
| 		this.$sidebarOptionSettings.appendChild($sidebarOption); | ||||
|  | ||||
| 		$sidebarOption.addEventListener('click', () => { | ||||
| 			const newValue = !ConfigUtil.getConfigItem('show-sidebar'); | ||||
| 			ConfigUtil.setConfigItem('show-sidebar', newValue); | ||||
| 			ipcRenderer.send('forward-message', 'toggle-sidebar', newValue); | ||||
| 			this.initSidebarToggle(); | ||||
| 	updateUpdateOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#betaupdate-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('betaUpdate', false), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('betaUpdate'); | ||||
| 				ConfigUtil.setConfigItem('betaUpdate', newValue); | ||||
| 				this.updateUpdateOption(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	handleServerInfoChange() { | ||||
| 		ipcRenderer.send('forward-message', 'reload-viewer'); | ||||
| 	updateSilentOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#silent-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('silent', false), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('silent', true); | ||||
| 				ConfigUtil.setConfigItem('silent', newValue); | ||||
| 				this.updateSilentOption(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	showDesktopNotification() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#show-notification-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('showNotification', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('showNotification', true); | ||||
| 				ConfigUtil.setConfigItem('showNotification', newValue); | ||||
| 				this.showDesktopNotification(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateSidebarOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#sidebar-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('showSidebar', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('showSidebar'); | ||||
| 				ConfigUtil.setConfigItem('showSidebar', newValue); | ||||
| 				ipcRenderer.send('forward-message', 'toggle-sidebar', newValue); | ||||
| 				this.updateSidebarOption(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateStartAtLoginOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#startAtLogin-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('startAtLogin', false), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('startAtLogin'); | ||||
| 				ConfigUtil.setConfigItem('startAtLogin', newValue); | ||||
| 				ipcRenderer.send('toggleAutoLauncher', newValue); | ||||
| 				this.updateStartAtLoginOption(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	enableSpellchecker() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#enable-spellchecker-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('enableSpellchecker', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('enableSpellchecker'); | ||||
| 				ConfigUtil.setConfigItem('enableSpellchecker', newValue); | ||||
| 				this.enableSpellchecker(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	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()); | ||||
|  | ||||
| 		dialog.showMessageBox({ | ||||
| 			type: 'warning', | ||||
| 			buttons: ['YES', 'NO'], | ||||
| 			defaultId: 0, | ||||
| 			message: 'Are you sure', | ||||
| 			detail: clearAppDataMessage | ||||
| 		}, response => { | ||||
| 			if (response === 0) { | ||||
| 				fs.remove(getAppPath); | ||||
| 				setTimeout(() => ipcRenderer.send('forward-message', 'hard-reload'), 1000); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateResetDataOption() { | ||||
| 		const resetDataButton = document.querySelector('#resetdata-option .reset-data-button'); | ||||
| 		resetDataButton.addEventListener('click', () => { | ||||
| 			this.clearAppDataDialog(); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| module.exports = GeneralSection; | ||||
|   | ||||
| @@ -8,7 +8,7 @@ class PreferenceNav extends BaseComponent { | ||||
|  | ||||
| 		this.props = props; | ||||
|  | ||||
| 		this.navItems = ['General', 'Servers']; | ||||
| 		this.navItems = ['General', 'Network', 'Servers', 'Shortcuts']; | ||||
|  | ||||
| 		this.init(); | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										102
									
								
								app/renderer/js/pages/preference/network-section.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								app/renderer/js/pages/preference/network-section.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const {ipcRenderer} = require('electron'); | ||||
|  | ||||
| const BaseSection = require(__dirname + '/base-section.js'); | ||||
| const ConfigUtil = require(__dirname + '/../../utils/config-util.js'); | ||||
|  | ||||
| class NetworkSection extends BaseSection { | ||||
| 	constructor(props) { | ||||
| 		super(); | ||||
| 		this.props = props; | ||||
| 	} | ||||
|  | ||||
| 	template() { | ||||
| 		return ` | ||||
|             <div class="settings-pane"> | ||||
|                 <div class="title">Proxy</div> | ||||
|                 <div id="appearance-option-settings" class="settings-card"> | ||||
| 					<div class="setting-row" id="use-proxy-option"> | ||||
| 						<div class="setting-description">Connect servers through a proxy</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 					<div class="setting-block"> | ||||
| 						<div class="setting-row" id="proxy-pac-option"> | ||||
| 							<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">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">Proxy bypass rules</span> | ||||
| 							<input class="setting-input-value" placeholder="e.g. foobar.com"/> | ||||
| 						</div> | ||||
| 						<div class="setting-row"> | ||||
| 							<div class="action blue" id="proxy-save-action"> | ||||
| 								<i class="material-icons">check_box</i> | ||||
| 								<span>Save</span> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
|             </div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	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.getElementById('proxy-save-action'); | ||||
| 		this.$settingBlock = this.props.$root.querySelector('.setting-block'); | ||||
| 		this.initProxyOption(); | ||||
|  | ||||
| 		this.$proxyPAC.value = ConfigUtil.getConfigItem('proxyPAC', ''); | ||||
| 		this.$proxyRules.value = ConfigUtil.getConfigItem('proxyRules', ''); | ||||
| 		this.$proxyBypass.value = ConfigUtil.getConfigItem('proxyBypass', ''); | ||||
|  | ||||
| 		this.$proxySaveAction.addEventListener('click', () => { | ||||
| 			ConfigUtil.setConfigItem('proxyPAC', this.$proxyPAC.value); | ||||
| 			ConfigUtil.setConfigItem('proxyRules', this.$proxyRules.value); | ||||
| 			ConfigUtil.setConfigItem('proxyBypass', this.$proxyBypass.value); | ||||
|  | ||||
| 			ipcRenderer.send('forward-message', 'reload-proxy', true); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	initProxyOption() { | ||||
| 		const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false); | ||||
| 		this.toggleProxySettings(proxyEnabled); | ||||
| 		this.updateProxyOption(); | ||||
| 	} | ||||
|  | ||||
| 	toggleProxySettings(option) { | ||||
| 		if (option) { | ||||
| 			this.$settingBlock.classList.remove('hidden'); | ||||
| 		} else { | ||||
| 			this.$settingBlock.classList.add('hidden'); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	updateProxyOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#use-proxy-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('useProxy', false), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('useProxy'); | ||||
| 				ConfigUtil.setConfigItem('useProxy', newValue); | ||||
| 				this.toggleProxySettings(newValue); | ||||
| 				if (newValue === false) { | ||||
| 					// Reload proxy if the proxy is turned off | ||||
| 					ipcRenderer.send('forward-message', 'reload-proxy', false); | ||||
| 				} | ||||
| 				this.updateProxyOption(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = NetworkSection; | ||||
| @@ -11,15 +11,15 @@ class NewServerForm extends BaseComponent { | ||||
|  | ||||
| 	template() { | ||||
| 		return ` | ||||
| 			<div class="settings-card" style="border: solid 1px #4CAF50;"> | ||||
| 			<div class="settings-card"> | ||||
| 				<div class="server-info-right"> | ||||
| 					<div class="server-info-row"> | ||||
| 						<input class="server-info-url" autofocus placeholder="Enter the url of your Zulip server..."/> | ||||
| 						<input class="setting-input-value" autofocus placeholder="Enter the URL of your Zulip organization..."/> | ||||
| 					</div> | ||||
| 					<div class="server-info-row"> | ||||
| 						<div class="action green server-save-action"> | ||||
| 						<div class="action blue server-save-action"> | ||||
| 							<i class="material-icons">check_box</i> | ||||
| 							<span>Save</span> | ||||
| 							<span>Add</span> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| @@ -38,18 +38,29 @@ class NewServerForm extends BaseComponent { | ||||
| 		this.props.$root.innerHTML = ''; | ||||
| 		this.props.$root.appendChild(this.$newServerForm); | ||||
|  | ||||
| 		this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-url')[0]; | ||||
| 		this.$newServerUrl = this.$newServerForm.querySelectorAll('input.setting-input-value')[0]; | ||||
| 	} | ||||
|  | ||||
| 	submitFormHandler() { | ||||
| 		DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => { | ||||
| 			DomainUtil.addDomain(serverConf).then(() => { | ||||
| 				this.props.onChange(this.props.index); | ||||
| 			}); | ||||
| 		}, errorMessage => { | ||||
| 			alert(errorMessage); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	initActions() { | ||||
| 		this.$saveServerButton.addEventListener('click', () => { | ||||
| 			DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => { | ||||
| 				DomainUtil.addDomain(serverConf).then(() => { | ||||
| 					this.props.onChange(this.props.index); | ||||
| 				}); | ||||
| 			}, errorMessage => { | ||||
| 				alert(errorMessage); | ||||
| 			}); | ||||
| 			this.submitFormHandler(); | ||||
| 		}); | ||||
| 		this.$newServerUrl.addEventListener('keypress', event => { | ||||
| 			const EnterkeyCode = event.keyCode; | ||||
| 			// Submit form when Enter key is pressed | ||||
| 			if (EnterkeyCode === 13) { | ||||
| 				this.submitFormHandler(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,15 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const BaseComponent = require(__dirname + '/js/components/base.js'); | ||||
| const {ipcRenderer} = require('electron'); | ||||
| const { ipcRenderer } = require('electron'); | ||||
|  | ||||
| const ConfigUtil = require(__dirname + '/js/utils/config-util.js'); | ||||
|  | ||||
| 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 ShortcutsSection = require(__dirname + '/js/pages/preference/shortcuts-section.js'); | ||||
|  | ||||
| class PreferenceView extends BaseComponent { | ||||
| 	constructor() { | ||||
| @@ -23,6 +27,7 @@ class PreferenceView extends BaseComponent { | ||||
|  | ||||
| 		this.setDefaultView(); | ||||
| 		this.registerIpcs(); | ||||
| 		this.setDefaultSettings(); | ||||
| 	} | ||||
|  | ||||
| 	setDefaultView() { | ||||
| @@ -34,6 +39,30 @@ class PreferenceView extends BaseComponent { | ||||
| 		this.handleNavigation(nav); | ||||
| 	} | ||||
|  | ||||
| 	// 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 | ||||
| 	setDefaultSettings() { | ||||
| 		// Default settings which should be respected | ||||
| 		const settingOptions = { | ||||
| 			trayIcon: true, | ||||
| 			useProxy: false, | ||||
| 			showSidebar: true, | ||||
| 			badgeOption: true, | ||||
| 			startAtLogin: false, | ||||
| 			enableSpellchecker: true, | ||||
| 			showNotification: true, | ||||
| 			betaUpdate: false, | ||||
| 			silent: false | ||||
| 		}; | ||||
|  | ||||
| 		for (const i in settingOptions) { | ||||
| 			if (ConfigUtil.getConfigItem(i) === null) { | ||||
| 				ConfigUtil.setConfigItem(i, settingOptions[i]); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	handleNavigation(navItem) { | ||||
| 		this.nav.select(navItem); | ||||
| 		switch (navItem) { | ||||
| @@ -49,6 +78,18 @@ class PreferenceView extends BaseComponent { | ||||
| 				}); | ||||
| 				break; | ||||
| 			} | ||||
| 			case 'Network': { | ||||
| 				this.section = new NetworkSection({ | ||||
| 					$root: this.$settingsContainer | ||||
| 				}); | ||||
| 				break; | ||||
| 			} | ||||
| 			case 'Shortcuts': { | ||||
| 				this.section = new ShortcutsSection({ | ||||
| 					$root: this.$settingsContainer | ||||
| 				}); | ||||
| 				break; | ||||
| 			} | ||||
| 			default: break; | ||||
| 		} | ||||
| 		this.section.init(); | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class ServerInfoForm extends BaseComponent { | ||||
| 						<i class="material-icons open-tab-button">open_in_new</i>						 | ||||
| 					</div> | ||||
| 					<div class="server-info-row"> | ||||
| 						<input class="server-info-url" disabled value="${this.props.server.url}"/> | ||||
| 						<input class="setting-input-value" disabled value="${this.props.server.url}"/> | ||||
| 					</div> | ||||
| 					<div class="server-info-row"> | ||||
| 						<div class="action red server-delete-action"> | ||||
|   | ||||
| @@ -1,13 +1,12 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const {ipcRenderer} = require('electron'); | ||||
|  | ||||
| const BaseComponent = require(__dirname + '/../../components/base.js'); | ||||
| const BaseSection = require(__dirname + '/base-section.js'); | ||||
| const DomainUtil = require(__dirname + '/../../utils/domain-util.js'); | ||||
| const ServerInfoForm = require(__dirname + '/server-info-form.js'); | ||||
| const NewServerForm = require(__dirname + '/new-server-form.js'); | ||||
| const CreateOrganziation = require(__dirname + '/create-new-org.js'); | ||||
|  | ||||
| class ServersSection extends BaseComponent { | ||||
| class ServersSection extends BaseSection { | ||||
| 	constructor(props) { | ||||
| 		super(); | ||||
| 		this.props = props; | ||||
| @@ -16,23 +15,17 @@ class ServersSection extends BaseComponent { | ||||
| 	template() { | ||||
| 		return ` | ||||
| 			<div class="settings-pane" id="server-settings-pane"> | ||||
| 				<div class="title">Manage Servers</div> | ||||
| 				<div class="actions-container"> | ||||
| 					<div class="action green" id="new-server-action"> | ||||
| 						<i class="material-icons">add_box</i> | ||||
| 						<span>Add Server</span> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div id="new-server-container" class="hidden"></div> | ||||
| 				<div class="sub-title" id="existing-servers"></div> | ||||
| 				<div class="title">Add Server</div> | ||||
| 				<div id="new-server-container"></div> | ||||
| 				<div class="title" id="existing-servers"></div> | ||||
| 				<div id="server-info-container"></div> | ||||
| 				<div id="create-organization-container"></div> | ||||
| 			</div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	init() { | ||||
| 		this.initServers(); | ||||
| 		this.initActions(); | ||||
| 	} | ||||
|  | ||||
| 	initServers() { | ||||
| @@ -47,35 +40,34 @@ class ServersSection extends BaseComponent { | ||||
|  | ||||
| 		this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!'; | ||||
| 		// Show Existing servers if servers are there otherwise hide it | ||||
| 		this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing servers'; | ||||
| 		this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing Servers'; | ||||
| 		this.initNewServerForm(); | ||||
|  | ||||
| 		this.$createOrganizationContainer = document.getElementById('create-organization-container'); | ||||
| 		this.initCreateNewOrganization(); | ||||
|  | ||||
| 		for (let i = 0; i < servers.length; i++) { | ||||
| 			new ServerInfoForm({ | ||||
| 				$root: this.$serverInfoContainer, | ||||
| 				server: servers[i], | ||||
| 				index: i, | ||||
| 				onChange: this.handleServerInfoChange.bind(this) | ||||
| 				onChange: this.reloadApp | ||||
| 			}).init(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	initCreateNewOrganization() { | ||||
| 		new CreateOrganziation({ | ||||
| 			$root: this.$createOrganizationContainer | ||||
| 		}).init(); | ||||
| 	} | ||||
|  | ||||
| 	initNewServerForm() { | ||||
| 		new NewServerForm({ | ||||
| 			$root: this.$newServerContainer, | ||||
| 			onChange: this.handleServerInfoChange.bind(this) | ||||
| 			onChange: this.reloadApp | ||||
| 		}).init(); | ||||
| 	} | ||||
|  | ||||
| 	initActions() { | ||||
| 		this.$newServerContainer.classList.remove('hidden'); | ||||
| 		this.$newServerButton.classList.remove('green'); | ||||
| 		this.$newServerButton.classList.add('grey'); | ||||
| 	} | ||||
|  | ||||
| 	handleServerInfoChange() { | ||||
| 		ipcRenderer.send('forward-message', 'reload-viewer'); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = ServersSection; | ||||
|   | ||||
							
								
								
									
										311
									
								
								app/renderer/js/pages/preference/shortcuts-section.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								app/renderer/js/pages/preference/shortcuts-section.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,311 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const BaseSection = require(__dirname + '/base-section.js'); | ||||
|  | ||||
| class ShortcutsSection extends BaseSection { | ||||
| 	constructor(props) { | ||||
| 		super(); | ||||
| 		this.props = props; | ||||
| 	} | ||||
|  | ||||
| 	templateMac() { | ||||
| 		const userOSKey = '⌘'; | ||||
|  | ||||
| 		return ` | ||||
|             <div class="settings-pane"> | ||||
|               <div class="title">Application Shortcuts</div> | ||||
|               <div class="settings-card"> | ||||
|                 <table> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd><kbd>,</kbd></td> | ||||
|                     <td>Settings</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd><kbd>K</kbd></td> | ||||
|                     <td>Keyboard Shortcuts</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>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>${userOSKey}</kbd><kbd>F</kbd></td> | ||||
|                     <td>Find</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd><kbd>G</kbd></td> | ||||
|                     <td>Find Next</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>S</kbd></td> | ||||
|                     <td>Toggle Sidebar</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>I</kbd></td> | ||||
|                     <td>Toggle DevTools for Zulip App</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>U</kbd></td> | ||||
|                     <td>Toggle DevTools for Active Tab</td> | ||||
|                   </tr> | ||||
|                 </table> | ||||
|                 <div class="setting-control"></div> | ||||
|               </div> | ||||
|               <div class="title">History Shortcuts</div> | ||||
|               <div class="settings-card"> | ||||
|                 <table> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd><kbd>←</kbd></td> | ||||
|                     <td>Back</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd><kbd>→</kbd></td> | ||||
|                     <td>Forward</td> | ||||
|                   </tr> | ||||
|                 </table> | ||||
|                 <div class="setting-control"></div> | ||||
|               </div> | ||||
|               <div class="title">Window Shortcuts</div> | ||||
|               <div class="settings-card"> | ||||
|                 <table> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd><kbd>M</kbd></td> | ||||
|                     <td>Minimize</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd><kbd>W</kbd></td> | ||||
|                     <td>Close</td> | ||||
|                   </tr> | ||||
|                 </table> | ||||
|                 <div class="setting-control"></div> | ||||
|               </div> | ||||
|             </div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	templateWinLin() { | ||||
| 		const userOSKey = 'Ctrl'; | ||||
|  | ||||
| 		return ` | ||||
|             <div class="settings-pane"> | ||||
|               <div class="title">Application Shortcuts</div> | ||||
|               <div class="settings-card"> | ||||
|                 <table> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd> + <kbd>,</kbd></td> | ||||
|                     <td>Settings</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td> | ||||
|                     <td>Keyboard Shortcuts</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd> + <kbd>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>S</kbd></td> | ||||
|                     <td>Toggle Sidebar</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td> | ||||
|                     <td>Toggle DevTools for Zulip App</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td> | ||||
|                     <td>Toggle DevTools for Active Tab</td> | ||||
|                   </tr> | ||||
|                 </table> | ||||
|                 <div class="setting-control"></div> | ||||
|               </div> | ||||
|               <div class="title">History Shortcuts</div> | ||||
|               <div class="settings-card"> | ||||
|                 <table> | ||||
|                   <tr> | ||||
|                     <td><kbd>Alt</kbd> + <kbd>←</kbd></td> | ||||
|                     <td>Back</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>Alt</kbd> + <kbd>→</kbd></td> | ||||
|                     <td>Forward</td> | ||||
|                   </tr> | ||||
|                 </table> | ||||
|                 <div class="setting-control"></div> | ||||
|               </div> | ||||
|               <div class="title">Window Shortcuts</div> | ||||
|               <div class="settings-card"> | ||||
|                 <table> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd> + <kbd>M</kbd></td> | ||||
|                     <td>Minimize</td> | ||||
|                   </tr> | ||||
|                   <tr> | ||||
|                     <td><kbd>${userOSKey}</kbd> + <kbd>W</kbd></td> | ||||
|                     <td>Close</td> | ||||
|                   </tr> | ||||
|                 </table> | ||||
|                 <div class="setting-control"></div> | ||||
|               </div> | ||||
|             </div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	init() { | ||||
| 		this.props.$root.innerHTML = (process.platform === 'darwin') ? | ||||
| 			this.templateMac() : this.templateWinLin(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = ShortcutsSection; | ||||
| @@ -1,7 +1,10 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const {ipcRenderer} = require('electron'); | ||||
| const {spellChecker} = require('./spellchecker'); | ||||
| const { ipcRenderer } = require('electron'); | ||||
| const SetupSpellChecker = require('./spellchecker'); | ||||
|  | ||||
| const ConfigUtil = require(__dirname + '/utils/config-util.js'); | ||||
|  | ||||
| // eslint-disable-next-line import/no-unassigned-import | ||||
| require('./notification'); | ||||
|  | ||||
| @@ -32,11 +35,28 @@ process.once('loaded', () => { | ||||
|  | ||||
| // To prevent failing this script on linux we need to load it after the document loaded | ||||
| document.addEventListener('DOMContentLoaded', () => { | ||||
| 	// Init spellchecker | ||||
| 	spellChecker(); | ||||
| 	// 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 | ||||
| 	document.querySelector('.restart_get_events_button').addEventListener('click', () => { | ||||
| 		ipcRenderer.send('forward-message', 'reload-viewer'); | ||||
| 	}); | ||||
| 	const getRestartButton = document.querySelector('.restart_get_events_button'); | ||||
| 	if (getRestartButton) { | ||||
| 		getRestartButton.addEventListener('click', () => { | ||||
| 			ipcRenderer.send('forward-message', 'reload-viewer'); | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| // Clean up spellchecker events after you navigate away from this page; | ||||
| // otherwise, you may experience errors | ||||
| window.addEventListener('beforeunload', () => { | ||||
| 	SetupSpellChecker.unsubscribeSpellChecker(); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -1,29 +1,56 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker'); | ||||
| const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker'); | ||||
|  | ||||
| function spellChecker() { | ||||
| 	// Implement spellcheck using electron api | ||||
| 	window.spellCheckHandler = new SpellCheckHandler(); | ||||
| 	window.spellCheckHandler.attachToInput(); | ||||
| const ConfigUtil = require(__dirname + '/utils/config-util.js'); | ||||
|  | ||||
| 	// Start off as US English | ||||
| 	window.spellCheckHandler.switchLanguage('en-US'); | ||||
| class SetupSpellChecker { | ||||
| 	init() { | ||||
| 		if (ConfigUtil.getConfigItem('enableSpellchecker')) { | ||||
| 			this.enableSpellChecker(); | ||||
| 		} | ||||
| 		this.enableContextMenu(); | ||||
| 	} | ||||
|  | ||||
| 	const contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler); | ||||
| 	const contextMenuListener = new ContextMenuListener(info => { | ||||
| 		contextMenuBuilder.showPopupMenu(info); | ||||
| 	}); | ||||
| 	enableSpellChecker() { | ||||
| 		try { | ||||
| 			this.SpellCheckHandler = new SpellCheckHandler(); | ||||
| 		} catch (err) { | ||||
| 			console.log(err); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Clean up events after you navigate away from this page; | ||||
| 	// otherwise, you may experience errors | ||||
| 	window.addEventListener('beforeunload', () => { | ||||
| 	// eslint-disable-next-line no-undef | ||||
| 		spellCheckHandler.unsubscribe(); | ||||
| 		contextMenuListener.unsubscribe(); | ||||
| 	}); | ||||
| 	enableContextMenu() { | ||||
| 		if (this.SpellCheckHandler) { | ||||
| 			this.SpellCheckHandler.attachToInput(); | ||||
|  | ||||
| 			const userLanguage = ConfigUtil.getConfigItem('spellcheckerLanguage'); | ||||
|  | ||||
| 			// eslint-disable-next-line no-unused-expressions | ||||
| 			process.platform === 'darwin' ? | ||||
| 				// On macOS, spellchecker fails to auto-detect the lanugage user is typing in | ||||
| 				// that's why we need to mention it explicitly | ||||
| 				this.SpellCheckHandler.switchLanguage(userLanguage) : | ||||
| 				// On Linux and Windows, spellchecker can automatically detects the language the user is typing in | ||||
| 				// and silently switches on the fly; thus we can start off as US English | ||||
| 				this.SpellCheckHandler.switchLanguage('en-US'); | ||||
| 		} | ||||
|  | ||||
| 		const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler); | ||||
| 		this.contextMenuListener = new ContextMenuListener(info => { | ||||
| 			contextMenuBuilder.showPopupMenu(info); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	unsubscribeSpellChecker() { | ||||
| 		// eslint-disable-next-line no-undef | ||||
| 		if (this.SpellCheckHandler) { | ||||
| 			this.SpellCheckHandler.unsubscribe(); | ||||
| 		} | ||||
| 		if (this.contextMenuListener) { | ||||
| 			this.contextMenuListener.unsubscribe(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
| 	spellChecker | ||||
| }; | ||||
| module.exports = new SetupSpellChecker(); | ||||
|   | ||||
| @@ -180,11 +180,10 @@ ipcRenderer.on('tray', (event, arg) => { | ||||
| 	if (!window.tray) { | ||||
| 		return; | ||||
| 	} | ||||
| 	// We don't want to create tray from unread messages on windows and macOS since these systems already have dock badges and taskbar overlay icon. | ||||
| 	if (process.platform === 'linux') { | ||||
| 	// We don't want to create tray from unread messages on macOS since it already has dock badges. | ||||
| 	if (process.platform === 'linux' || process.platform === 'win32') { | ||||
| 		if (arg === 0) { | ||||
| 			unread = arg; | ||||
| 			// Message Count // console.log("message count is zero."); | ||||
| 			window.tray.setImage(iconPath()); | ||||
| 			window.tray.setToolTip('No unread messages'); | ||||
| 		} else { | ||||
| @@ -206,7 +205,7 @@ function toggleTray() { | ||||
| 		ConfigUtil.setConfigItem('trayIcon', false); | ||||
| 	} else { | ||||
| 		createTray(); | ||||
| 		if (process.platform === 'linux') { | ||||
| 		if (process.platform === 'linux' || process.platform === 'win32') { | ||||
| 			renderNativeImage(unread).then(image => { | ||||
| 				window.tray.setImage(image); | ||||
| 				window.tray.setToolTip(unread + ' unread messages'); | ||||
|   | ||||
| @@ -26,6 +26,7 @@ class ConfigUtil { | ||||
| 	} | ||||
|  | ||||
| 	getConfigItem(key, defaultValue = null) { | ||||
| 		this.reloadDB(); | ||||
| 		const value = this.db.getData('/')[key]; | ||||
| 		if (value === undefined) { | ||||
| 			this.setConfigItem(key, defaultValue); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const {app, dialog} = require('electron').remote; | ||||
| const { app, dialog } = require('electron').remote; | ||||
| const fs = require('fs'); | ||||
| const path = require('path'); | ||||
| const JsonDB = require('node-json-db'); | ||||
| @@ -45,6 +45,11 @@ class DomainUtil { | ||||
| 		return this.db.getData(`/domains[${index}]`); | ||||
| 	} | ||||
|  | ||||
| 	updateDomain(index, server) { | ||||
| 		this.reloadDB(); | ||||
| 		this.db.push(`/domains[${index}]`, server, true); | ||||
| 	} | ||||
|  | ||||
| 	addDomain(server) { | ||||
| 		return new Promise(resolve => { | ||||
| 			if (server.icon) { | ||||
| @@ -73,11 +78,26 @@ class DomainUtil { | ||||
| 		this.reloadDB(); | ||||
| 	} | ||||
|  | ||||
| 	checkDomain(domain) { | ||||
| 		const hasPrefix = (domain.indexOf('http') === 0); | ||||
| 		if (!hasPrefix) { | ||||
| 			domain = (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`; | ||||
| 	// Check if domain is already added | ||||
| 	duplicateDomain(domain) { | ||||
| 		domain = this.formatUrl(domain); | ||||
| 		const servers = this.getDomains(); | ||||
| 		for (const i in servers) { | ||||
| 			if (servers[i].url === domain) { | ||||
| 				return true; | ||||
| 			} | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	checkDomain(domain, silent = false) { | ||||
| 		if (!silent && this.duplicateDomain(domain)) { | ||||
| 			// Do not check duplicate in silent mode | ||||
| 			alert('This server has been added.'); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		domain = this.formatUrl(domain); | ||||
|  | ||||
| 		const checkDomain = domain + '/static/audio/zulip.ogg'; | ||||
|  | ||||
| @@ -90,8 +110,10 @@ class DomainUtil { | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			request(checkDomain, (error, response) => { | ||||
| 				const certsError = | ||||
| 					['Error: self signed certificate', | ||||
| 						'Error: unable to verify the first certificate' | ||||
| 					[ | ||||
| 						'Error: self signed certificate', | ||||
| 						'Error: unable to verify the first certificate', | ||||
| 						'Error: unable to get local issuer certificate' | ||||
| 					]; | ||||
| 				if (!error && response.statusCode !== 404) { | ||||
| 					// Correct | ||||
| @@ -101,24 +123,40 @@ class DomainUtil { | ||||
| 						resolve(serverConf); | ||||
| 					}); | ||||
| 				} else if (certsError.indexOf(error.toString()) >= 0) { | ||||
| 					dialog.showMessageBox({ | ||||
| 						type: 'question', | ||||
| 						buttons: ['Yes', 'No'], | ||||
| 						defaultId: 0, | ||||
| 						message: `Do you trust certificate from ${domain}? \n ${error}` | ||||
| 					}, response => { | ||||
| 						if (response === 0) { | ||||
| 							this.getServerSettings(domain).then(serverSettings => { | ||||
| 								resolve(serverSettings); | ||||
| 							}, () => { | ||||
| 								resolve(serverConf); | ||||
| 							}); | ||||
| 						} else { | ||||
| 							reject('Untrusted Certificate.'); | ||||
| 						} | ||||
| 					}); | ||||
| 					if (silent) { | ||||
| 						this.getServerSettings(domain).then(serverSettings => { | ||||
| 							resolve(serverSettings); | ||||
| 						}, () => { | ||||
| 							resolve(serverConf); | ||||
| 						}); | ||||
| 					} else { | ||||
| 						const certErrorMessage = `Do you trust certificate from ${domain}? \n ${error}`; | ||||
| 						const certErrorDetail = `The server 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. | ||||
| 						\n Unless you have a good reason to believe otherwise, you should not proceed. | ||||
| 						\n You can click here if you'd like to proceed with the connection.`; | ||||
|  | ||||
| 						dialog.showMessageBox({ | ||||
| 							type: 'warning', | ||||
| 							buttons: ['Yes', 'No'], | ||||
| 							defaultId: 0, | ||||
| 							message: certErrorMessage, | ||||
| 							detail: certErrorDetail | ||||
| 						}, response => { | ||||
| 							if (response === 0) { | ||||
| 								this.getServerSettings(domain).then(serverSettings => { | ||||
| 									resolve(serverSettings); | ||||
| 								}, () => { | ||||
| 									resolve(serverConf); | ||||
| 								}); | ||||
| 							} else { | ||||
| 								reject('Untrusted Certificate.'); | ||||
| 							} | ||||
| 						}); | ||||
| 					} | ||||
| 				} else { | ||||
| 					reject('Not a valid Zulip server'); | ||||
| 					const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \ | ||||
| 					(1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings`; | ||||
| 					reject(invalidZulipServerError); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| @@ -132,7 +170,9 @@ class DomainUtil { | ||||
| 					const data = JSON.parse(response.body); | ||||
| 					if (data.hasOwnProperty('realm_icon') && data.realm_icon) { | ||||
| 						resolve({ | ||||
| 							icon: data.realm_uri + data.realm_icon, | ||||
| 							// Some Zulip Servers use absolute URL for server icon whereas others use relative URL | ||||
| 							// Following check handles both the cases | ||||
| 							icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon, | ||||
| 							url: data.realm_uri, | ||||
| 							alias: data.realm_name | ||||
| 						}); | ||||
| @@ -146,14 +186,8 @@ class DomainUtil { | ||||
|  | ||||
| 	saveServerIcon(url) { | ||||
| 		// The save will always succeed. If url is invalid, downgrade to default icon. | ||||
| 		const dir = `${app.getPath('userData')}/server-icons`; | ||||
|  | ||||
| 		if (!fs.existsSync(dir)) { | ||||
| 			fs.mkdirSync(dir); | ||||
| 		} | ||||
|  | ||||
| 		return new Promise(resolve => { | ||||
| 			const filePath = `${dir}/${new Date().getMilliseconds()}${path.extname(url).split('?')[0]}`; | ||||
| 			const filePath = this.generateFilePath(url); | ||||
| 			const file = fs.createWriteStream(filePath); | ||||
| 			try { | ||||
| 				request(url).on('response', response => { | ||||
| @@ -175,9 +209,48 @@ class DomainUtil { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateSavedServer(url, index) { | ||||
| 		// Does not promise successful update | ||||
| 		this.checkDomain(url, true).then(newServerConf => { | ||||
| 			this.saveServerIcon(newServerConf.icon).then(localIconUrl => { | ||||
| 				newServerConf.icon = localIconUrl; | ||||
| 				this.updateDomain(index, newServerConf); | ||||
| 				this.reloadDB(); | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	reloadDB() { | ||||
| 		this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); | ||||
| 	} | ||||
|  | ||||
| 	generateFilePath(url) { | ||||
| 		const dir = `${app.getPath('userData')}/server-icons`; | ||||
| 		const extension = path.extname(url).split('?')[0]; | ||||
|  | ||||
| 		let hash = 5381; | ||||
| 		let len = url.length; | ||||
|  | ||||
| 		while (len) { | ||||
| 			hash = (hash * 33) ^ url.charCodeAt(--len); | ||||
| 		} | ||||
|  | ||||
| 		// Create 'server-icons' directory if not existed | ||||
| 		if (!fs.existsSync(dir)) { | ||||
| 			fs.mkdirSync(dir); | ||||
| 		} | ||||
|  | ||||
| 		return `${dir}/${hash >>> 0}${extension}`; | ||||
| 	} | ||||
|  | ||||
| 	formatUrl(domain) { | ||||
| 		const hasPrefix = (domain.indexOf('http') === 0); | ||||
| 		if (hasPrefix) { | ||||
| 			return domain; | ||||
| 		} else { | ||||
| 			return (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = new DomainUtil(); | ||||
|   | ||||
| @@ -13,13 +13,14 @@ | ||||
|     <div class="popup"> | ||||
|       <span class="popuptext hidden" id="fullscreen-popup"></span> | ||||
|   </div> | ||||
|   <div id="sidebar"> | ||||
|   <div id="sidebar" class="toggle-sidebar"> | ||||
|     <div id="view-controls-container"> | ||||
|       <div id="tabs-container"></div> | ||||
|       <div id="add-tab" class="tab functional-tab"> | ||||
|         <div class="server-tab" id="add-action"> | ||||
|           <i class="material-icons">add</i> | ||||
|         </div> | ||||
|         <span id="add-server-tooltip" style="display:none">Add organization</span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div id="actions-container"> | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@2x.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@3x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@3x.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@4x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@4x.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.8 KiB | 
| @@ -1 +1,58 @@ | ||||
| ** Windows Set up instructions ** | ||||
|  | ||||
| ## Prerequisites | ||||
|  | ||||
| * [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git) | ||||
| * [Node.js](https://nodejs.org) >= v6.9.0 | ||||
| * [python](https://www.python.org/downloads/release/python-2713/) (v2.7.x recommended) | ||||
| * [node-gyp](https://github.com/nodejs/node-gyp#installation) (installed via powershell) | ||||
|  | ||||
| ## System specific dependencies | ||||
|  | ||||
| * use only 32bit or 64bit for all of the installers, do not mix architectures | ||||
| * install using default settings | ||||
| * open Windows Powershell as Admin | ||||
| ```powershell | ||||
| C:\Windows\system32> npm install --global --production windows-build-tools | ||||
| ``` | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| Clone the source locally: | ||||
|  | ||||
| ```sh | ||||
| $ git clone https://github.com/zulip/zulip-electron | ||||
| $ cd zulip-electron | ||||
| ``` | ||||
|  | ||||
| Install project dependencies: | ||||
|  | ||||
| ```sh | ||||
| $ npm install | ||||
| ``` | ||||
|  | ||||
| Start the app: | ||||
|  | ||||
| ```sh | ||||
| $ npm start | ||||
| ``` | ||||
|  | ||||
| Start and watch changes | ||||
|  | ||||
| ```sh | ||||
| $ npm run dev | ||||
| ``` | ||||
| ### Making a release | ||||
|  | ||||
| To package app into an installer use command: | ||||
| ``` | ||||
| npm run pack | ||||
| npm run dist | ||||
| ``` | ||||
| It will start the packaging process. The ready for distribution file (e.g. dmg, windows installer, deb package) will be outputted to the `dist` directory. | ||||
|  | ||||
| # Troubleshooting | ||||
| If you have any problems running the app please see the [most common issues](./troubleshooting.md). | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										105
									
								
								help.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								help.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| # User Guide | ||||
|  | ||||
| > Welcome! This guide will walk you through the basics of using Zulip Desktop. | ||||
|  | ||||
|  | ||||
| ## Get Zulip Desktop | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Connect to a Server | ||||
|  | ||||
| ### Connect through a proxy | ||||
|  | ||||
| It's possible to connect to your server through a proxy.  | ||||
| You can enter the proxy settings in the `Network` section of App Settings.  | ||||
| There are three fields provided: | ||||
| * `PAC script` - The URL associated with the PAC file. | ||||
| * `Proxy rules` - Rules indicating which proxies to use. | ||||
| * `Proxy bypass rules` - Rules indicating which URLs should | ||||
|     bypass the proxy settings. | ||||
| 	 | ||||
| For a typical setup where internet access is required to use an HTTP proxy, | ||||
| but URLs on the local network should be accessed directly, configure as follows: | ||||
|  | ||||
| `Proxy rules = proxy.example.com` | ||||
|  | ||||
| Your HTTP proxy server | ||||
| `Proxy bypass rules = *.example.com;10.0.0.0/8` | ||||
|  | ||||
| Directly connect to your own domain and private IP subnet | ||||
| for more complex setups, read below to configure complex proxy rules and proxy bypass rules. | ||||
|  | ||||
| ### Sets the proxy settings. | ||||
|  | ||||
| When `PAC script` and `Proxy rules` are provided together, the `Proxy rules` | ||||
| option is ignored and `PAC script` configuration is applied. | ||||
|  | ||||
| The `Proxy rules` has to follow the rules below: | ||||
|  | ||||
| ``` | ||||
| proxyRules = schemeProxies[";"<schemeProxies>] | ||||
| schemeProxies = [<urlScheme>"="]<proxyURIList> | ||||
| urlScheme = "http" | "https" | "ftp" | "socks" | ||||
| proxyURIList = <proxyURL>[","<proxyURIList>] | ||||
| proxyURL = [<proxyScheme>"://"]<proxyHost>[":"<proxyPort>] | ||||
| ``` | ||||
|  | ||||
| For example: | ||||
|  | ||||
| * `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and | ||||
|   HTTP proxy `foopy2:80` for `ftp://` URLs. | ||||
| * `foopy:80` - Use HTTP proxy `foopy:80` for all URLs. | ||||
| * `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing | ||||
|   over to `bar` if `foopy:80` is unavailable, and after that using no proxy. | ||||
| * `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs. | ||||
| * `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail | ||||
|   over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable. | ||||
| * `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no | ||||
|   proxy if `foopy` is unavailable. | ||||
| * `http=foopy;socks=foopy2` -  Use HTTP proxy `foopy` for http URLs, and use | ||||
|   `socks4://foopy2` for all other URLs. | ||||
|  | ||||
| The `Proxy bypass rules` is a comma separated list of rules described below: | ||||
|  | ||||
| * `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]` | ||||
|  | ||||
|    Match all hostnames that match the pattern HOSTNAME_PATTERN. | ||||
|  | ||||
|    Examples: | ||||
|      "foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99", | ||||
|      "https://x.*.y.com:99" | ||||
|  | ||||
|  * `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]` | ||||
|  | ||||
|    Match a particular domain suffix. | ||||
|  | ||||
|    Examples: | ||||
|      ".google.com", ".com", "http://.google.com" | ||||
|  | ||||
| * `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]` | ||||
|  | ||||
|    Match URLs which are IP address literals. | ||||
|  | ||||
|    Examples: | ||||
|      "127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99" | ||||
|  | ||||
| *  `IP_LITERAL "/" PREFIX_LENGHT_IN_BITS` | ||||
|  | ||||
|    Match any URL that is to an IP literal that falls between the | ||||
|    given range. IP range is specified using CIDR notation. | ||||
|  | ||||
|    Examples: | ||||
|      "192.168.1.1/16", "fefe:13::abc/33". | ||||
|  | ||||
| *  `<local>` | ||||
|  | ||||
|    Match local addresses. The meaning of `<local>` is whether the | ||||
|    host matches one of: "127.0.0.1", "::1", "localhost". | ||||
|  | ||||
|  | ||||
| ## Change App Preferences | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Reporting an Issue | ||||
							
								
								
									
										68
									
								
								how-to-install.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								how-to-install.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| # How to install | ||||
|  | ||||
| **Note:** If you download from the [releases page](https://github.com/zulip/zulip-electron/releases), be careful what version you pick. Releases that end with `-beta` are beta releases and the rest are stable. | ||||
| - **beta:** these releases are the right balance between getting new features early while staying away from nasty bugs. | ||||
| - **stable:** these releases are more thoroughly tested; they receive new features later, but there's a lower chance that things will go wrong. | ||||
|  | ||||
| [LR]: https://github.com/zulip/zulip-electron/releases | ||||
|  | ||||
| ## OS X | ||||
|  | ||||
| **DMG or zip**: | ||||
|  | ||||
| 1. Download [Zulip-x.x.x.dmg][LR] or [Zulip-x.x.x-mac.zip][LR] | ||||
| 2. Open or unzip the file and drag the app into the `Applications` folder | ||||
| 3. Done! The app will update automatically | ||||
|  | ||||
| **Using brew**: | ||||
|  | ||||
| 1. Run `brew cask install zulip` in your terminal | ||||
| 2. The app will be installed in your `Applications` | ||||
| 3. Done! The app will update automatically (you can also use `brew update && brew upgrade zulip`) | ||||
|  | ||||
| ## Windows | ||||
|  | ||||
| **Installer (recommended)**: | ||||
|  | ||||
| 1. Download [Zulip-Web-Setup-x.x.x.exe][LR] | ||||
| 2. Run the installer, wait until it finishes | ||||
| 3. Done! The app will update automatically | ||||
|  | ||||
| **Portable**: | ||||
|  | ||||
| 1. Download [zulip-x.x.x-arch.nsis.7z][LR]  [*here arch = ia32 (32-bit), x64 (64-bit)*] | ||||
| 2. Extract the zip wherever you want (e.g. a flash drive) and run the app from there | ||||
|  | ||||
| ## Linux | ||||
|  | ||||
| **Ubuntu, Debian 8+ (deb package)**: | ||||
|  | ||||
| 1. Download [Zulip-x.x.x-amd64.deb][LR] | ||||
| 2. Double click and install, or run `dpkg -i Zulip-x.x.x-amd64.deb` in the terminal | ||||
| 3. Start the app with your app launcher or by running `zulip` in a terminal | ||||
| 4. Done! The app will NOT update automatically, but you can still check for updates | ||||
|  | ||||
| **Other distros (Fedora, CentOS, Arch Linux etc)** : | ||||
| 1. Download Zulip-x.x.x-x86_64.AppImage[LR] | ||||
| 2. Make it executable using chmod a+x Zulip-x.x.x-x86_64.AppImage | ||||
| 3. Start the app with your app launcher | ||||
|  | ||||
| **You can also use `apt-get` (recommended)**: | ||||
|  | ||||
| * First download our signing key to make sure the deb you download is correct: | ||||
|  | ||||
| ``` | ||||
| sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9 | ||||
| ``` | ||||
|  | ||||
| * Add the repo to your apt source list : | ||||
| ``` | ||||
| echo "deb https://dl.bintray.com/zulip/debian/ beta main" | | ||||
|   sudo tee -a /etc/apt/sources.list.d/zulip.list | ||||
| ``` | ||||
|  | ||||
| * Now install the client : | ||||
| ``` | ||||
| sudo apt-get update | ||||
| sudo apt-get install zulip | ||||
| ``` | ||||
							
								
								
									
										30
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "zulip", | ||||
|   "productName": "Zulip", | ||||
|   "version": "1.3.0-beta", | ||||
|   "version": "1.6.0-beta", | ||||
|   "main": "./app/main", | ||||
|   "description": "Zulip Desktop App", | ||||
|   "license": "Apache-2.0", | ||||
| @@ -20,15 +20,18 @@ | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "electron app --disable-http-cache", | ||||
|     "reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install", | ||||
|     "postinstall": "electron-builder install-app-deps", | ||||
|     "test": "xo", | ||||
|     "dev": "gulp dev", | ||||
|     "pack": "electron-builder --dir", | ||||
|     "dist": "electron-builder", | ||||
|     "mas": "electron-builder --mac mas", | ||||
|     "build:win": "electron-builder --win nsis-web --ia32 --x64", | ||||
|     "travis": "cd ./scripts && ./travis-build-test.sh" | ||||
|   }, | ||||
|   "pre-commit": [ | ||||
|     "test" | ||||
|   ], | ||||
|   "build": { | ||||
|     "appId": "org.zulip.zulip-electron", | ||||
|     "asar": true, | ||||
| @@ -42,7 +45,7 @@ | ||||
|       "category": "public.app-category.productivity" | ||||
|     }, | ||||
|     "linux": { | ||||
|       "category": "", | ||||
|       "category": "Chat;GNOME;GTK;Network;InstantMessaging", | ||||
|       "packageCategory": "GNOME;GTK;Network;InstantMessaging", | ||||
|       "description": "Zulip Desktop Client for Linux", | ||||
|       "target": [ | ||||
| @@ -74,7 +77,15 @@ | ||||
|       ] | ||||
|     }, | ||||
|     "win": { | ||||
|       "target": "nsis", | ||||
|       "target": [ | ||||
|         { | ||||
|           "target": "nsis-web", | ||||
|           "arch": [ | ||||
|             "x64", | ||||
|             "ia32" | ||||
|           ] | ||||
|         } | ||||
|       ], | ||||
|       "icon": "build/icon.ico", | ||||
|       "publisherName": "Kandra Labs, Inc." | ||||
|     }, | ||||
| @@ -95,15 +106,17 @@ | ||||
|   "devDependencies": { | ||||
|     "assert": "1.4.1", | ||||
|     "devtron": "1.4.0", | ||||
|     "electron-builder": "19.19.1", | ||||
|     "electron": "1.6.11", | ||||
|     "electron-builder": "19.45.5", | ||||
|     "electron": "1.6.14", | ||||
|     "electron-connect": "0.6.2", | ||||
|     "gulp": "3.9.1", | ||||
|     "gulp-mocha": "4.3.1", | ||||
|     "chai-as-promised": "7.1.1", | ||||
|     "chai": "4.1.1", | ||||
|     "spectron": "3.7.2", | ||||
|     "xo": "0.18.2" | ||||
|     "xo": "0.18.2", | ||||
|     "pre-commit": "1.2.2", | ||||
|     "electron-debug": "1.4.0" | ||||
|   }, | ||||
|   "xo": { | ||||
|     "parserOptions": { | ||||
| @@ -122,6 +135,7 @@ | ||||
|             500 | ||||
|           ], | ||||
|           "no-warning-comments": 0, | ||||
|           "object-curly-spacing": 0, | ||||
|           "capitalized-comments": 0, | ||||
|           "no-else-return": 0, | ||||
|           "no-path-concat": 0, | ||||
| @@ -143,4 +157,4 @@ | ||||
|       "mocha" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user