mirror of
				https://github.com/zulip/zulip-desktop.git
				synced 2025-10-31 03:53:34 +00:00 
			
		
		
		
	Compare commits
	
		
			101 Commits
		
	
	
		
			v1.6.0-bet
			...
			v1.8.2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | d673d5b76c | ||
|  | 2bf88aa912 | ||
|  | c9f179a2ae | ||
|  | c11089027c | ||
|  | 5c45ab7b66 | ||
|  | 6205ca0aca | ||
|  | b83e2dd428 | ||
|  | fd421a62d2 | ||
|  | fa85241c79 | ||
|  | b163c237b6 | ||
|  | 5351ee10fa | ||
|  | d48b6ae80d | ||
|  | 2d07d40c92 | ||
|  | 55ae71c00c | ||
|  | 0c18bb5226 | ||
|  | 6d213c86a8 | ||
|  | 0c1cd96ed7 | ||
|  | d77b1b5960 | ||
|  | 2c6c0c8c8d | ||
|  | 84bf59d32f | ||
|  | 256c352fb9 | ||
|  | da28589c17 | ||
|  | c452ee2ef6 | ||
|  | 3582aa4694 | ||
|  | 7f7eee2455 | ||
|  | c716b8f233 | ||
|  | 4a40c75127 | ||
|  | 239631a2b6 | ||
|  | 89d1344e2f | ||
|  | 1948ba2cc3 | ||
|  | b8da7dd6ee | ||
|  | 4a0efb7301 | ||
|  | aedd95259d | ||
|  | c8d7a79877 | ||
|  | 6e6db42b54 | ||
|  | db79284fbb | ||
|  | 2434f06655 | ||
|  | 1d611d3382 | ||
|  | a746194e9e | ||
|  | 7cc13f7a26 | ||
|  | 6a9bb152a0 | ||
|  | 8b6dcd355f | ||
|  | 91742a5770 | ||
|  | fb74251a2c | ||
|  | a920720f91 | ||
|  | aa8e99b7a6 | ||
|  | e23f8aaa58 | ||
|  | 5c3208d44c | ||
|  | c0b57bbe2b | ||
|  | afe4e8901b | ||
|  | 231e7fd9c2 | ||
|  | a0d898a5b7 | ||
|  | 1abf62555c | ||
|  | 6befcbaa8f | ||
|  | e56a01049b | ||
|  | 72cb8459ff | ||
|  | 0b83b22206 | ||
|  | 267d25e5c4 | ||
|  | 8401f8f5ce | ||
|  | c4a7264f34 | ||
|  | 9d081ecd5a | ||
|  | dc6582fa82 | ||
|  | 3b412672c6 | ||
|  | 04083bfa81 | ||
|  | 562e82d2f1 | ||
|  | 3b014e0715 | ||
|  | 13178ebc8f | ||
|  | 08693bf105 | ||
|  | d7a0b63d62 | ||
|  | a193ecf229 | ||
|  | 31f04754a4 | ||
|  | ae7374475f | ||
|  | 7697d5d698 | ||
|  | d3e1b5de45 | ||
|  | 9efa6191f7 | ||
|  | d86797d2fc | ||
|  | 23eef7edb0 | ||
|  | a1d5a35ccf | ||
|  | 62e8dfe180 | ||
|  | 8322054984 | ||
|  | 13ae6f07e9 | ||
|  | dab92be54c | ||
|  | fe9c66d8c2 | ||
|  | 43b4d511dc | ||
|  | aa5a47ad53 | ||
|  | ce27f92900 | ||
|  | 7be051bb6e | ||
|  | c2a01adabe | ||
|  | 770926e6eb | ||
|  | 10ef627f59 | ||
|  | 4adba0f4b4 | ||
|  | ed590c26e3 | ||
|  | 6d10291a87 | ||
|  | 3fe3a3da85 | ||
|  | ba64438a99 | ||
|  | bcc27894c4 | ||
|  | 0dd0f593d1 | ||
|  | f5e9342f78 | ||
|  | 083ccdf229 | ||
|  | 1261786db2 | ||
|  | 7f567f55c3 | 
							
								
								
									
										8
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | --- | ||||||
|  | <!-- Please Include: --> | ||||||
|  | - **Operating System**: | ||||||
|  |   - [ ] Windows | ||||||
|  |   - [ ] Linux/Ubutnu | ||||||
|  |   - [ ] macOS | ||||||
|  | - **Clear steps to reproduce the issue**: | ||||||
|  | - **Relevant error messages and/or screenshots**: | ||||||
							
								
								
									
										16
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | --- | ||||||
|  | <!-- | ||||||
|  | Remove the fields that are not appropriate | ||||||
|  | Please include: | ||||||
|  | --> | ||||||
|  |  | ||||||
|  | **What's this PR do?** | ||||||
|  |  | ||||||
|  | **Any background context you want to provide?** | ||||||
|  |  | ||||||
|  | **Screenshots?** | ||||||
|  |  | ||||||
|  | **You have tested this PR on:** | ||||||
|  |   - [ ] Windows | ||||||
|  |   - [ ] Linux/Ubuntu | ||||||
|  |   - [ ] macOS | ||||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -24,5 +24,8 @@ yarn-error.log* | |||||||
| # miscellaneous | # miscellaneous | ||||||
| .idea | .idea | ||||||
| config.gypi | config.gypi | ||||||
| .python-version |  | ||||||
|  |  | ||||||
|  | # Test generated files | ||||||
|  | tests/package.json | ||||||
|  |  | ||||||
|  | .python-version | ||||||
|   | |||||||
| @@ -18,8 +18,9 @@ node_js: | |||||||
| - '6' | - '6' | ||||||
|  |  | ||||||
| before_install: | before_install: | ||||||
| - npm install -g gulp |   - ./scripts/travis-xvfb.sh | ||||||
| - npm install |   - npm install -g gulp | ||||||
|  |   - npm install | ||||||
|  |  | ||||||
| cache: | cache: | ||||||
|   directories: |   directories: | ||||||
| @@ -33,4 +34,4 @@ notifications: | |||||||
|     urls: |     urls: | ||||||
|       - https://zulip.org/zulipbot/travis |       - https://zulip.org/zulipbot/travis | ||||||
|     on_success: always |     on_success: always | ||||||
|     on_failure: always |     on_failure: always | ||||||
|   | |||||||
| @@ -1,7 +0,0 @@ | |||||||
| --- |  | ||||||
| Please include:  |  | ||||||
| - `Operating System` |  | ||||||
| - `Clear steps to reproduce the issue` |  | ||||||
| - `Relevant error messages and/or screenshots` |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @@ -12,19 +12,12 @@ Please see [installation guide](https://zulipchat.com/help/desktop-app-install-g | |||||||
|  |  | ||||||
| # Features | # Features | ||||||
| * Sign in to multiple teams | * Sign in to multiple teams | ||||||
| * Native desktop Notifications | * Desktop Notifications with inline reply support | ||||||
| * SpellChecker | * Multilanguage SpellChecker | ||||||
| * OSX/Win/Linux installers | * OSX/Win/Linux installers | ||||||
| * Automatic Updates (macOS/Windows) | * Automatic Updates (macOS/Windows/Linux) | ||||||
| * Keyboard shortcuts | * Keyboard shortcuts | ||||||
|  |  | ||||||
| Description            | Keys |  | ||||||
| -----------------------| ----------------------- |  | ||||||
| Default shortcuts      | <kbd>Cmd/Ctrl</kbd> <kbd>k</kbd> |  | ||||||
| Manage Zulip Servers    | <kbd>Cmd/Ctrl</kbd> <kbd>,</kbd> |  | ||||||
| Back                   | <kbd>Cmd/Ctrl</kbd> <kbd>[</kbd> |  | ||||||
| Forward                | <kbd>Cmd/Ctrl</kbd> <kbd>]</kbd> |  | ||||||
|  |  | ||||||
| # Development | # Development | ||||||
| Please see our [development guide](./development.md) to get started and run app locally. | Please see our [development guide](./development.md) to get started and run app locally. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| const fs = require('fs'); |  | ||||||
| const { app, dialog } = require('electron'); | const { app, dialog } = require('electron'); | ||||||
| const { autoUpdater } = require('electron-updater'); | const { autoUpdater } = require('electron-updater'); | ||||||
| const isDev = require('electron-is-dev'); | const isDev = require('electron-is-dev'); | ||||||
| @@ -12,13 +11,15 @@ function appUpdater() { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if (process.platform === 'linux' && !process.env.APPIMAGE) { | ||||||
|  | 		const { linuxUpdateNotification } = require('./linuxupdater'); | ||||||
|  | 		linuxUpdateNotification(); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Create Logs directory | 	// Create Logs directory | ||||||
| 	const LogsDir = `${app.getPath('userData')}/Logs`; | 	const LogsDir = `${app.getPath('userData')}/Logs`; | ||||||
|  |  | ||||||
| 	if (!fs.existsSync(LogsDir)) { |  | ||||||
| 		fs.mkdirSync(LogsDir); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Log whats happening | 	// Log whats happening | ||||||
| 	const log = require('electron-log'); | 	const log = require('electron-log'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ const { setAutoLaunch } = require('./startup'); | |||||||
| const { app, ipcMain } = electron; | const { app, ipcMain } = electron; | ||||||
|  |  | ||||||
| const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js'); | const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js'); | ||||||
|  | const ConfigUtil = require('./../renderer/js/utils/config-util.js'); | ||||||
|  |  | ||||||
| // Adds debug features like hotkeys for triggering dev tools and reload | // Adds debug features like hotkeys for triggering dev tools and reload | ||||||
| // in development mode | // in development mode | ||||||
| @@ -66,8 +67,8 @@ function createMainWindow() { | |||||||
| 		y: mainWindowState.y, | 		y: mainWindowState.y, | ||||||
| 		width: mainWindowState.width, | 		width: mainWindowState.width, | ||||||
| 		height: mainWindowState.height, | 		height: mainWindowState.height, | ||||||
| 		minWidth: 600, | 		minWidth: 300, | ||||||
| 		minHeight: 500, | 		minHeight: 400, | ||||||
| 		webPreferences: { | 		webPreferences: { | ||||||
| 			plugins: true, | 			plugins: true, | ||||||
| 			allowDisplayingInsecureContent: true, | 			allowDisplayingInsecureContent: true, | ||||||
| @@ -81,7 +82,11 @@ function createMainWindow() { | |||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	win.once('ready-to-show', () => { | 	win.once('ready-to-show', () => { | ||||||
| 		win.show(); | 		if (ConfigUtil.getConfigItem('startMinimized')) { | ||||||
|  | 			win.minimize(); | ||||||
|  | 		} else { | ||||||
|  | 			win.show(); | ||||||
|  | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	win.loadURL(mainURL); | 	win.loadURL(mainURL); | ||||||
| @@ -124,6 +129,9 @@ function createMainWindow() { | |||||||
| 	return win; | 	return win; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Decrease load on GPU (experimental) | ||||||
|  | app.disableHardwareAcceleration(); | ||||||
|  |  | ||||||
| // eslint-disable-next-line max-params | // eslint-disable-next-line max-params | ||||||
| app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { | app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { | ||||||
| 	event.preventDefault(); | 	event.preventDefault(); | ||||||
| @@ -145,7 +153,11 @@ app.on('ready', () => { | |||||||
| 	const page = mainWindow.webContents; | 	const page = mainWindow.webContents; | ||||||
|  |  | ||||||
| 	page.on('dom-ready', () => { | 	page.on('dom-ready', () => { | ||||||
| 		mainWindow.show(); | 		if (ConfigUtil.getConfigItem('startMinimized')) { | ||||||
|  | 			mainWindow.minimize(); | ||||||
|  | 		} else { | ||||||
|  | 			mainWindow.show(); | ||||||
|  | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	page.once('did-frame-finish-load', () => { | 	page.once('did-frame-finish-load', () => { | ||||||
| @@ -154,9 +166,11 @@ app.on('ready', () => { | |||||||
| 		crashHandler(); | 		crashHandler(); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	electron.powerMonitor.on('resume', () => { | 	// Temporarily remove this event | ||||||
| 		page.send('reload-viewer'); | 	// electron.powerMonitor.on('resume', () => { | ||||||
| 	}); | 	// 	mainWindow.reload(); | ||||||
|  | 	// 	page.send('destroytray'); | ||||||
|  | 	// }); | ||||||
|  |  | ||||||
| 	ipcMain.on('focus-app', () => { | 	ipcMain.on('focus-app', () => { | ||||||
| 		mainWindow.show(); | 		mainWindow.show(); | ||||||
| @@ -208,11 +222,6 @@ app.on('ready', () => { | |||||||
| 		appMenu.setMenu(props); | 		appMenu.setMenu(props); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	ipcMain.on('register-server-tab-shortcut', (event, index) => { |  | ||||||
| 		// Array index == Shown index - 1 |  | ||||||
| 		page.send('switch-server-tab', index - 1); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => { | 	ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => { | ||||||
| 		setAutoLaunch(AutoLaunchValue); | 		setAutoLaunch(AutoLaunchValue); | ||||||
| 	}); | 	}); | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								app/main/linuxupdater.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/main/linuxupdater.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | const { app } = require('electron'); | ||||||
|  | const { Notification } = require('electron'); | ||||||
|  |  | ||||||
|  | const request = require('request'); | ||||||
|  | const semver = require('semver'); | ||||||
|  | const ConfigUtil = require('../renderer/js/utils/config-util'); | ||||||
|  | const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util'); | ||||||
|  |  | ||||||
|  | function linuxUpdateNotification() { | ||||||
|  | 	let	url = 'https://api.github.com/repos/zulip/zulip-electron/releases'; | ||||||
|  | 	url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest'; | ||||||
|  |  | ||||||
|  | 	const options = { | ||||||
|  | 		url, | ||||||
|  | 		headers: {'User-Agent': 'request'} | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	request(options, (error, response, body) => { | ||||||
|  | 		if (error) { | ||||||
|  | 			console.log('Error:', error); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		if (response.statusCode < 400) { | ||||||
|  | 			const data = JSON.parse(body); | ||||||
|  | 			const latestVersion = ConfigUtil.getConfigItem('betaUpdate') ? data[0].tag_name : data.tag_name; | ||||||
|  |  | ||||||
|  | 			if (semver.gt(latestVersion, app.getVersion())) { | ||||||
|  | 				const notified = LinuxUpdateUtil.getUpdateItem(latestVersion); | ||||||
|  | 				if (notified === null) { | ||||||
|  | 					new Notification({title: 'Zulip Update', body: 'A new version ' + latestVersion + ' is available. Please update using your package manager.'}).show(); | ||||||
|  | 					LinuxUpdateUtil.setUpdateItem(latestVersion, true); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			console.log('Status:', response.statusCode); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  | 	linuxUpdateNotification | ||||||
|  | }; | ||||||
| @@ -54,7 +54,7 @@ class AppMenu { | |||||||
| 			role: 'togglefullscreen' | 			role: 'togglefullscreen' | ||||||
| 		}, { | 		}, { | ||||||
| 			label: 'Zoom In', | 			label: 'Zoom In', | ||||||
| 			accelerator: 'CommandOrControl+=', | 			accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=', | ||||||
| 			click(item, focusedWindow) { | 			click(item, focusedWindow) { | ||||||
| 				if (focusedWindow) { | 				if (focusedWindow) { | ||||||
| 					AppMenu.sendAction('zoomIn'); | 					AppMenu.sendAction('zoomIn'); | ||||||
| @@ -115,26 +115,33 @@ class AppMenu { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	getHelpSubmenu() { | 	getHelpSubmenu() { | ||||||
| 		return [{ | 		return [ | ||||||
| 			label: `${appName} Website`, | 			{ | ||||||
| 			click() { | 				label: `${appName + ' Desktop-'} v${app.getVersion()}`, | ||||||
| 				shell.openExternal('https://zulipchat.com/help/'); | 				enabled: false | ||||||
| 			} | 			}, | ||||||
| 		}, { | 			{ | ||||||
| 			label: `${appName + 'Desktop'} - ${app.getVersion()}`, | 				label: `${appName} Help`, | ||||||
| 			enabled: false | 				click() { | ||||||
| 		}, { | 					shell.openExternal('https://zulipchat.com/help/'); | ||||||
| 			label: 'Report an Issue...', | 				} | ||||||
| 			click() { | 			}, { | ||||||
| 				const body = ` | 				label: 'Show App Logs', | ||||||
|  | 				click() { | ||||||
|  | 					shell.openItem(app.getPath('userData')); | ||||||
|  | 				} | ||||||
|  | 			}, { | ||||||
|  | 				label: 'Report an Issue...', | ||||||
|  | 				click() { | ||||||
|  | 					const body = ` | ||||||
| 					<!-- Please succinctly describe your issue and steps to reproduce it. --> | 					<!-- Please succinctly describe your issue and steps to reproduce it. --> | ||||||
| 					- | 					- | ||||||
| 					${app.getName()} ${app.getVersion()} | 					${app.getName()} ${app.getVersion()} | ||||||
| 					Electron ${process.versions.electron} | 					Electron ${process.versions.electron} | ||||||
| 					${process.platform} ${process.arch} ${os.release()}`; | 					${process.platform} ${process.arch} ${os.release()}`; | ||||||
| 				shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`); | 					shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`); | ||||||
| 			} | 				} | ||||||
| 		}]; | 			}]; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	getWindowSubmenu(tabs, activeTabIndex) { | 	getWindowSubmenu(tabs, activeTabIndex) { | ||||||
| @@ -150,6 +157,11 @@ class AppMenu { | |||||||
| 				type: 'separator' | 				type: 'separator' | ||||||
| 			}); | 			}); | ||||||
| 			for (let i = 0; i < tabs.length; i++) { | 			for (let i = 0; i < tabs.length; i++) { | ||||||
|  | 				// Do not add functional tab settings to list of windows in menu bar | ||||||
|  | 				if (tabs[i].props.role === 'function' && tabs[i].webview.props.name === 'Settings') { | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				initialSubmenu.push({ | 				initialSubmenu.push({ | ||||||
| 					label: tabs[i].webview.props.name, | 					label: tabs[i].webview.props.name, | ||||||
| 					accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`, | 					accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`, | ||||||
| @@ -159,7 +171,7 @@ class AppMenu { | |||||||
| 							AppMenu.sendAction('switch-server-tab', tabs[i].props.index); | 							AppMenu.sendAction('switch-server-tab', tabs[i].props.index); | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
| 					type: 'radio' | 					type: 'checkbox' | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -182,7 +194,7 @@ class AppMenu { | |||||||
| 			}, { | 			}, { | ||||||
| 				type: 'separator' | 				type: 'separator' | ||||||
| 			}, { | 			}, { | ||||||
| 				label: 'Settings', | 				label: 'Desktop App Settings', | ||||||
| 				accelerator: 'Cmd+,', | 				accelerator: 'Cmd+,', | ||||||
| 				click(item, focusedWindow) { | 				click(item, focusedWindow) { | ||||||
| 					if (focusedWindow) { | 					if (focusedWindow) { | ||||||
| @@ -191,7 +203,7 @@ class AppMenu { | |||||||
| 				} | 				} | ||||||
| 			}, { | 			}, { | ||||||
| 				label: 'Keyboard Shortcuts', | 				label: 'Keyboard Shortcuts', | ||||||
| 				accelerator: 'Cmd+K', | 				accelerator: 'Cmd+Shift+K', | ||||||
| 				click(item, focusedWindow) { | 				click(item, focusedWindow) { | ||||||
| 					if (focusedWindow) { | 					if (focusedWindow) { | ||||||
| 						AppMenu.sendAction('shortcut'); | 						AppMenu.sendAction('shortcut'); | ||||||
| @@ -282,7 +294,7 @@ class AppMenu { | |||||||
| 			}, { | 			}, { | ||||||
| 				type: 'separator' | 				type: 'separator' | ||||||
| 			}, { | 			}, { | ||||||
| 				label: 'Settings', | 				label: 'Desktop App Settings', | ||||||
| 				accelerator: 'Ctrl+,', | 				accelerator: 'Ctrl+,', | ||||||
| 				click(item, focusedWindow) { | 				click(item, focusedWindow) { | ||||||
| 					if (focusedWindow) { | 					if (focusedWindow) { | ||||||
| @@ -293,7 +305,7 @@ class AppMenu { | |||||||
| 				type: 'separator' | 				type: 'separator' | ||||||
| 			}, { | 			}, { | ||||||
| 				label: 'Keyboard Shortcuts', | 				label: 'Keyboard Shortcuts', | ||||||
| 				accelerator: 'Ctrl+K', | 				accelerator: 'Ctrl+Shift+K', | ||||||
| 				click(item, focusedWindow) { | 				click(item, focusedWindow) { | ||||||
| 					if (focusedWindow) { | 					if (focusedWindow) { | ||||||
| 						AppMenu.sendAction('shortcut'); | 						AppMenu.sendAction('shortcut'); | ||||||
|   | |||||||
							
								
								
									
										1380
									
								
								app/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1380
									
								
								app/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,14 +1,13 @@ | |||||||
| { | { | ||||||
|   "name": "zulip", |   "name": "zulip", | ||||||
|   "productName": "Zulip", |   "productName": "Zulip", | ||||||
|   "version": "1.6.0-beta", |   "version": "1.8.2", | ||||||
|   "description": "Zulip Desktop App", |   "description": "Zulip Desktop App", | ||||||
|   "license": "Apache-2.0", |   "license": "Apache-2.0", | ||||||
|   "email": "<svnitakash@gmail.com>", |   "copyright": "Kandra Labs, Inc.", | ||||||
|   "copyright": "©2017 Kandra Labs, Inc.", |  | ||||||
|   "author": { |   "author": { | ||||||
|     "name": "Kandra Labs, Inc.", |     "name": "Kandra Labs, Inc.", | ||||||
|     "email": "svnitakash@gmail.com" |     "email": "support@zulipchat.com" | ||||||
|   }, |   }, | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "git", |     "type": "git", | ||||||
| @@ -27,14 +26,19 @@ | |||||||
|     "InstantMessaging" |     "InstantMessaging" | ||||||
|   ], |   ], | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "auto-launch": "5.0.1", | ||||||
|     "electron-is-dev": "0.3.0", |     "electron-is-dev": "0.3.0", | ||||||
|     "electron-log": "2.2.7", |     "electron-log": "2.2.7", | ||||||
|     "electron-spellchecker": "1.1.2", |     "electron-spellchecker": "1.1.2", | ||||||
|     "electron-updater": "2.16.2", |     "electron-updater": "2.18.2", | ||||||
|  |     "electron-window-state": "4.1.1", | ||||||
|  |     "is-online": "7.0.0", | ||||||
|     "node-json-db": "0.7.3", |     "node-json-db": "0.7.3", | ||||||
|     "request": "2.81.0", |     "request": "2.81.0", | ||||||
|     "wurl": "2.5.0", |     "semver": "5.4.1", | ||||||
|     "electron-window-state": "4.1.1", |     "wurl": "2.5.0" | ||||||
|     "auto-launch": "5.0.1" |   }, | ||||||
|  |   "optionalDependencies": { | ||||||
|  |     "node-mac-notifier": "0.0.13" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,25 +7,37 @@ | |||||||
| 	<body> | 	<body> | ||||||
| 	<div class="about"> | 	<div class="about"> | ||||||
| 		<img class="logo" src="../resources/zulip.png" /> | 		<img class="logo" src="../resources/zulip.png" /> | ||||||
| 		<p class="detail" id="version">version ?.?.?</p> | 		<p class="detail" id="version">v?.?.?</p> | ||||||
| 		<div class="maintenance-info"> | 		<div class="maintenance-info"> | ||||||
| 			<p class="detail maintainer">Maintained by Zulip</p> | 			<p class="detail maintainer"> | ||||||
| 			<p class="detail license">Available under the Apache License</p> | 				Maintained by <a onclick="linkInBrowser('website')">Zulip</a> | ||||||
| 			<a class="bug" onclick="linkInBrowser()" href="#">Found bug?</a> | 			</p> | ||||||
|  | 			<p class="detail license"> | ||||||
|  | 				Available under the <a onclick="linkInBrowser('license')">Apache 2.0 License</a> | ||||||
|  | 			</p> | ||||||
|  | 			<a class="bug" onclick="linkInBrowser('bug')" href="#">Found bug?</a> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 	<script> | 	<script> | ||||||
|  |  | ||||||
| 	const app = require('electron').remote.app; | 	const { app } = require('electron').remote; | ||||||
| 	const version_tag = document.getElementById('version'); | 	const { shell } = require('electron'); | ||||||
| 	version_tag.innerHTML = 'version ' + app.getVersion(); | 	const version_tag = document.querySelector('#version'); | ||||||
|  | 	version_tag.innerHTML = 'v' + app.getVersion(); | ||||||
| 	function linkInBrowser(event) { |  | ||||||
|  |  | ||||||
| 		const shell = require('electron').shell; |  | ||||||
|  |  | ||||||
| 		const url = "https://github.com/zulip/zulip-electron/issues/new?body=Please describe your issue and steps to reproduce it." |  | ||||||
|  |  | ||||||
|  | 	function linkInBrowser(type) { | ||||||
|  | 		let url; | ||||||
|  | 		switch (type) { | ||||||
|  | 			case 'website':  | ||||||
|  | 				url = "https://zulipchat.com"; | ||||||
|  | 				break; | ||||||
|  | 			case 'license':  | ||||||
|  | 				url = "https://github.com/zulip/zulip-electron/blob/master/LICENSE"; | ||||||
|  | 				break; | ||||||
|  | 			default: | ||||||
|  | 				url = 'https://github.com/zulip/zulip-electron/issues/new?body=' +  | ||||||
|  | 				      '%3C!--Please%20describe%20your%20issue%20and%20steps%20to%20reproduce%20it.--%3E'; | ||||||
|  | 		} | ||||||
| 		shell.openExternal(url); | 		shell.openExternal(url); | ||||||
| 	} | 	} | ||||||
| 	</script> | 	</script> | ||||||
|   | |||||||
| @@ -1,21 +1,23 @@ | |||||||
| body { | body { | ||||||
| 	background: #fafafa; | 	background: #fafafa; | ||||||
| 	font-family: menu, "Helvetica Neue", sans-serif; | 	font-family: menu, "Helvetica Neue", sans-serif; | ||||||
| 	-webkit-font-smoothing: antialiased; | 	-webkit-font-smoothing: subpixel-antialiased; | ||||||
| } | } | ||||||
|  |  | ||||||
| .logo { | .logo { | ||||||
| 	display: block; | 	display: block; | ||||||
| 	margin: 0 auto; | 	margin: -40px auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| #version { | #version { | ||||||
| 	color: #aaa; | 	color: #444343; | ||||||
| 	font-size: 0.9em; | 	font-size: 1.3em; | ||||||
|  | 	padding-top: 40px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .about { | .about { | ||||||
| 	margin-top: 50px; | 	margin: 25vh auto; | ||||||
|  | 	height: 25vh; | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -42,9 +44,9 @@ body { | |||||||
| } | } | ||||||
|  |  | ||||||
| .maintenance-info { | .maintenance-info { | ||||||
|  | 	cursor: pointer; | ||||||
| 	position: absolute; | 	position: absolute; | ||||||
| 	width: 100%; | 	width: 100%; | ||||||
| 	bottom: 20px; |  | ||||||
| 	left: 0px; | 	left: 0px; | ||||||
| 	color: #444; | 	color: #444; | ||||||
| } | } | ||||||
| @@ -52,7 +54,6 @@ body { | |||||||
| .maintenance-info p { | .maintenance-info p { | ||||||
| 	margin: 0; | 	margin: 0; | ||||||
| 	font-size: 1em; | 	font-size: 1em; | ||||||
|  |  | ||||||
| 	width: 100%; | 	width: 100%; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -71,3 +72,11 @@ body { | |||||||
| .maintenance-info .bug:hover { | .maintenance-info .bug:hover { | ||||||
| 	background-color: #32a692; | 	background-color: #32a692; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | p.detail a { | ||||||
|  | 	color: #355f4c; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | p.detail a:hover { | ||||||
|  | 	text-decoration: underline; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ body { | |||||||
| #content { | #content { | ||||||
|     display: flex; |     display: flex; | ||||||
|     height: 100%; |     height: 100%; | ||||||
|     background: #eee url(../img/ic_loading.gif) no-repeat; |     background: #fff url(../img/ic_loading.gif) no-repeat; | ||||||
|     background-size: 60px 60px; |     background-size: 60px 60px; | ||||||
|     background-position: center; |     background-position: center; | ||||||
| } | } | ||||||
| @@ -81,11 +81,18 @@ body { | |||||||
|     text-rendering: optimizeLegibility; |     text-rendering: optimizeLegibility; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #actions-container { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     position: fixed; | ||||||
|  |     bottom: 0; | ||||||
|  |     width: inherit; | ||||||
|  | } | ||||||
|  |  | ||||||
| .action-button { | .action-button { | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|     align-items: center; |     padding: 12px; | ||||||
|     padding: 10px; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .action-button:hover { | .action-button:hover { | ||||||
| @@ -101,6 +108,26 @@ body { | |||||||
|     color: #98a9b3; |     color: #98a9b3; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .action-button.disable { | ||||||
|  |     opacity: 0.6; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .action-button.disable:hover { | ||||||
|  |     cursor: not-allowed; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .action-button.disable:hover i { | ||||||
|  |     color: #6c8592; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .action-button.active { | ||||||
|  |     background-color: rgba(255, 255, 255, 0.25); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .action-button.active i { | ||||||
|  |     color: #eee; | ||||||
|  | } | ||||||
|  |  | ||||||
| .tab:first-child { | .tab:first-child { | ||||||
|     margin-top: 8px; |     margin-top: 8px; | ||||||
| } | } | ||||||
| @@ -223,7 +250,7 @@ body { | |||||||
|  |  | ||||||
| webview { | webview { | ||||||
|     opacity: 1; |     opacity: 1; | ||||||
|     transition: opacity 0.3s ease-in; |     /* transition: opacity 0.3s ease-in; */ | ||||||
|     flex-grow: 1; |     flex-grow: 1; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -246,6 +273,7 @@ webview:focus { | |||||||
|  |  | ||||||
| /* Tooltip styling */ | /* Tooltip styling */ | ||||||
|  |  | ||||||
|  | #back-tooltip, | ||||||
| #reload-tooltip, | #reload-tooltip, | ||||||
| #setting-tooltip { | #setting-tooltip { | ||||||
|     font-family: sans-serif; |     font-family: sans-serif; | ||||||
| @@ -262,6 +290,7 @@ webview:focus { | |||||||
|     font-size: 14px; |     font-size: 14px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #back-tooltip:after, | ||||||
| #reload-tooltip:after, | #reload-tooltip:after, | ||||||
| #setting-tooltip:after { | #setting-tooltip:after { | ||||||
|     content: " "; |     content: " "; | ||||||
| @@ -362,4 +391,4 @@ webview:focus { | |||||||
|         overflow: hidden; |         overflow: hidden; | ||||||
|         opacity: 1; |         opacity: 1; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,79 +5,87 @@ body { | |||||||
|     cursor: default; |     cursor: default; | ||||||
|     user-select: none; |     user-select: none; | ||||||
|     font-family: menu, "Helvetica Neue", sans-serif; |     font-family: menu, "Helvetica Neue", sans-serif; | ||||||
| 	-webkit-font-smoothing: antialiased; |     -webkit-font-smoothing: antialiased; | ||||||
|     font-size: 14px; |     font-size: 14px; | ||||||
|     color: #333; |     color: #333; | ||||||
|     background: #efefef; |     background: #efefef; | ||||||
| } | } | ||||||
|  |  | ||||||
| kbd { | kbd { | ||||||
|     padding: 0.3em 0.8em; |     display: inline-block; | ||||||
|     border: 1px solid #ccc; |     border: 1px solid #ccc ; | ||||||
|  |     border-radius: 3px; | ||||||
|     font-size: 15px; |     font-size: 15px; | ||||||
|     font-family: Courier New, Courier, monospace; |     font-family: Courier New, Courier, monospace; | ||||||
|     background-color: #383430; |  | ||||||
|     color: #ededed; |  | ||||||
|     display: inline-block; |  | ||||||
|     margin: 0 0.1em; |  | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     white-space: nowrap; |     white-space: nowrap; | ||||||
|  |     background-color: #f7f7f7; | ||||||
|  |     color: #333; | ||||||
|  |     margin: 0 0.1em; | ||||||
|  |     padding: 0.3em 0.8em; | ||||||
|  |     text-shadow: 0 1px 0 #fff; | ||||||
|  |     line-height: 1.4; | ||||||
| } | } | ||||||
|  |  | ||||||
| table, th, td { | table, | ||||||
|  | th, | ||||||
|  | td { | ||||||
|     border-collapse: collapse; |     border-collapse: collapse; | ||||||
|     color: #383430; |     color: #383430; | ||||||
| } | } | ||||||
|  |  | ||||||
| table {  | table { | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     margin-top: 18px; |     margin-top: 18px; | ||||||
|     margin-bottom: 18px; |     margin-bottom: 18px; | ||||||
|  } | } | ||||||
|  |  | ||||||
| table tr:nth-child(even) { background-color: #f7eee6; } | table tr:nth-child(even) { | ||||||
|  |     background-color: #fafafa; | ||||||
|  | } | ||||||
|  |  | ||||||
| table tr:nth-child(odd) { background-color: #fff8ef; } | table tr:nth-child(odd) { | ||||||
|  |     background-color: #fff; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | td { | ||||||
| td { padding: 5px; } |     padding: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
| td:nth-child(odd) { | td:nth-child(odd) { | ||||||
| 		text-align: right; |     text-align: right; | ||||||
| 		width: 50%; |     width: 50%; | ||||||
| } | } | ||||||
|  |  | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'Material Icons'; |     font-family: 'Material Icons'; | ||||||
|   font-style: normal; |     font-style: normal; | ||||||
|   font-weight: 400; |     font-weight: 400; | ||||||
|   src: local('Material Icons'), |     src: local('Material Icons'), local('MaterialIcons-Regular'), url(../fonts/MaterialIcons-Regular.ttf) format('truetype'); | ||||||
|        local('MaterialIcons-Regular'), |  | ||||||
|        url(../fonts/MaterialIcons-Regular.ttf) format('truetype'); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'Montserrat'; |     font-family: 'Montserrat'; | ||||||
|   src: url(../fonts/Montserrat-Regular.ttf) format('truetype'); |     src: url(../fonts/Montserrat-Regular.ttf) format('truetype'); | ||||||
| } | } | ||||||
|  |  | ||||||
| .material-icons { | .material-icons { | ||||||
|   font-family: 'Material Icons'; |     font-family: 'Material Icons'; | ||||||
|   font-weight: normal; |     font-weight: normal; | ||||||
|   font-style: normal; |     font-style: normal; | ||||||
|   /* Preferred icon size */ |     /* Preferred icon size */ | ||||||
|   font-size: 24px; |     font-size: 24px; | ||||||
|   display: inline-block; |     display: inline-block; | ||||||
|   line-height: 1; |     line-height: 1; | ||||||
|   text-transform: none; |     text-transform: none; | ||||||
|   letter-spacing: normal; |     letter-spacing: normal; | ||||||
|   word-wrap: normal; |     word-wrap: normal; | ||||||
|   white-space: nowrap; |     white-space: nowrap; | ||||||
|   direction: ltr; |     direction: ltr; | ||||||
|   /* Support for all WebKit browsers. */ |     /* Support for all WebKit browsers. */ | ||||||
|   -webkit-font-smoothing: antialiased; |     -webkit-font-smoothing: antialiased; | ||||||
|   /* Support for Safari and Chrome. */ |     /* Support for Safari and Chrome. */ | ||||||
|   text-rendering: optimizeLegibility; |     text-rendering: optimizeLegibility; | ||||||
| } | } | ||||||
|  |  | ||||||
| #content { | #content { | ||||||
| @@ -140,9 +148,15 @@ td:nth-child(odd) { | |||||||
|  |  | ||||||
| .title { | .title { | ||||||
|     padding: 4px 0 6px 0; |     padding: 4px 0 6px 0; | ||||||
|     font-weight: bold; |     font-weight: 500; | ||||||
|     color: #222c31; |     color: #222c31; | ||||||
|     text-transform: uppercase; | } | ||||||
|  |  | ||||||
|  | .page-title { | ||||||
|  |     color: #222c31; | ||||||
|  |     font-size: 15px; | ||||||
|  |     font-weight: bold; | ||||||
|  |     padding: 4px 0 6px 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .sub-title { | .sub-title { | ||||||
| @@ -189,16 +203,18 @@ img.server-info-icon { | |||||||
| .setting-input-value { | .setting-input-value { | ||||||
|     flex-grow: 1; |     flex-grow: 1; | ||||||
|     font-size: 14px; |     font-size: 14px; | ||||||
|     height: 24px; |     height: 22px; | ||||||
|     border: none; |     border-radius: 3px; | ||||||
|     border-bottom: #ededed 1px solid; |     padding: 7px; | ||||||
|  |     border: #ededed 2px solid; | ||||||
|     outline-width: 0; |     outline-width: 0; | ||||||
|     background: transparent; |     background: transparent; | ||||||
|     max-width: 500px; |     max-width: 500px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .setting-input-value:focus { | .setting-input-value:focus { | ||||||
|     border-bottom: #7cb980 1px solid; |     border: #7cb980 2px solid; | ||||||
|  |     border-radius: 3px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .setting-block { | .setting-block { | ||||||
| @@ -224,7 +240,7 @@ img.server-info-icon { | |||||||
| .action i { | .action i { | ||||||
|     margin-right: 5px; |     margin-right: 5px; | ||||||
|     font-size: 18px; |     font-size: 18px; | ||||||
|     line-height: 27px; |     line-height: 26px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .settings-pane { | .settings-pane { | ||||||
| @@ -250,7 +266,13 @@ img.server-info-icon { | |||||||
|     margin: 10px 0 20px 0; |     margin: 10px 0 20px 0; | ||||||
|     background: #fff; |     background: #fff; | ||||||
|     width: 70%; |     width: 70%; | ||||||
|  |     transition: all 0.2s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .settings-card:hover { | ||||||
|     border-left: 8px solid #bcbcbc; |     border-left: 8px solid #bcbcbc; | ||||||
|  |     box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), | ||||||
|  |                 0 2px 0px 0px rgba(0,0,0,0.12); | ||||||
| } | } | ||||||
|  |  | ||||||
| .hidden { | .hidden { | ||||||
| @@ -315,75 +337,79 @@ i.open-tab-button { | |||||||
| } | } | ||||||
|  |  | ||||||
| #server-info-container { | #server-info-container { | ||||||
| 		min-height: calc(100% - 235px); |     min-height: calc(100% - 260px); | ||||||
| } | } | ||||||
|  |  | ||||||
| #create-organization-container { | #create-organization-container { | ||||||
| 		font-size: 1.15em; |     font-size: 1.15em; | ||||||
| 		margin-bottom: 15px; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #create-organization-container i { | #create-organization-container i { | ||||||
| 	position: relative; |     position: relative; | ||||||
| 	top: 3px; |     top: 3px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #open-create-org-link { | #open-create-org-link { | ||||||
| 		color: #666; |     color: #666; | ||||||
| 		cursor: pointer; |     cursor: pointer; | ||||||
| 		text-decoration: none; |     text-decoration: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #open-create-org-link:hover { | #open-create-org-link:hover { | ||||||
| 		color: #005580;; |     color: #005580; | ||||||
| 		text-decoration: underline; |     ; | ||||||
|  |     text-decoration: underline; | ||||||
| } | } | ||||||
|  |  | ||||||
| .toggle { | .toggle { | ||||||
|   position: absolute; |     position: absolute; | ||||||
|   margin-left: -9999px; |     margin-left: -9999px; | ||||||
|   visibility: hidden; |     visibility: hidden; | ||||||
| } |  | ||||||
| .toggle + label { |  | ||||||
|   display: block; |  | ||||||
|   position: relative; |  | ||||||
|   cursor: pointer; |  | ||||||
|   outline: none; |  | ||||||
|   user-select: none; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| input.toggle-round + label { | .toggle+label { | ||||||
|   padding: 2px; |     display: block; | ||||||
|   width: 50px; |     position: relative; | ||||||
|   height: 25px; |     cursor: pointer; | ||||||
|   background-color: #dddddd; |     outline: none; | ||||||
|   border-radius: 25px; |     user-select: none; | ||||||
| } | } | ||||||
| input.toggle-round + label:before, |  | ||||||
| input.toggle-round + label:after { | input.toggle-round+label { | ||||||
|   display: block; |     padding: 2px; | ||||||
|   position: absolute; |     width: 50px; | ||||||
|   top: 2px; |     height: 25px; | ||||||
|   left: 2px; |     background-color: #dddddd; | ||||||
|   bottom: 2px; |     border-radius: 25px; | ||||||
|   content: ""; |  | ||||||
| } | } | ||||||
| input.toggle-round + label:before { |  | ||||||
|   right: 2px; | input.toggle-round+label:before, | ||||||
|   background-color: #f1f1f1; | input.toggle-round+label:after { | ||||||
|   border-radius: 25px; |     display: block; | ||||||
|   transition: background 0.4s; |     position: absolute; | ||||||
|  |     top: 2px; | ||||||
|  |     left: 2px; | ||||||
|  |     bottom: 2px; | ||||||
|  |     content: ""; | ||||||
| } | } | ||||||
| input.toggle-round + label:after { |  | ||||||
|   width: 25px; | input.toggle-round+label:before { | ||||||
|   height: 25px; |     right: 2px; | ||||||
|   background-color: #fff; |     background-color: #f1f1f1; | ||||||
|   border-radius: 100%; |     border-radius: 25px; | ||||||
|   transition: margin 0.4s; |  | ||||||
| } | } | ||||||
| input.toggle-round:checked + label:before { |  | ||||||
|   background-color: #4EBFAC; | input.toggle-round+label:after { | ||||||
|  |     width: 25px; | ||||||
|  |     height: 25px; | ||||||
|  |     background-color: #fff; | ||||||
|  |     border-radius: 100%; | ||||||
| } | } | ||||||
| input.toggle-round:checked + label:after { |  | ||||||
|   margin-left: 25px; | input.toggle-round:checked+label:before { | ||||||
|  |     background-color: #4EBFAC; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input.toggle-round:checked+label:after { | ||||||
|  |     margin-left: 25px; | ||||||
| } | } | ||||||
| @@ -4,7 +4,7 @@ const Tab = require(__dirname + '/../components/tab.js'); | |||||||
|  |  | ||||||
| class FunctionalTab extends Tab { | class FunctionalTab extends Tab { | ||||||
| 	template() { | 	template() { | ||||||
| 		return `<div class="tab functional-tab"> | 		return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}"> | ||||||
| 					<div class="server-tab-badge close-button"> | 					<div class="server-tab-badge close-button"> | ||||||
| 						<i class="material-icons">close</i> | 						<i class="material-icons">close</i> | ||||||
| 					</div> | 					</div> | ||||||
| @@ -16,10 +16,11 @@ class FunctionalTab extends Tab { | |||||||
|  |  | ||||||
| 	init() { | 	init() { | ||||||
| 		this.$el = this.generateNodeFromTemplate(this.template()); | 		this.$el = this.generateNodeFromTemplate(this.template()); | ||||||
| 		this.props.$root.appendChild(this.$el); | 		if (this.props.name !== 'Settings') { | ||||||
|  | 			this.props.$root.appendChild(this.$el); | ||||||
| 		this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0]; | 			this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0]; | ||||||
| 		this.registerListeners(); | 			this.registerListeners(); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	registerListeners() { | 	registerListeners() { | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ const {ipcRenderer} = require('electron'); | |||||||
|  |  | ||||||
| class ServerTab extends Tab { | class ServerTab extends Tab { | ||||||
| 	template() { | 	template() { | ||||||
| 		return `<div class="tab"> | 		return `<div class="tab" data-tab-id="${this.props.tabIndex}"> | ||||||
| 					<div class="server-tooltip" style="display:none"></div> | 					<div class="server-tooltip" style="display:none"></div> | ||||||
| 					<div class="server-tab-badge"></div> | 					<div class="server-tab-badge"></div> | ||||||
| 					<div class="server-tab"> | 					<div class="server-tab"> | ||||||
| @@ -50,7 +50,8 @@ class ServerTab extends Tab { | |||||||
| 			shortcutText = `Ctrl+${shownIndex}`; | 			shortcutText = `Ctrl+${shownIndex}`; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ipcRenderer.send('register-server-tab-shortcut', shownIndex); | 		// Array index == Shown index - 1 | ||||||
|  | 		ipcRenderer.send('switch-server-tab', shownIndex - 1); | ||||||
|  |  | ||||||
| 		return shortcutText; | 		return shortcutText; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -4,12 +4,14 @@ const path = require('path'); | |||||||
| const fs = require('fs'); | const fs = require('fs'); | ||||||
|  |  | ||||||
| const DomainUtil = require(__dirname + '/../utils/domain-util.js'); | const DomainUtil = require(__dirname + '/../utils/domain-util.js'); | ||||||
|  | const ConfigUtil = require(__dirname + '/../utils/config-util.js'); | ||||||
| const SystemUtil = require(__dirname + '/../utils/system-util.js'); | const SystemUtil = require(__dirname + '/../utils/system-util.js'); | ||||||
| const LinkUtil = require(__dirname + '/../utils/link-util.js'); | const LinkUtil = require(__dirname + '/../utils/link-util.js'); | ||||||
| const { shell, app } = require('electron').remote; | const { shell, app } = require('electron').remote; | ||||||
|  |  | ||||||
| const BaseComponent = require(__dirname + '/../components/base.js'); | const BaseComponent = require(__dirname + '/../components/base.js'); | ||||||
|  |  | ||||||
|  | const shouldSilentWebview = ConfigUtil.getConfigItem('silent'); | ||||||
| class WebView extends BaseComponent { | class WebView extends BaseComponent { | ||||||
| 	constructor(props) { | 	constructor(props) { | ||||||
| 		super(); | 		super(); | ||||||
| @@ -24,6 +26,7 @@ class WebView extends BaseComponent { | |||||||
| 	template() { | 	template() { | ||||||
| 		return `<webview | 		return `<webview | ||||||
| 					class="disabled" | 					class="disabled" | ||||||
|  | 					data-tab-id="${this.props.tabIndex}" | ||||||
| 					src="${this.props.url}" | 					src="${this.props.url}" | ||||||
| 					${this.props.nodeIntegration ? 'nodeIntegration' : ''} | 					${this.props.nodeIntegration ? 'nodeIntegration' : ''} | ||||||
| 					disablewebsecurity | 					disablewebsecurity | ||||||
| @@ -54,12 +57,30 @@ class WebView extends BaseComponent { | |||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		if (shouldSilentWebview) { | ||||||
|  | 			this.$el.addEventListener('dom-ready', () => { | ||||||
|  | 				this.$el.setAudioMuted(true); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		this.$el.addEventListener('page-title-updated', event => { | 		this.$el.addEventListener('page-title-updated', event => { | ||||||
| 			const { title } = event; | 			const { title } = event; | ||||||
| 			this.badgeCount = this.getBadgeCount(title); | 			this.badgeCount = this.getBadgeCount(title); | ||||||
| 			this.props.onTitleChange(); | 			this.props.onTitleChange(); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		this.$el.addEventListener('did-navigate-in-page', event => { | ||||||
|  | 			const isSettingPage = event.url.includes('renderer/preference.html'); | ||||||
|  | 			if (isSettingPage) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			this.canGoBackButton(); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		this.$el.addEventListener('did-navigate', () => { | ||||||
|  | 			this.canGoBackButton(); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		this.$el.addEventListener('page-favicon-updated', event => { | 		this.$el.addEventListener('page-favicon-updated', event => { | ||||||
| 			const { favicons } = 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 | 			// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like | ||||||
| @@ -174,6 +195,15 @@ class WebView extends BaseComponent { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	canGoBackButton() { | ||||||
|  | 		const $backButton = document.querySelector('#actions-container #back-action'); | ||||||
|  | 		if (this.$el.canGoBack()) { | ||||||
|  | 			$backButton.classList.remove('disable'); | ||||||
|  | 		} else { | ||||||
|  | 			$backButton.classList.add('disable'); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	forward() { | 	forward() { | ||||||
| 		if (this.$el.canGoForward()) { | 		if (this.$el.canGoForward()) { | ||||||
| 			this.$el.goForward(); | 			this.$el.goForward(); | ||||||
|   | |||||||
| @@ -1,15 +1,17 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| require(__dirname + '/js/tray.js'); |  | ||||||
| const { ipcRenderer, remote } = require('electron'); | const { ipcRenderer, remote } = require('electron'); | ||||||
|  | const isDev = require('electron-is-dev'); | ||||||
|  |  | ||||||
| const { session } = remote; | const { session } = remote; | ||||||
|  |  | ||||||
|  | require(__dirname + '/js/tray.js'); | ||||||
| const DomainUtil = require(__dirname + '/js/utils/domain-util.js'); | const DomainUtil = require(__dirname + '/js/utils/domain-util.js'); | ||||||
| const WebView = require(__dirname + '/js/components/webview.js'); | const WebView = require(__dirname + '/js/components/webview.js'); | ||||||
| const ServerTab = require(__dirname + '/js/components/server-tab.js'); | const ServerTab = require(__dirname + '/js/components/server-tab.js'); | ||||||
| const FunctionalTab = require(__dirname + '/js/components/functional-tab.js'); | const FunctionalTab = require(__dirname + '/js/components/functional-tab.js'); | ||||||
| const ConfigUtil = require(__dirname + '/js/utils/config-util.js'); | const ConfigUtil = require(__dirname + '/js/utils/config-util.js'); | ||||||
|  | const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js'); | ||||||
|  |  | ||||||
| class ServerManagerView { | class ServerManagerView { | ||||||
| 	constructor() { | 	constructor() { | ||||||
| @@ -20,11 +22,13 @@ class ServerManagerView { | |||||||
| 		this.$reloadButton = $actionsContainer.querySelector('#reload-action'); | 		this.$reloadButton = $actionsContainer.querySelector('#reload-action'); | ||||||
| 		this.$settingsButton = $actionsContainer.querySelector('#settings-action'); | 		this.$settingsButton = $actionsContainer.querySelector('#settings-action'); | ||||||
| 		this.$webviewsContainer = document.getElementById('webviews-container'); | 		this.$webviewsContainer = document.getElementById('webviews-container'); | ||||||
|  | 		this.$backButton = $actionsContainer.querySelector('#back-action'); | ||||||
|  |  | ||||||
| 		this.$addServerTooltip = document.getElementById('add-server-tooltip'); | 		this.$addServerTooltip = document.getElementById('add-server-tooltip'); | ||||||
| 		this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip'); | 		this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip'); | ||||||
| 		this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip'); | 		this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip'); | ||||||
| 		this.$serverIconTooltip = document.getElementsByClassName('server-tooltip'); | 		this.$serverIconTooltip = document.getElementsByClassName('server-tooltip'); | ||||||
|  | 		this.$backTooltip = $actionsContainer.querySelector('#back-tooltip'); | ||||||
|  |  | ||||||
| 		this.$sidebar = document.getElementById('sidebar'); | 		this.$sidebar = document.getElementById('sidebar'); | ||||||
|  |  | ||||||
| @@ -35,6 +39,7 @@ class ServerManagerView { | |||||||
| 		this.activeTabIndex = -1; | 		this.activeTabIndex = -1; | ||||||
| 		this.tabs = []; | 		this.tabs = []; | ||||||
| 		this.functionalTabs = {}; | 		this.functionalTabs = {}; | ||||||
|  | 		this.tabIndex = 0; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	init() { | 	init() { | ||||||
| @@ -43,6 +48,7 @@ class ServerManagerView { | |||||||
| 			this.initTabs(); | 			this.initTabs(); | ||||||
| 			this.initActions(); | 			this.initActions(); | ||||||
| 			this.registerIpcs(); | 			this.registerIpcs(); | ||||||
|  | 			this.initDefaultSettings(); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -65,6 +71,39 @@ class ServerManagerView { | |||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Settings are initialized only when user clicks on General/Server/Network section settings | ||||||
|  | 	// In case, user doesn't visit these section, those values set to be null automatically | ||||||
|  | 	// This will make sure the default settings are correctly set to either true or false | ||||||
|  | 	initDefaultSettings() { | ||||||
|  | 		// Default settings which should be respected | ||||||
|  | 		const settingOptions = { | ||||||
|  | 			trayIcon: true, | ||||||
|  | 			useProxy: false, | ||||||
|  | 			showSidebar: true, | ||||||
|  | 			badgeOption: true, | ||||||
|  | 			startAtLogin: false, | ||||||
|  | 			startMinimized: false, | ||||||
|  | 			enableSpellchecker: true, | ||||||
|  | 			showNotification: true, | ||||||
|  | 			betaUpdate: false, | ||||||
|  | 			silent: false, | ||||||
|  | 			lastActiveTab: 0 | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		// Platform specific settings | ||||||
|  |  | ||||||
|  | 		if (process.platform === 'win32') { | ||||||
|  | 			// Only available on Windows | ||||||
|  | 			settingOptions.flashTaskbarOnMessage = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for (const i in settingOptions) { | ||||||
|  | 			if (ConfigUtil.getConfigItem(i) === null) { | ||||||
|  | 				ConfigUtil.setConfigItem(i, settingOptions[i]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	initSidebar() { | 	initSidebar() { | ||||||
| 		const showSidebar = ConfigUtil.getConfigItem('showSidebar', true); | 		const showSidebar = ConfigUtil.getConfigItem('showSidebar', true); | ||||||
| 		this.toggleSidebar(showSidebar); | 		this.toggleSidebar(showSidebar); | ||||||
| @@ -80,23 +119,28 @@ class ServerManagerView { | |||||||
| 			} | 			} | ||||||
| 			// Open last active tab | 			// Open last active tab | ||||||
| 			this.activateTab(ConfigUtil.getConfigItem('lastActiveTab')); | 			this.activateTab(ConfigUtil.getConfigItem('lastActiveTab')); | ||||||
|  | 			// Remove focus from the settings icon at sidebar bottom | ||||||
|  | 			this.$settingsButton.classList.remove('active'); | ||||||
| 		} else { | 		} else { | ||||||
| 			this.openSettings('Servers'); | 			this.openSettings('Servers'); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	initServer(server, index) { | 	initServer(server, index) { | ||||||
|  | 		const tabIndex = this.getTabIndex(); | ||||||
| 		this.tabs.push(new ServerTab({ | 		this.tabs.push(new ServerTab({ | ||||||
| 			role: 'server', | 			role: 'server', | ||||||
| 			icon: server.icon, | 			icon: server.icon, | ||||||
| 			$root: this.$tabsContainer, | 			$root: this.$tabsContainer, | ||||||
| 			onClick: this.activateLastTab.bind(this, index), | 			onClick: this.activateLastTab.bind(this, index), | ||||||
| 			index, | 			index, | ||||||
|  | 			tabIndex, | ||||||
| 			onHover: this.onHover.bind(this, index, server.alias), | 			onHover: this.onHover.bind(this, index, server.alias), | ||||||
| 			onHoverOut: this.onHoverOut.bind(this, index), | 			onHoverOut: this.onHoverOut.bind(this, index), | ||||||
| 			webview: new WebView({ | 			webview: new WebView({ | ||||||
| 				$root: this.$webviewsContainer, | 				$root: this.$webviewsContainer, | ||||||
| 				index, | 				index, | ||||||
|  | 				tabIndex, | ||||||
| 				url: server.url, | 				url: server.url, | ||||||
| 				name: server.alias, | 				name: server.alias, | ||||||
| 				isActive: () => { | 				isActive: () => { | ||||||
| @@ -120,10 +164,27 @@ class ServerManagerView { | |||||||
| 		this.$settingsButton.addEventListener('click', () => { | 		this.$settingsButton.addEventListener('click', () => { | ||||||
| 			this.openSettings('General'); | 			this.openSettings('General'); | ||||||
| 		}); | 		}); | ||||||
|  | 		this.$backButton.addEventListener('click', () => { | ||||||
|  | 			this.tabs[this.activeTabIndex].webview.back(); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		const $serverImgs = document.querySelectorAll('.server-icons'); | ||||||
|  | 		$serverImgs.forEach($serverImg => { | ||||||
|  | 			$serverImg.addEventListener('error', () => { | ||||||
|  | 				$serverImg.src = 'img/icon.png'; | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip); | 		this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip); | ||||||
| 		this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip); | 		this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip); | ||||||
| 		this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip); | 		this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip); | ||||||
|  | 		this.sidebarHoverEvent(this.$backButton, this.$backTooltip); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	getTabIndex() { | ||||||
|  | 		const currentIndex = this.tabIndex; | ||||||
|  | 		this.tabIndex++; | ||||||
|  | 		return currentIndex; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	sidebarHoverEvent(SidebarButton, SidebarTooltip) { | 	sidebarHoverEvent(SidebarButton, SidebarTooltip) { | ||||||
| @@ -152,16 +213,20 @@ class ServerManagerView { | |||||||
|  |  | ||||||
| 		this.functionalTabs[tabProps.name] = this.tabs.length; | 		this.functionalTabs[tabProps.name] = this.tabs.length; | ||||||
|  |  | ||||||
|  | 		const tabIndex = this.getTabIndex(); | ||||||
| 		this.tabs.push(new FunctionalTab({ | 		this.tabs.push(new FunctionalTab({ | ||||||
| 			role: 'function', | 			role: 'function', | ||||||
| 			materialIcon: tabProps.materialIcon, | 			materialIcon: tabProps.materialIcon, | ||||||
|  | 			name: tabProps.name, | ||||||
| 			$root: this.$tabsContainer, | 			$root: this.$tabsContainer, | ||||||
| 			index: this.functionalTabs[tabProps.name], | 			index: this.functionalTabs[tabProps.name], | ||||||
|  | 			tabIndex, | ||||||
| 			onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]), | 			onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]), | ||||||
| 			onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]), | 			onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]), | ||||||
| 			webview: new WebView({ | 			webview: new WebView({ | ||||||
| 				$root: this.$webviewsContainer, | 				$root: this.$webviewsContainer, | ||||||
| 				index: this.functionalTabs[tabProps.name], | 				index: this.functionalTabs[tabProps.name], | ||||||
|  | 				tabIndex, | ||||||
| 				url: tabProps.url, | 				url: tabProps.url, | ||||||
| 				name: tabProps.name, | 				name: tabProps.name, | ||||||
| 				isActive: () => { | 				isActive: () => { | ||||||
| @@ -173,7 +238,6 @@ class ServerManagerView { | |||||||
| 				preload: false | 				preload: false | ||||||
| 			}) | 			}) | ||||||
| 		})); | 		})); | ||||||
|  |  | ||||||
| 		this.activateTab(this.functionalTabs[tabProps.name]); | 		this.activateTab(this.functionalTabs[tabProps.name]); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -183,6 +247,7 @@ class ServerManagerView { | |||||||
| 			materialIcon: 'settings', | 			materialIcon: 'settings', | ||||||
| 			url: `file://${__dirname}/preference.html#${nav}` | 			url: `file://${__dirname}/preference.html#${nav}` | ||||||
| 		}); | 		}); | ||||||
|  | 		this.$settingsButton.classList.add('active'); | ||||||
| 		this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav); | 		this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -218,10 +283,19 @@ class ServerManagerView { | |||||||
| 			if (this.activeTabIndex === index) { | 			if (this.activeTabIndex === index) { | ||||||
| 				return; | 				return; | ||||||
| 			} else if (hideOldTab) { | 			} else if (hideOldTab) { | ||||||
|  | 				// If old tab is functional tab Settings, remove focus from the settings icon at sidebar bottom | ||||||
|  | 				if (this.tabs[this.activeTabIndex].props.role === 'function' && this.tabs[this.activeTabIndex].props.name === 'Settings') { | ||||||
|  | 					this.$settingsButton.classList.remove('active'); | ||||||
|  | 				} | ||||||
| 				this.tabs[this.activeTabIndex].deactivate(); | 				this.tabs[this.activeTabIndex].deactivate(); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		try { | ||||||
|  | 			this.tabs[index].webview.canGoBackButton(); | ||||||
|  | 		} catch (err) { | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		this.activeTabIndex = index; | 		this.activeTabIndex = index; | ||||||
| 		this.tabs[index].activate(); | 		this.tabs[index].activate(); | ||||||
|  |  | ||||||
| @@ -229,6 +303,27 @@ class ServerManagerView { | |||||||
| 			tabs: this.tabs, | 			tabs: this.tabs, | ||||||
| 			activeTabIndex: this.activeTabIndex | 			activeTabIndex: this.activeTabIndex | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		ipcRenderer.on('toggle-sidebar', (event, state) => { | ||||||
|  | 			const selector = 'webview:not([class*=disabled])'; | ||||||
|  | 			const webview = document.querySelector(selector); | ||||||
|  | 			const webContents = webview.getWebContents(); | ||||||
|  | 			webContents.send('toggle-sidebar', state); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		ipcRenderer.on('toogle-silent', (event, state) => { | ||||||
|  | 			const webviews = document.querySelectorAll('webview'); | ||||||
|  | 			webviews.forEach(webview => { | ||||||
|  | 				try { | ||||||
|  | 					webview.setAudioMuted(state); | ||||||
|  | 				} catch (err) { | ||||||
|  | 					// webview is not ready yet | ||||||
|  | 					webview.addEventListener('dom-ready', () => { | ||||||
|  | 						webview.isAudioMuted(); | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	destroyTab(name, index) { | 	destroyTab(name, index) { | ||||||
| @@ -337,7 +432,7 @@ class ServerManagerView { | |||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		ipcRenderer.on('switch-server-tab', (event, index) => { | 		ipcRenderer.on('switch-server-tab', (event, index) => { | ||||||
| 			this.activateTab(index); | 			this.activateLastTab(index); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		ipcRenderer.on('reload-proxy', (event, showAlert) => { | 		ipcRenderer.on('reload-proxy', (event, showAlert) => { | ||||||
| @@ -361,6 +456,18 @@ class ServerManagerView { | |||||||
| 			this.$fullscreenPopup.classList.remove('show'); | 			this.$fullscreenPopup.classList.remove('show'); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		ipcRenderer.on('focus-webview-with-id', (event, webviewId) => { | ||||||
|  | 			const webviews = document.querySelectorAll('webview'); | ||||||
|  | 			webviews.forEach(webview => { | ||||||
|  | 				const currentId = webview.getWebContents().id; | ||||||
|  | 				const tabId = webview.getAttribute('data-tab-id'); | ||||||
|  | 				const concurrentTab = document.querySelector(`div[data-tab-id="${tabId}"]`); | ||||||
|  | 				if (currentId === webviewId) { | ||||||
|  | 					concurrentTab.click(); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		ipcRenderer.on('render-taskbar-icon', (event, messageCount) => { | 		ipcRenderer.on('render-taskbar-icon', (event, messageCount) => { | ||||||
| 			// Create a canvas from unread messagecounts | 			// Create a canvas from unread messagecounts | ||||||
| 			function createOverlayIcon(messageCount) { | 			function createOverlayIcon(messageCount) { | ||||||
| @@ -394,9 +501,24 @@ class ServerManagerView { | |||||||
|  |  | ||||||
| window.onload = () => { | window.onload = () => { | ||||||
| 	const serverManagerView = new ServerManagerView(); | 	const serverManagerView = new ServerManagerView(); | ||||||
|  | 	const reconnectUtil = new ReconnectUtil(serverManagerView); | ||||||
| 	serverManagerView.init(); | 	serverManagerView.init(); | ||||||
|  |  | ||||||
| 	window.addEventListener('online', () => { | 	window.addEventListener('online', () => { | ||||||
| 		serverManagerView.reloadView(); | 		reconnectUtil.pollInternetAndReload(); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  | 	window.addEventListener('offline', () => { | ||||||
|  | 		reconnectUtil.clearState(); | ||||||
|  | 		console.log('No internet connection, you are offline.'); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// only start electron-connect (auto reload on change) when its ran | ||||||
|  | 	// from `npm run dev` or `gulp dev` and not from `npm start` when | ||||||
|  | 	// app is started `npm start` main process's proces.argv will have | ||||||
|  | 	// `--no-electron-connect` | ||||||
|  | 	const mainProcessArgv = remote.getGlobal('process').argv; | ||||||
|  | 	if (isDev && !mainProcessArgv.includes('--no-electron-connect')) { | ||||||
|  | 		const electronConnect = require('electron-connect'); | ||||||
|  | 		electronConnect.client.create(); | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,30 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
|  |  | ||||||
| const { remote } = require('electron'); |  | ||||||
|  |  | ||||||
| const ConfigUtil = require(__dirname + '/utils/config-util.js'); |  | ||||||
|  |  | ||||||
| const app = remote.app; |  | ||||||
|  |  | ||||||
| // From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid |  | ||||||
| // On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work. |  | ||||||
| app.setAppUserModelId('org.zulip.zulip-electron'); |  | ||||||
|  |  | ||||||
| const NativeNotification = window.Notification; |  | ||||||
|  |  | ||||||
| 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 = baseNotification; |  | ||||||
|  |  | ||||||
							
								
								
									
										100
									
								
								app/renderer/js/notification/darwin-notifications.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								app/renderer/js/notification/darwin-notifications.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const { ipcRenderer } = require('electron'); | ||||||
|  | const url = require('url'); | ||||||
|  | const MacNotifier = require('node-mac-notifier'); | ||||||
|  | const ConfigUtil = require('../utils/config-util'); | ||||||
|  | const { | ||||||
|  | 	appId, customReply, focusCurrentServer, parseReply, setupReply | ||||||
|  | } = require('./helpers'); | ||||||
|  |  | ||||||
|  | let replyHandler; | ||||||
|  | let clickHandler; | ||||||
|  | class DarwinNotification { | ||||||
|  | 	constructor(title, opts) { | ||||||
|  | 		const silent = ConfigUtil.getConfigItem('silent') || false; | ||||||
|  | 		const { host, protocol } = location; | ||||||
|  | 		const { icon } = opts; | ||||||
|  | 		const profilePic = url.resolve(`${protocol}//${host}`, icon); | ||||||
|  |  | ||||||
|  | 		this.tag = opts.tag; | ||||||
|  | 		const notification = new MacNotifier(title, Object.assign(opts, { | ||||||
|  | 			bundleId: appId, | ||||||
|  | 			canReply: true, | ||||||
|  | 			silent, | ||||||
|  | 			icon: profilePic | ||||||
|  | 		})); | ||||||
|  |  | ||||||
|  | 		notification.addEventListener('click', () => { | ||||||
|  | 			// focus to the server who sent the | ||||||
|  | 			// notification if not focused already | ||||||
|  | 			if (clickHandler) { | ||||||
|  | 				clickHandler(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			focusCurrentServer(); | ||||||
|  | 			ipcRenderer.send('focus-app'); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		notification.addEventListener('reply', this.notificationHandler); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static requestPermission() { | ||||||
|  | 		return; // eslint-disable-line no-useless-return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Override default Notification permission | ||||||
|  | 	static get permission() { | ||||||
|  | 		return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied'; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	set onreply(handler) { | ||||||
|  | 		replyHandler = handler; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	get onreply() { | ||||||
|  | 		return replyHandler; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	set onclick(handler) { | ||||||
|  | 		clickHandler = handler; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	get onclick() { | ||||||
|  | 		return clickHandler; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// not something that is common or | ||||||
|  | 	// used by zulip server but added to be | ||||||
|  | 	// future proff. | ||||||
|  | 	addEventListener(event, handler) { | ||||||
|  | 		if (event === 'click') { | ||||||
|  | 			clickHandler = handler; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (event === 'reply') { | ||||||
|  | 			replyHandler = handler; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	notificationHandler({ response }) { | ||||||
|  | 		response = parseReply(response); | ||||||
|  | 		focusCurrentServer(); | ||||||
|  | 		setupReply(this.tag); | ||||||
|  |  | ||||||
|  | 		if (replyHandler) { | ||||||
|  | 			replyHandler(response); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		customReply(response); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// method specific to notification api | ||||||
|  | 	// used by zulip | ||||||
|  | 	close() { | ||||||
|  | 		return; // eslint-disable-line no-useless-return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = DarwinNotification; | ||||||
							
								
								
									
										31
									
								
								app/renderer/js/notification/default-notification.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/renderer/js/notification/default-notification.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const { ipcRenderer } = require('electron'); | ||||||
|  | const ConfigUtil = require('../utils/config-util'); | ||||||
|  | const { focusCurrentServer } = require('./helpers'); | ||||||
|  |  | ||||||
|  | const NativeNotification = window.Notification; | ||||||
|  | class BaseNotification extends NativeNotification { | ||||||
|  | 	constructor(title, opts) { | ||||||
|  | 		opts.silent = true; | ||||||
|  | 		super(title, opts); | ||||||
|  |  | ||||||
|  | 		this.addEventListener('click', () => { | ||||||
|  | 			// focus to the server who sent the | ||||||
|  | 			// notification if not focused already | ||||||
|  | 			focusCurrentServer(); | ||||||
|  | 			ipcRenderer.send('focus-app'); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static requestPermission() { | ||||||
|  | 		return; // eslint-disable-line no-useless-return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Override default Notification permission | ||||||
|  | 	static get permission() { | ||||||
|  | 		return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied'; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = BaseNotification; | ||||||
							
								
								
									
										149
									
								
								app/renderer/js/notification/helpers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								app/renderer/js/notification/helpers.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | |||||||
|  | const { remote } = require('electron'); | ||||||
|  |  | ||||||
|  | // Do not change this | ||||||
|  | const appId = 'org.zulip.zulip-electron'; | ||||||
|  |  | ||||||
|  | const botsList = []; | ||||||
|  | let botsListLoaded = false; | ||||||
|  |  | ||||||
|  | // this function load list of bots from the server | ||||||
|  | // sync=True for a synchronous getJSON request | ||||||
|  | // in case botsList isn't already completely loaded when required in parseRely | ||||||
|  | function loadBots(sync = false) { | ||||||
|  | 	const { $ } = window; | ||||||
|  | 	botsList.length = 0; | ||||||
|  | 	if (sync) { | ||||||
|  | 		$.ajaxSetup({async: false}); | ||||||
|  | 	} | ||||||
|  | 	$.getJSON('/json/users') | ||||||
|  | 		.done(data => { | ||||||
|  | 			const members = data.members; | ||||||
|  | 			members.forEach(membersRow => { | ||||||
|  | 				if (membersRow.is_bot) { | ||||||
|  | 					const bot = `@${membersRow.full_name}`; | ||||||
|  | 					const mention = `@**${bot.replace(/^@/, '')}**`; | ||||||
|  | 					botsList.push([bot, mention]); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 			botsListLoaded = true; | ||||||
|  | 		}) | ||||||
|  | 		.fail(error => { | ||||||
|  | 			console.log('Request failed: ', error.responseText); | ||||||
|  | 			console.log('Request status: ', error.statusText); | ||||||
|  | 		}); | ||||||
|  | 	if (sync) { | ||||||
|  | 		$.ajaxSetup({async: true}); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function checkElements(...elements) { | ||||||
|  | 	let status = true; | ||||||
|  | 	elements.forEach(element => { | ||||||
|  | 		if (element === null || element === undefined) { | ||||||
|  | 			status = false; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	return status; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function customReply(reply) { | ||||||
|  | 	// server does not support notification reply yet. | ||||||
|  | 	const buttonSelector = '.messagebox #send_controls button[type=submit]'; | ||||||
|  | 	const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content'; | ||||||
|  | 	const textarea = document.querySelector('#compose-textarea'); | ||||||
|  | 	const messagebox = document.querySelector(messageboxSelector); | ||||||
|  | 	const sendButton = document.querySelector(buttonSelector); | ||||||
|  |  | ||||||
|  | 	// sanity check for old server versions | ||||||
|  | 	const elementsExists = checkElements(textarea, messagebox, sendButton); | ||||||
|  | 	if (!elementsExists) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	textarea.value = reply; | ||||||
|  | 	messagebox.click(); | ||||||
|  | 	sendButton.click(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const currentWindow = remote.getCurrentWindow(); | ||||||
|  | const webContents = remote.getCurrentWebContents(); | ||||||
|  | const webContentsId = webContents.id; | ||||||
|  |  | ||||||
|  | // this function will focus the server that sent | ||||||
|  | // the notification. Main function implemented in main.js | ||||||
|  | function focusCurrentServer() { | ||||||
|  | 	currentWindow.send('focus-webview-with-id', webContentsId); | ||||||
|  | } | ||||||
|  | // this function parses the reply from to notification | ||||||
|  | // making it easier to reply from notification eg | ||||||
|  | // @username in reply will be converted to @**username** | ||||||
|  | // #stream in reply will be converted to #**stream** | ||||||
|  | // bot mentions are not yet supported | ||||||
|  | function parseReply(reply) { | ||||||
|  | 	const usersDiv = document.querySelectorAll('#user_presences li'); | ||||||
|  | 	const streamHolder = document.querySelectorAll('#stream_filters li'); | ||||||
|  | 	const users = []; | ||||||
|  | 	const streams = []; | ||||||
|  |  | ||||||
|  | 	usersDiv.forEach(userRow => { | ||||||
|  | 		const anchor = userRow.querySelector('span a'); | ||||||
|  | 		if (anchor !== null) { | ||||||
|  | 			const user = `@${anchor.textContent.trim()}`; | ||||||
|  | 			const mention = `@**${user.replace(/^@/, '')}**`; | ||||||
|  | 			users.push([user, mention]); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	streamHolder.forEach(stream => { | ||||||
|  | 		const streamAnchor = stream.querySelector('div a'); | ||||||
|  | 		if (streamAnchor !== null) { | ||||||
|  | 			const streamName = `#${streamAnchor.textContent.trim()}`; | ||||||
|  | 			const streamMention = `#**${streamName.replace(/^#/, '')}**`; | ||||||
|  | 			streams.push([streamName, streamMention]); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	users.forEach(([user, mention]) => { | ||||||
|  | 		if (reply.includes(user)) { | ||||||
|  | 			const regex = new RegExp(user, 'g'); | ||||||
|  | 			reply = reply.replace(regex, mention); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	streams.forEach(([stream, streamMention]) => { | ||||||
|  | 		const regex = new RegExp(stream, 'g'); | ||||||
|  | 		reply = reply.replace(regex, streamMention); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// If botsList isn't completely loaded yet, make a synchronous getJSON request for list | ||||||
|  | 	if (botsListLoaded === false) { | ||||||
|  | 		loadBots(true); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Iterate for every bot name and replace in reply | ||||||
|  | 	// @botname with @**botname** | ||||||
|  | 	botsList.forEach(([bot, mention]) => { | ||||||
|  | 		if (reply.includes(bot)) { | ||||||
|  | 			const regex = new RegExp(bot, 'g'); | ||||||
|  | 			reply = reply.replace(regex, mention); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	reply = reply.replace(/\\n/, '\n'); | ||||||
|  | 	return reply; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function setupReply(id) { | ||||||
|  | 	const { narrow } = window; | ||||||
|  | 	narrow.by_subject(id, { trigger: 'notification' }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  | 	appId, | ||||||
|  | 	checkElements, | ||||||
|  | 	customReply, | ||||||
|  | 	parseReply, | ||||||
|  | 	setupReply, | ||||||
|  | 	focusCurrentServer, | ||||||
|  | 	loadBots | ||||||
|  | }; | ||||||
							
								
								
									
										27
									
								
								app/renderer/js/notification/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/renderer/js/notification/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const { | ||||||
|  |   remote: { app } | ||||||
|  | } = require('electron'); | ||||||
|  |  | ||||||
|  | const DefaultNotification = require('./default-notification'); | ||||||
|  | const { appId, loadBots } = require('./helpers'); | ||||||
|  |  | ||||||
|  | // From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid | ||||||
|  | // On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work. | ||||||
|  | app.setAppUserModelId(appId); | ||||||
|  |  | ||||||
|  | window.Notification = DefaultNotification; | ||||||
|  |  | ||||||
|  | if (process.platform === 'darwin') { | ||||||
|  | 	const DarwinNotification = require('./darwin-notifications'); | ||||||
|  | 	window.Notification = DarwinNotification; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | window.addEventListener('load', () => { | ||||||
|  | 	// Call this function only when user is logged in | ||||||
|  | 	// eslint-disable-next-line no-undef, camelcase | ||||||
|  | 	if (page_params.realm_uri) { | ||||||
|  | 		loadBots(); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
| @@ -13,7 +13,7 @@ class CreateOrganziation extends BaseComponent { | |||||||
| 		return ` | 		return ` | ||||||
| 			<div class="setting-row"> | 			<div class="setting-row"> | ||||||
| 				<div class="setting-description"> | 				<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> | 					<span id="open-create-org-link">Or create a new organization on zulipchat.com<i class="material-icons open-tab-button">open_in_new</i></span> | ||||||
| 				</div> | 				</div> | ||||||
| 				<div class="setting-control"></div> | 				<div class="setting-control"></div> | ||||||
| 			</div> | 			</div> | ||||||
|   | |||||||
| @@ -1,11 +1,10 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| const path = require('path'); | const path = require('path'); | ||||||
|  | const { ipcRenderer, remote } = require('electron'); | ||||||
| const { ipcRenderer } = require('electron'); |  | ||||||
| const { app, dialog } = require('electron').remote; |  | ||||||
|  |  | ||||||
| const fs = require('fs-extra'); | const fs = require('fs-extra'); | ||||||
|  |  | ||||||
|  | const { app, dialog } = remote; | ||||||
|  | const currentBrowserWindow = remote.getCurrentWindow(); | ||||||
| const BaseSection = require(__dirname + '/base-section.js'); | const BaseSection = require(__dirname + '/base-section.js'); | ||||||
| const ConfigUtil = require(__dirname + '/../../utils/config-util.js'); | const ConfigUtil = require(__dirname + '/../../utils/config-util.js'); | ||||||
|  |  | ||||||
| @@ -61,10 +60,14 @@ class GeneralSection extends BaseSection { | |||||||
| 						<div class="setting-description">Start app at login</div> | 						<div class="setting-description">Start app at login</div> | ||||||
| 						<div class="setting-control"></div> | 						<div class="setting-control"></div> | ||||||
| 					</div> | 					</div> | ||||||
|  | 					<div class="setting-row" id="start-minimize-option"> | ||||||
|  | 						<div class="setting-description">Always start minimized</div> | ||||||
|  | 						<div class="setting-control"></div> | ||||||
|  | 					</div> | ||||||
| 					<div class="setting-row" id="enable-spellchecker-option"> | 					<div class="setting-row" id="enable-spellchecker-option"> | ||||||
| 					<div class="setting-description">Enable Spellchecker (requires restart)</div> | 						<div class="setting-description">Enable Spellchecker (requires restart)</div> | ||||||
| 					<div class="setting-control"></div> | 						<div class="setting-control"></div> | ||||||
| 				</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 				<div class="title">Reset Application Data</div> | 				<div class="title">Reset Application Data</div> | ||||||
|                 <div class="settings-card"> |                 <div class="settings-card"> | ||||||
| @@ -89,6 +92,7 @@ class GeneralSection extends BaseSection { | |||||||
| 		this.updateResetDataOption(); | 		this.updateResetDataOption(); | ||||||
| 		this.showDesktopNotification(); | 		this.showDesktopNotification(); | ||||||
| 		this.enableSpellchecker(); | 		this.enableSpellchecker(); | ||||||
|  | 		this.minimizeOnStart(); | ||||||
|  |  | ||||||
| 		// Platform specific settings | 		// Platform specific settings | ||||||
| 		// Flashing taskbar on Windows | 		// Flashing taskbar on Windows | ||||||
| @@ -155,6 +159,7 @@ class GeneralSection extends BaseSection { | |||||||
| 				const newValue = !ConfigUtil.getConfigItem('silent', true); | 				const newValue = !ConfigUtil.getConfigItem('silent', true); | ||||||
| 				ConfigUtil.setConfigItem('silent', newValue); | 				ConfigUtil.setConfigItem('silent', newValue); | ||||||
| 				this.updateSilentOption(); | 				this.updateSilentOption(); | ||||||
|  | 				currentBrowserWindow.send('toogle-silent', newValue); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| @@ -234,6 +239,18 @@ class GeneralSection extends BaseSection { | |||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	minimizeOnStart() { | ||||||
|  | 		this.generateSettingOption({ | ||||||
|  | 			$element: document.querySelector('#start-minimize-option .setting-control'), | ||||||
|  | 			value: ConfigUtil.getConfigItem('startMinimized', false), | ||||||
|  | 			clickHandler: () => { | ||||||
|  | 				const newValue = !ConfigUtil.getConfigItem('startMinimized'); | ||||||
|  | 				ConfigUtil.setConfigItem('startMinimized', newValue); | ||||||
|  | 				this.minimizeOnStart(); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = GeneralSection; | module.exports = GeneralSection; | ||||||
|   | |||||||
| @@ -13,12 +13,13 @@ class NewServerForm extends BaseComponent { | |||||||
| 		return ` | 		return ` | ||||||
| 			<div class="settings-card"> | 			<div class="settings-card"> | ||||||
| 				<div class="server-info-right"> | 				<div class="server-info-right"> | ||||||
|  | 					<div class="title">URL of Zulip organization</div> | ||||||
| 					<div class="server-info-row"> | 					<div class="server-info-row"> | ||||||
| 						<input class="setting-input-value" autofocus placeholder="Enter the URL of your Zulip organization..."/> | 						<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or chat.your-organization.com"/> | ||||||
| 					</div> | 					</div> | ||||||
| 					<div class="server-info-row"> | 					<div class="server-info-row"> | ||||||
| 						<div class="action blue server-save-action"> | 						<div class="action blue server-save-action"> | ||||||
| 							<i class="material-icons">check_box</i> | 							<i class="material-icons">add_box</i> | ||||||
| 							<span>Add</span> | 							<span>Add</span> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| @@ -42,11 +43,13 @@ class NewServerForm extends BaseComponent { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	submitFormHandler() { | 	submitFormHandler() { | ||||||
|  | 		this.$saveServerButton.children[1].innerHTML = 'Adding...'; | ||||||
| 		DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => { | 		DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => { | ||||||
| 			DomainUtil.addDomain(serverConf).then(() => { | 			DomainUtil.addDomain(serverConf).then(() => { | ||||||
| 				this.props.onChange(this.props.index); | 				this.props.onChange(this.props.index); | ||||||
| 			}); | 			}); | ||||||
| 		}, errorMessage => { | 		}, errorMessage => { | ||||||
|  | 			this.$saveServerButton.children[1].innerHTML = 'Add'; | ||||||
| 			alert(errorMessage); | 			alert(errorMessage); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -3,8 +3,6 @@ | |||||||
| const BaseComponent = require(__dirname + '/js/components/base.js'); | 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 Nav = require(__dirname + '/js/pages/preference/nav.js'); | ||||||
| const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js'); | const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js'); | ||||||
| const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js'); | const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js'); | ||||||
| @@ -27,7 +25,6 @@ class PreferenceView extends BaseComponent { | |||||||
|  |  | ||||||
| 		this.setDefaultView(); | 		this.setDefaultView(); | ||||||
| 		this.registerIpcs(); | 		this.registerIpcs(); | ||||||
| 		this.setDefaultSettings(); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	setDefaultView() { | 	setDefaultView() { | ||||||
| @@ -39,30 +36,6 @@ class PreferenceView extends BaseComponent { | |||||||
| 		this.handleNavigation(nav); | 		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) { | 	handleNavigation(navItem) { | ||||||
| 		this.nav.select(navItem); | 		this.nav.select(navItem); | ||||||
| 		switch (navItem) { | 		switch (navItem) { | ||||||
| @@ -96,10 +69,27 @@ class PreferenceView extends BaseComponent { | |||||||
| 		window.location.hash = `#${navItem}`; | 		window.location.hash = `#${navItem}`; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Handle toggling and reflect changes in preference page | ||||||
|  | 	handleToggle(elementName, state) { | ||||||
|  | 		const inputSelector = `#${elementName} .action .switch input`; | ||||||
|  | 		const input = document.querySelector(inputSelector); | ||||||
|  | 		if (input) { | ||||||
|  | 			input.checked = state; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	registerIpcs() { | 	registerIpcs() { | ||||||
| 		ipcRenderer.on('switch-settings-nav', (event, navItem) => { | 		ipcRenderer.on('switch-settings-nav', (event, navItem) => { | ||||||
| 			this.handleNavigation(navItem); | 			this.handleNavigation(navItem); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		ipcRenderer.on('toggle-sidebar', (event, state) => { | ||||||
|  | 			this.handleToggle('sidebar-option', state); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		ipcRenderer.on('toggletray', (event, state) => { | ||||||
|  | 			this.handleToggle('tray-option', state); | ||||||
|  | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class ServerInfoForm extends BaseComponent { | |||||||
| 				<div class="server-info-right"> | 				<div class="server-info-right"> | ||||||
| 					<div class="server-info-row"> | 					<div class="server-info-row"> | ||||||
| 						<span class="server-info-alias">${this.props.server.alias}</span> | 						<span class="server-info-alias">${this.props.server.alias}</span> | ||||||
| 						<i class="material-icons open-tab-button">open_in_new</i>						 | 						<i class="material-icons open-tab-button">open_in_new</i> | ||||||
| 					</div> | 					</div> | ||||||
| 					<div class="server-info-row"> | 					<div class="server-info-row"> | ||||||
| 						<input class="setting-input-value" disabled value="${this.props.server.url}"/> | 						<input class="setting-input-value" disabled value="${this.props.server.url}"/> | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ class ServersSection extends BaseSection { | |||||||
| 	template() { | 	template() { | ||||||
| 		return ` | 		return ` | ||||||
| 			<div class="settings-pane" id="server-settings-pane"> | 			<div class="settings-pane" id="server-settings-pane"> | ||||||
| 				<div class="title">Add Server</div> | 				<div class="page-title">Register or login to a Zulip organization to get started</div> | ||||||
| 				<div id="new-server-container"></div> | 				<div id="new-server-container"></div> | ||||||
| 				<div class="title" id="existing-servers"></div> | 				<div class="title" id="existing-servers"></div> | ||||||
| 				<div id="server-info-container"></div> | 				<div id="server-info-container"></div> | ||||||
| @@ -38,9 +38,9 @@ class ServersSection extends BaseSection { | |||||||
| 		this.$newServerContainer = document.getElementById('new-server-container'); | 		this.$newServerContainer = document.getElementById('new-server-container'); | ||||||
| 		this.$newServerButton = document.getElementById('new-server-action'); | 		this.$newServerButton = document.getElementById('new-server-action'); | ||||||
|  |  | ||||||
| 		this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!'; | 		this.$serverInfoContainer.innerHTML = servers.length ? '' : ''; | ||||||
| 		// Show Existing servers if servers are there otherwise hide it | 		// 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 organizations'; | ||||||
| 		this.initNewServerForm(); | 		this.initNewServerForm(); | ||||||
|  |  | ||||||
| 		this.$createOrganizationContainer = document.getElementById('create-organization-container'); | 		this.$createOrganizationContainer = document.getElementById('create-organization-container'); | ||||||
|   | |||||||
| @@ -109,7 +109,7 @@ class ShortcutsSection extends BaseSection { | |||||||
|                     <td>Enter Full Screen</td> |                     <td>Enter Full Screen</td> | ||||||
|                   </tr> |                   </tr> | ||||||
|                   <tr> |                   <tr> | ||||||
|                     <td><kbd>${userOSKey}</kbd><kbd>=</kbd></td> |                     <td><kbd>${userOSKey}</kbd><kbd>+</kbd></td> | ||||||
|                     <td>Zoom In</td> |                     <td>Zoom In</td> | ||||||
|                   </tr> |                   </tr> | ||||||
|                   <tr> |                   <tr> | ||||||
|   | |||||||
| @@ -197,13 +197,16 @@ ipcRenderer.on('tray', (event, arg) => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| function toggleTray() { | function toggleTray() { | ||||||
|  | 	let state; | ||||||
| 	if (window.tray) { | 	if (window.tray) { | ||||||
|  | 		state = false; | ||||||
| 		window.tray.destroy(); | 		window.tray.destroy(); | ||||||
| 		if (window.tray.isDestroyed()) { | 		if (window.tray.isDestroyed()) { | ||||||
| 			window.tray = null; | 			window.tray = null; | ||||||
| 		} | 		} | ||||||
| 		ConfigUtil.setConfigItem('trayIcon', false); | 		ConfigUtil.setConfigItem('trayIcon', false); | ||||||
| 	} else { | 	} else { | ||||||
|  | 		state = true; | ||||||
| 		createTray(); | 		createTray(); | ||||||
| 		if (process.platform === 'linux' || process.platform === 'win32') { | 		if (process.platform === 'linux' || process.platform === 'win32') { | ||||||
| 			renderNativeImage(unread).then(image => { | 			renderNativeImage(unread).then(image => { | ||||||
| @@ -213,6 +216,10 @@ function toggleTray() { | |||||||
| 		} | 		} | ||||||
| 		ConfigUtil.setConfigItem('trayIcon', true); | 		ConfigUtil.setConfigItem('trayIcon', true); | ||||||
| 	} | 	} | ||||||
|  | 	const selector = 'webview:not([class*=disabled])'; | ||||||
|  | 	const webview = document.querySelector(selector); | ||||||
|  | 	const webContents = webview.getWebContents(); | ||||||
|  | 	webContents.send('toggletray', state); | ||||||
| } | } | ||||||
|  |  | ||||||
| ipcRenderer.on('toggletray', toggleTray); | ipcRenderer.on('toggletray', toggleTray); | ||||||
|   | |||||||
| @@ -1,16 +1,29 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
|  | const fs = require('fs'); | ||||||
|  | const path = require('path'); | ||||||
| const process = require('process'); | const process = require('process'); | ||||||
| const JsonDB = require('node-json-db'); | const JsonDB = require('node-json-db'); | ||||||
|  | const Logger = require('./logger-util'); | ||||||
|  |  | ||||||
|  | const logger = new Logger({ | ||||||
|  | 	file: 'config-util.log', | ||||||
|  | 	timestamp: true | ||||||
|  | }); | ||||||
|  |  | ||||||
| let instance = null; | let instance = null; | ||||||
|  | let dialog = null; | ||||||
| let app = null; | let app = null; | ||||||
|  |  | ||||||
| /* To make the util runnable in both main and renderer process */ | /* To make the util runnable in both main and renderer process */ | ||||||
| if (process.type === 'renderer') { | if (process.type === 'renderer') { | ||||||
| 	app = require('electron').remote.app; | 	const remote = require('electron').remote; | ||||||
|  | 	dialog = remote.dialog; | ||||||
|  | 	app = remote.app; | ||||||
| } else { | } else { | ||||||
| 	app = require('electron').app; | 	const electron = require('electron'); | ||||||
|  | 	dialog = electron.dialog; | ||||||
|  | 	app = electron.app; | ||||||
| } | } | ||||||
|  |  | ||||||
| class ConfigUtil { | class ConfigUtil { | ||||||
| @@ -47,7 +60,22 @@ class ConfigUtil { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	reloadDB() { | 	reloadDB() { | ||||||
| 		this.db = new JsonDB(app.getPath('userData') + '/settings.json', true, true); | 		const settingsJsonPath = path.join(app.getPath('userData'), '/settings.json'); | ||||||
|  | 		try { | ||||||
|  | 			const file = fs.readFileSync(settingsJsonPath, 'utf8'); | ||||||
|  | 			JSON.parse(file); | ||||||
|  | 		} catch (err) { | ||||||
|  | 			if (fs.existsSync(settingsJsonPath)) { | ||||||
|  | 				fs.unlinkSync(settingsJsonPath); | ||||||
|  | 				dialog.showErrorBox( | ||||||
|  | 					'Error saving settings', | ||||||
|  | 					'We encountered error while saving current settings.' | ||||||
|  | 				); | ||||||
|  | 				logger.error('Error while JSON parsing settings.json: '); | ||||||
|  | 				logger.error(err); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		this.db = new JsonDB(settingsJsonPath, true, true); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								app/renderer/js/utils/default-util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/renderer/js/utils/default-util.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | const fs = require('fs'); | ||||||
|  |  | ||||||
|  | let app = null; | ||||||
|  | let setupCompleted = false; | ||||||
|  | if (process.type === 'renderer') { | ||||||
|  | 	app = require('electron').remote.app; | ||||||
|  | } else { | ||||||
|  | 	app = require('electron').app; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const zulipDir = app.getPath('userData'); | ||||||
|  | const logDir = `${zulipDir}/Logs/`; | ||||||
|  | const initSetUp = () => { | ||||||
|  | 	// if it is the first time the app is running | ||||||
|  | 	// create zulip dir in userData folder to | ||||||
|  | 	// avoid errors | ||||||
|  | 	if (!setupCompleted) { | ||||||
|  | 		if (!fs.existsSync(zulipDir)) { | ||||||
|  | 			fs.mkdirSync(zulipDir); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!fs.existsSync(logDir)) { | ||||||
|  | 			fs.mkdirSync(logDir); | ||||||
|  | 		} | ||||||
|  | 		setupCompleted = true; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  | 	initSetUp | ||||||
|  | }; | ||||||
| @@ -5,6 +5,12 @@ const fs = require('fs'); | |||||||
| const path = require('path'); | const path = require('path'); | ||||||
| const JsonDB = require('node-json-db'); | const JsonDB = require('node-json-db'); | ||||||
| const request = require('request'); | const request = require('request'); | ||||||
|  | const Logger = require('./logger-util'); | ||||||
|  |  | ||||||
|  | const logger = new Logger({ | ||||||
|  | 	file: `domain-util.log`, | ||||||
|  | 	timestamp: true | ||||||
|  | }); | ||||||
|  |  | ||||||
| let instance = null; | let instance = null; | ||||||
|  |  | ||||||
| @@ -93,8 +99,7 @@ class DomainUtil { | |||||||
| 	checkDomain(domain, silent = false) { | 	checkDomain(domain, silent = false) { | ||||||
| 		if (!silent && this.duplicateDomain(domain)) { | 		if (!silent && this.duplicateDomain(domain)) { | ||||||
| 			// Do not check duplicate in silent mode | 			// Do not check duplicate in silent mode | ||||||
| 			alert('This server has been added.'); | 			return Promise.reject('This server has been added.'); | ||||||
| 			return; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		domain = this.formatUrl(domain); | 		domain = this.formatUrl(domain); | ||||||
| @@ -115,14 +120,23 @@ class DomainUtil { | |||||||
| 						'Error: unable to verify the first certificate', | 						'Error: unable to verify the first certificate', | ||||||
| 						'Error: unable to get local issuer certificate' | 						'Error: unable to get local issuer certificate' | ||||||
| 					]; | 					]; | ||||||
| 				if (!error && response.statusCode !== 404) { |  | ||||||
|  | 				// If the domain contains following strings we just bypass the server | ||||||
|  | 				const whitelistDomains = [ | ||||||
|  | 					'zulipdev.org' | ||||||
|  | 				]; | ||||||
|  |  | ||||||
|  | 				// make sure that error is a error or string not undefined | ||||||
|  | 				// so validation does not throw error. | ||||||
|  | 				error = error || ''; | ||||||
|  | 				if (!error && response.statusCode < 400) { | ||||||
| 					// Correct | 					// Correct | ||||||
| 					this.getServerSettings(domain).then(serverSettings => { | 					this.getServerSettings(domain).then(serverSettings => { | ||||||
| 						resolve(serverSettings); | 						resolve(serverSettings); | ||||||
| 					}, () => { | 					}, () => { | ||||||
| 						resolve(serverConf); | 						resolve(serverConf); | ||||||
| 					}); | 					}); | ||||||
| 				} else if (certsError.indexOf(error.toString()) >= 0) { | 				} else if (domain.indexOf(whitelistDomains) >= 0 || certsError.indexOf(error.toString()) >= 0) { | ||||||
| 					if (silent) { | 					if (silent) { | ||||||
| 						this.getServerSettings(domain).then(serverSettings => { | 						this.getServerSettings(domain).then(serverSettings => { | ||||||
| 							resolve(serverSettings); | 							resolve(serverSettings); | ||||||
| @@ -155,7 +169,7 @@ class DomainUtil { | |||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \ | 					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`; | 					\n(1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server`; | ||||||
| 					reject(invalidZulipServerError); | 					reject(invalidZulipServerError); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
| @@ -221,7 +235,23 @@ class DomainUtil { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	reloadDB() { | 	reloadDB() { | ||||||
| 		this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); | 		const domainJsonPath = path.join(app.getPath('userData'), '/domain.json'); | ||||||
|  | 		try { | ||||||
|  | 			const file = fs.readFileSync(domainJsonPath, 'utf8'); | ||||||
|  | 			JSON.parse(file); | ||||||
|  | 		} catch (err) { | ||||||
|  | 			if (fs.existsSync(domainJsonPath)) { | ||||||
|  | 				fs.unlinkSync(domainJsonPath); | ||||||
|  | 				dialog.showErrorBox( | ||||||
|  | 					'Error saving new organization', | ||||||
|  | 					'There seems to be error while saving new organization, ' + | ||||||
|  | 					'you may have to re-add your previous organizations back.' | ||||||
|  | 				); | ||||||
|  | 				logger.error('Error while JSON parsing domain.json: '); | ||||||
|  | 				logger.error(err); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		this.db = new JsonDB(domainJsonPath, true, true); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	generateFilePath(url) { | 	generateFilePath(url) { | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								app/renderer/js/utils/linux-update-util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								app/renderer/js/utils/linux-update-util.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const fs = require('fs'); | ||||||
|  | const path = require('path'); | ||||||
|  | const process = require('process'); | ||||||
|  | const remote = | ||||||
|  | 	process.type === 'renderer' ? require('electron').remote : require('electron'); | ||||||
|  | const JsonDB = require('node-json-db'); | ||||||
|  | const Logger = require('./logger-util'); | ||||||
|  |  | ||||||
|  | const logger = new Logger({ | ||||||
|  | 	file: 'linux-update-util.log', | ||||||
|  | 	timestamp: true | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /* To make the util runnable in both main and renderer process */ | ||||||
|  | const { dialog, app } = remote; | ||||||
|  |  | ||||||
|  | let instance = null; | ||||||
|  |  | ||||||
|  | class LinuxUpdateUtil { | ||||||
|  | 	constructor() { | ||||||
|  | 		if (instance) { | ||||||
|  | 			return instance; | ||||||
|  | 		} else { | ||||||
|  | 			instance = this; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		this.reloadDB(); | ||||||
|  | 		return instance; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	getUpdateItem(key, defaultValue = null) { | ||||||
|  | 		this.reloadDB(); | ||||||
|  | 		const value = this.db.getData('/')[key]; | ||||||
|  | 		if (value === undefined) { | ||||||
|  | 			this.setUpdateItem(key, defaultValue); | ||||||
|  | 			return defaultValue; | ||||||
|  | 		} else { | ||||||
|  | 			return value; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	setUpdateItem(key, value) { | ||||||
|  | 		this.db.push(`/${key}`, value, true); | ||||||
|  | 		this.reloadDB(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	removeUpdateItem(key) { | ||||||
|  | 		this.db.delete(`/${key}`); | ||||||
|  | 		this.reloadDB(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	reloadDB() { | ||||||
|  | 		const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/updates.json'); | ||||||
|  | 		try { | ||||||
|  | 			const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8'); | ||||||
|  | 			JSON.parse(file); | ||||||
|  | 		} catch (err) { | ||||||
|  | 			if (fs.existsSync(linuxUpdateJsonPath)) { | ||||||
|  | 				fs.unlinkSync(linuxUpdateJsonPath); | ||||||
|  | 				dialog.showErrorBox( | ||||||
|  | 					'Error saving update notifications.', | ||||||
|  | 					'We encountered error while saving update notifications.' | ||||||
|  | 				); | ||||||
|  | 				logger.error('Error while JSON parsing updates.json: '); | ||||||
|  | 				logger.error(err); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		this.db = new JsonDB(linuxUpdateJsonPath, true, true); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = new LinuxUpdateUtil(); | ||||||
							
								
								
									
										87
									
								
								app/renderer/js/utils/logger-util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								app/renderer/js/utils/logger-util.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | const NodeConsole = require('console').Console; | ||||||
|  | const fs = require('fs'); | ||||||
|  | const isDev = require('electron-is-dev'); | ||||||
|  | const { initSetUp } = require('./default-util'); | ||||||
|  |  | ||||||
|  | initSetUp(); | ||||||
|  | let app = null; | ||||||
|  | if (process.type === 'renderer') { | ||||||
|  | 	app = require('electron').remote.app; | ||||||
|  | } else { | ||||||
|  | 	app = require('electron').app; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const browserConsole = console; | ||||||
|  | const logDir = `${app.getPath('userData')}/Logs`; | ||||||
|  |  | ||||||
|  | class Logger { | ||||||
|  | 	constructor(opts = {}) { | ||||||
|  | 		let { | ||||||
|  | 			timestamp = true, | ||||||
|  | 			file = 'console.log', | ||||||
|  | 			level = true, | ||||||
|  | 			logInDevMode = false | ||||||
|  | 		} = opts; | ||||||
|  |  | ||||||
|  | 		file = `${logDir}/${file}`; | ||||||
|  | 		if (timestamp === true) { | ||||||
|  | 			timestamp = this.getTimestamp; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const fileStream = fs.createWriteStream(file, { flags: 'a' }); | ||||||
|  | 		const nodeConsole = new NodeConsole(fileStream); | ||||||
|  |  | ||||||
|  | 		this.nodeConsole = nodeConsole; | ||||||
|  | 		this.timestamp = timestamp; | ||||||
|  | 		this.level = level; | ||||||
|  | 		this.logInDevMode = logInDevMode; | ||||||
|  | 		this.setUpConsole(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_log(type, ...args) { | ||||||
|  | 		const { | ||||||
|  | 			nodeConsole, timestamp, level, logInDevMode | ||||||
|  | 		} = this; | ||||||
|  | 		let nodeConsoleLog; | ||||||
|  |  | ||||||
|  | 		/* eslint-disable no-fallthrough */ | ||||||
|  | 		switch (true) { | ||||||
|  | 			case typeof timestamp === 'function': | ||||||
|  | 				args.unshift(timestamp() + ' |\t'); | ||||||
|  |  | ||||||
|  | 			case (level !== false): | ||||||
|  | 				args.unshift(type.toUpperCase() + ' |'); | ||||||
|  |  | ||||||
|  | 			case isDev || logInDevMode: | ||||||
|  | 				nodeConsoleLog = nodeConsole[type] || nodeConsole.log; | ||||||
|  | 				nodeConsoleLog.apply(null, args); | ||||||
|  |  | ||||||
|  | 			default: break; | ||||||
|  | 		} | ||||||
|  | 		/* eslint-enable no-fallthrough */ | ||||||
|  |  | ||||||
|  | 		browserConsole[type].apply(null, args); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	setUpConsole() { | ||||||
|  | 		for (const type in browserConsole) { | ||||||
|  | 			this.setupConsoleMethod(type); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	setupConsoleMethod(type) { | ||||||
|  | 		this[type] = (...args) => { | ||||||
|  | 			this._log(type, ...args); | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	getTimestamp() { | ||||||
|  | 		const date = new Date(); | ||||||
|  | 		const timestamp = | ||||||
|  | 			`${date.getMonth()}/${date.getDate()} ` + | ||||||
|  | 			`${date.getMinutes()}:${date.getSeconds()}`; | ||||||
|  | 		return timestamp; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = Logger; | ||||||
							
								
								
									
										52
									
								
								app/renderer/js/utils/reconnect-util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								app/renderer/js/utils/reconnect-util.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | const isOnline = require('is-online'); | ||||||
|  |  | ||||||
|  | class ReconnectUtil { | ||||||
|  | 	constructor(serverManagerView) { | ||||||
|  | 		this.serverManagerView = serverManagerView; | ||||||
|  | 		this.alreadyReloaded = false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	clearState() { | ||||||
|  | 		this.alreadyReloaded = false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pollInternetAndReload() { | ||||||
|  | 		const pollInterval = setInterval(() => { | ||||||
|  | 			this._checkAndReload() | ||||||
|  | 			.then(status => { | ||||||
|  | 				if (status) { | ||||||
|  | 					this.alreadyReloaded = true; | ||||||
|  | 					clearInterval(pollInterval); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}, 1500); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_checkAndReload() { | ||||||
|  | 		return new Promise(resolve => { | ||||||
|  | 			if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition | ||||||
|  | 				isOnline() | ||||||
|  | 				.then(online => { | ||||||
|  | 					if (online) { | ||||||
|  | 						if (!this.alreadyReloaded) { | ||||||
|  | 							this.serverManagerView.reloadView(); | ||||||
|  | 						} | ||||||
|  | 						console.log('You\'re back online.'); | ||||||
|  | 						return resolve(true); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					console.log('There is no internet connection, try checking network cables, modem and router.'); | ||||||
|  | 					const errMsgHolder = document.querySelector('#description'); | ||||||
|  | 					errMsgHolder.innerHTML = ` | ||||||
|  | 										<div>You internet connection does't seem to work properly!</div> | ||||||
|  | 										</div>Verify that it works and then click try again.</div>`; | ||||||
|  | 					return resolve(false); | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				return resolve(true); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = ReconnectUtil; | ||||||
| @@ -28,6 +28,10 @@ | |||||||
|         <i class="material-icons md-48">refresh</i> |         <i class="material-icons md-48">refresh</i> | ||||||
|         <span id="reload-tooltip" style="display:none">Reload</span> |         <span id="reload-tooltip" style="display:none">Reload</span> | ||||||
|       </div> |       </div> | ||||||
|  |       <div class="action-button disable" id="back-action"> | ||||||
|  |         <i class="material-icons md-48">arrow_back</i> | ||||||
|  |         <span id="back-tooltip" style="display:none">Go Back</span> | ||||||
|  |       </div> | ||||||
|       <div class="action-button" id="settings-action"> |       <div class="action-button" id="settings-action"> | ||||||
|         <i class="material-icons md-48">settings</i> |         <i class="material-icons md-48">settings</i> | ||||||
|         <span id="setting-tooltip" style="display:none">Settings</span> |         <span id="setting-tooltip" style="display:none">Settings</span> | ||||||
| @@ -40,5 +44,4 @@ | |||||||
|   </div> |   </div> | ||||||
| </body> | </body> | ||||||
| <script src="js/main.js"></script> | <script src="js/main.js"></script> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
| @@ -12,5 +12,5 @@ | |||||||
|       <div id="settings-container"></div> |       <div id="settings-container"></div> | ||||||
|     </div> |     </div> | ||||||
|   </body> |   </body> | ||||||
|   <script src="js/pages/preference/preference.js"></script>   |   <script src="js/pages/preference/preference.js"></script> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -17,7 +17,8 @@ install: | |||||||
|   - npm install |   - npm install | ||||||
|   - npm install -g gulp |   - npm install -g gulp | ||||||
|  |  | ||||||
| build: off   | build: off | ||||||
|  |  | ||||||
| test_script: | test_script: | ||||||
|   - npm run test |   - npm run test | ||||||
|  |   - npm run test-e2e | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								gulpfile.js
									
									
									
									
									
								
							| @@ -1,9 +1,10 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| const gulp = require('gulp'); | const gulp = require('gulp'); | ||||||
| const mocha = require('gulp-mocha'); |  | ||||||
| const electron = require('electron-connect').server.create({ | const electron = require('electron-connect').server.create({ | ||||||
| 	verbose: true | 	verbose: true | ||||||
| }); | }); | ||||||
|  | const tape = require('gulp-tape'); | ||||||
|  | const tapColorize = require('tap-colorize'); | ||||||
|  |  | ||||||
| gulp.task('dev', () => { | gulp.task('dev', () => { | ||||||
|   // Start browser process |   // Start browser process | ||||||
| @@ -13,7 +14,7 @@ gulp.task('dev', () => { | |||||||
|   // Reload renderer process |   // Reload renderer process | ||||||
| 	gulp.watch('app/renderer/css/*.css', ['reload:renderer']); | 	gulp.watch('app/renderer/css/*.css', ['reload:renderer']); | ||||||
| 	gulp.watch('app/renderer/*.html', ['reload:renderer']); | 	gulp.watch('app/renderer/*.html', ['reload:renderer']); | ||||||
| 	gulp.watch('app/renderer/js/*.js', ['reload:renderer']); | 	gulp.watch('app/renderer/js/**/*.js', ['reload:renderer']); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| gulp.task('restart:browser', done => { | gulp.task('restart:browser', done => { | ||||||
| @@ -28,9 +29,11 @@ gulp.task('reload:renderer', done => { | |||||||
| 	done(); | 	done(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // Test app using mocha+spectron | gulp.task('test-e2e', () => { | ||||||
| gulp.task('test', () => { | 	return gulp.src('tests/*.js') | ||||||
| 	return gulp.src('tests/index.js').pipe(mocha()); | 	.pipe(tape({ | ||||||
|  | 		reporter: tapColorize() | ||||||
|  | 	})); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| gulp.task('default', ['dev', 'test']); | gulp.task('default', ['dev', 'test-e2e']); | ||||||
|   | |||||||
							
								
								
									
										9854
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9854
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										46
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,15 +1,14 @@ | |||||||
| { | { | ||||||
|   "name": "zulip", |   "name": "zulip", | ||||||
|   "productName": "Zulip", |   "productName": "Zulip", | ||||||
|   "version": "1.6.0-beta", |   "version": "1.8.2", | ||||||
|   "main": "./app/main", |   "main": "./app/main", | ||||||
|   "description": "Zulip Desktop App", |   "description": "Zulip Desktop App", | ||||||
|   "license": "Apache-2.0", |   "license": "Apache-2.0", | ||||||
|   "email": "<svnitakash@gmail.com>", |   "copyright": "Kandra Labs, Inc.", | ||||||
|   "copyright": "©2017 Kandra Labs, Inc.", |  | ||||||
|   "author": { |   "author": { | ||||||
|     "name": "Kandra Labs, Inc.", |     "name": "Kandra Labs, Inc.", | ||||||
|     "email": "svnitakash@gmail.com" |     "email": "support@zulipchat.com" | ||||||
|   }, |   }, | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "git", |     "type": "git", | ||||||
| @@ -18,12 +17,16 @@ | |||||||
|   "bugs": { |   "bugs": { | ||||||
|     "url": "https://github.com/zulip/zulip-electron/issues" |     "url": "https://github.com/zulip/zulip-electron/issues" | ||||||
|   }, |   }, | ||||||
|  |   "engines": { | ||||||
|  |     "node": ">=8.0.0" | ||||||
|  |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "electron app --disable-http-cache", |     "start": "electron app --disable-http-cache --no-electron-connect", | ||||||
|     "reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install", |     "reinstall": "./tools/reinstall-node-modules", | ||||||
|     "postinstall": "electron-builder install-app-deps", |     "postinstall": "electron-builder install-app-deps", | ||||||
|     "test": "xo", |     "test": "xo", | ||||||
|     "dev": "gulp dev", |     "test-e2e": "gulp test-e2e", | ||||||
|  |     "dev": "gulp dev & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js", | ||||||
|     "pack": "electron-builder --dir", |     "pack": "electron-builder --dir", | ||||||
|     "dist": "electron-builder", |     "dist": "electron-builder", | ||||||
|     "mas": "electron-builder --mac mas", |     "mas": "electron-builder --mac mas", | ||||||
| @@ -56,7 +59,9 @@ | |||||||
|       "maintainer": "Akash Nimare <svnitakash@gmail.com>" |       "maintainer": "Akash Nimare <svnitakash@gmail.com>" | ||||||
|     }, |     }, | ||||||
|     "deb": { |     "deb": { | ||||||
|       "synopsis": "Zulip Desktop App" |       "synopsis": "Zulip Desktop App", | ||||||
|  |       "afterInstall": "./scripts/debian-add-repo.sh", | ||||||
|  |       "afterRemove": "./scripts/debian-uninstaller.sh" | ||||||
|     }, |     }, | ||||||
|     "dmg": { |     "dmg": { | ||||||
|       "background": "build/appdmg.png", |       "background": "build/appdmg.png", | ||||||
| @@ -105,18 +110,21 @@ | |||||||
|   ], |   ], | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "assert": "1.4.1", |     "assert": "1.4.1", | ||||||
|  |     "cp-file": "^5.0.0", | ||||||
|     "devtron": "1.4.0", |     "devtron": "1.4.0", | ||||||
|     "electron-builder": "19.45.5", |     "electron": "1.8.2", | ||||||
|     "electron": "1.6.14", |     "electron-builder": "19.53.6", | ||||||
|     "electron-connect": "0.6.2", |     "electron-connect": "0.6.2", | ||||||
|  |     "electron-debug": "1.4.0", | ||||||
|     "gulp": "3.9.1", |     "gulp": "3.9.1", | ||||||
|     "gulp-mocha": "4.3.1", |     "gulp-tape": "0.0.9", | ||||||
|     "chai-as-promised": "7.1.1", |     "is-ci": "^1.0.10", | ||||||
|     "chai": "4.1.1", |     "nodemon": "^1.14.11", | ||||||
|     "spectron": "3.7.2", |  | ||||||
|     "xo": "0.18.2", |  | ||||||
|     "pre-commit": "1.2.2", |     "pre-commit": "1.2.2", | ||||||
|     "electron-debug": "1.4.0" |     "spectron": "3.7.2", | ||||||
|  |     "tap-colorize": "^1.2.0", | ||||||
|  |     "tape": "^4.8.0", | ||||||
|  |     "xo": "0.18.2" | ||||||
|   }, |   }, | ||||||
|   "xo": { |   "xo": { | ||||||
|     "parserOptions": { |     "parserOptions": { | ||||||
| @@ -132,7 +140,11 @@ | |||||||
|         "rules": { |         "rules": { | ||||||
|           "max-lines": [ |           "max-lines": [ | ||||||
|             "warn", |             "warn", | ||||||
|             500 |             { | ||||||
|  |               "max": 500, | ||||||
|  |               "skipBlankLines": true, | ||||||
|  |               "skipComments": true | ||||||
|  |             } | ||||||
|           ], |           ], | ||||||
|           "no-warning-comments": 0, |           "no-warning-comments": 0, | ||||||
|           "object-curly-spacing": 0, |           "object-curly-spacing": 0, | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								scripts/debian-add-repo.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								scripts/debian-add-repo.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # This script runs when user install the debian package | ||||||
|  |  | ||||||
|  | # Install apt repository source list if it does not exist | ||||||
|  | if ! grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then | ||||||
|  |     sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9 | ||||||
|  |     echo "deb https://dl.bintray.com/zulip/debian/ stable main" | \ | ||||||
|  |     sudo tee -a /etc/apt/sources.list.d/zulip.list; | ||||||
|  | fi | ||||||
							
								
								
									
										31
									
								
								scripts/debian-uninstaller.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								scripts/debian-uninstaller.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # This script runs when user uninstall the debian package. | ||||||
|  | # It will remove all the config files and anything which was added by the app. | ||||||
|  |  | ||||||
|  | # Remove apt repository source list when user uninstalls Zulip app | ||||||
|  | if grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then | ||||||
|  | 	sudo apt-key del 69AD12704E71A4803DCA3A682424BE5AE9BD10D9; | ||||||
|  | 	sudo rm /etc/apt/sources.list.d/zulip.list; | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # Get the root user | ||||||
|  | if [ $SUDO_USER ]; | ||||||
|  | 	then getSudoUser=$SUDO_USER; | ||||||
|  | 	else getSudoUser=`whoami`; | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # Get the path for Zulip's desktop entry which is created by auto-launch script | ||||||
|  | getDesktopEntry=/home/$getSudoUser/.config/autostart/zulip.desktop; | ||||||
|  |  | ||||||
|  | # Remove desktop entry if exists | ||||||
|  | if [ -f $getDesktopEntry ]; then | ||||||
|  |     sudo rm $getDesktopEntry; | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # App directory which contains all the config, setting files | ||||||
|  | appDirectory=/home/$getSudoUser/.config/Zulip/; | ||||||
|  |  | ||||||
|  | if [ -d $appDirectory ]; then | ||||||
|  |     sudo rm -rf $appDirectory; | ||||||
|  | fi | ||||||
| @@ -1,10 +1,20 @@ | |||||||
| #!/usr/bin/env bash | #!/usr/bin/env bash | ||||||
|  |  | ||||||
|  | # exit script if fails | ||||||
|  | set -e; | ||||||
|  |  | ||||||
| if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then | ||||||
|   export {no_proxy,NO_PROXY}="127.0.0.1,localhost" |     export {no_proxy,NO_PROXY}="127.0.0.1,localhost" | ||||||
| 	export DISPLAY=:99.0 |     export DISPLAY=:99.0 | ||||||
|   sh -e /etc/init.d/xvfb start |     sh -e /etc/init.d/xvfb start | ||||||
|   sleep 3 |     sleep 3 | ||||||
|  |  | ||||||
|  |     echo 'Travis Screen Resolution:' | ||||||
|  |     xdpyinfo | grep dimensions | ||||||
| fi | fi | ||||||
|  |  | ||||||
| npm run test | npm run test | ||||||
|  |  | ||||||
|  | if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then | ||||||
|  |     npm run test-e2e | ||||||
|  | fi | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								scripts/travis-xvfb.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5
									
								
								scripts/travis-xvfb.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  |  | ||||||
|  | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then | ||||||
|  |     /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 | ||||||
|  | fi | ||||||
							
								
								
									
										7
									
								
								tests/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | const path = require('path') | ||||||
|  |  | ||||||
|  | const TEST_APP_PRODUCT_NAME = 'ZulipTest' | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   TEST_APP_PRODUCT_NAME | ||||||
|  | } | ||||||
| @@ -1,81 +1,13 @@ | |||||||
| const assert = require('assert') | const test = require('tape') | ||||||
| const Application = require('spectron').Application | const setup = require('./setup') | ||||||
| const chai = require('chai') |  | ||||||
| const { expect } = chai |  | ||||||
| const chaiAsPromised = require('chai-as-promised') |  | ||||||
|  |  | ||||||
| chai.should() |  | ||||||
| chai.use(chaiAsPromised) |  | ||||||
|  |  | ||||||
| describe('application launch', function () { |  | ||||||
|   this.timeout(15000) |  | ||||||
|  |  | ||||||
|   beforeEach(function () { |  | ||||||
|     this.app = new Application({ |  | ||||||
|       path: require('electron'), |  | ||||||
|       args: [__dirname + '/../app/renderer/main.html'] |  | ||||||
|     }) |  | ||||||
|     return this.app.start() |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   beforeEach(function () { |  | ||||||
|     chaiAsPromised.transferPromiseness = this.app.transferPromiseness |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   afterEach(function () { |  | ||||||
|     if (this.app && this.app.isRunning()) { |  | ||||||
|       return this.app.stop() |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   it('shows an initial window', function () { |  | ||||||
|      return this.app.client.waitUntilWindowLoaded(5000) |  | ||||||
|       .getWindowCount().should.eventually.equal(2) |  | ||||||
|       .browserWindow.isMinimized().should.eventually.be.false |  | ||||||
|       .browserWindow.isDevToolsOpened().should.eventually.be.false |  | ||||||
|       .browserWindow.isVisible().should.eventually.be.true |  | ||||||
|       .browserWindow.isFocused().should.eventually.be.true |  | ||||||
|       .browserWindow.getBounds().should.eventually.have.property('width').and.be.above(0) |  | ||||||
|       .browserWindow.getBounds().should.eventually.have.property('height').and.be.above(0) |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
| 	it('sets up a default organization', function () { |  | ||||||
| 		let app = this.app |  | ||||||
| 		let self = this |  | ||||||
| 		app.client.execute(() => { |  | ||||||
| 			window.confirm = function () { return true } |  | ||||||
| 		}) |  | ||||||
|  |  | ||||||
| 		function createOrg (client, name, url, winIndex) { |  | ||||||
| 			return client |  | ||||||
| 				// Focus on settings webview |  | ||||||
| 				.then(switchToWebviewAtIndex.bind(null, self.app.client, winIndex)) |  | ||||||
| 				.pause(1000) // wait for settings to load |  | ||||||
|  |  | ||||||
| 				// Fill settings form |  | ||||||
| 				.click('#new-server-action') |  | ||||||
| 				.setValue('input[id="server-info-name"]', name) |  | ||||||
| 				.setValue('input[id="server-info-url"]', url) |  | ||||||
| 				.click('#save-server-action') |  | ||||||
| 				.pause(500) // Need to pause while server verification takes place |  | ||||||
| 				.then(() =>  app.browserWindow.reload()) |  | ||||||
| 			  .pause(1500) // Wait for webview of org to load |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		function switchToWebviewAtIndex(client, index) { |  | ||||||
| 			return client |  | ||||||
| 			.windowHandles() |  | ||||||
| 			.then(function (session) { |  | ||||||
| 				this.window(session.value[index]) |  | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return this.app.client.waitUntilWindowLoaded(5000) |  | ||||||
| 			.then(() => createOrg(self.app.client, 'Zulip 1', 'chat.zulip.org', 1)) |  | ||||||
| 			.then(switchToWebviewAtIndex.bind(null, self.app.client, 0)) |  | ||||||
| 			.click('#add-action > i').pause(500) |  | ||||||
| 			.then(switchToWebviewAtIndex.bind(null, self.app.client, 2)) |  | ||||||
| 			.then(() => createOrg(self.app.client, 'Zulip 2', 'chat.zulip.org', 2)) |  | ||||||
| 	}) |  | ||||||
| }) |  | ||||||
|  |  | ||||||
|  | test('app runs', function (t) { | ||||||
|  |   t.timeoutAfter(10e3) | ||||||
|  |   setup.resetTestDataDir() | ||||||
|  |   const app = setup.createApp() | ||||||
|  |   setup.waitForLoad(app, t) | ||||||
|  |     .then(() => app.client.windowByIndex(1)) // focus on webview | ||||||
|  |     .then(() => app.client.waitForExist('//*[@id="new-server-container"]/div/div/div[2]/input')) | ||||||
|  |     .then(() => setup.endTest(app, t), | ||||||
|  |           (err) => setup.endTest(app, t, err || 'error')) | ||||||
|  | }) | ||||||
							
								
								
									
										99
									
								
								tests/setup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								tests/setup.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | const Application = require('spectron').Application | ||||||
|  | const cpFile = require('cp-file') | ||||||
|  | const fs = require('fs') | ||||||
|  | const isCI = require('is-ci') | ||||||
|  | const mkdirp = require('mkdirp') | ||||||
|  | const path = require('path') | ||||||
|  | const rimraf = require('rimraf') | ||||||
|  |  | ||||||
|  | const config = require('./config') | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |   createApp, | ||||||
|  |   endTest, | ||||||
|  |   waitForLoad, | ||||||
|  |   wait, | ||||||
|  |   resetTestDataDir | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Runs Zulip Desktop. | ||||||
|  | // Returns a promise that resolves to a Spectron Application once the app has loaded. | ||||||
|  | // Takes a Tape test. Makes some basic assertions to verify that the app loaded correctly. | ||||||
|  | function createApp (t) { | ||||||
|  |   generateTestAppPackageJson() | ||||||
|  |   return new Application({ | ||||||
|  |     path: path.join(__dirname, '..', 'node_modules', '.bin', | ||||||
|  |       'electron' + (process.platform === 'win32' ? '.cmd' : '')), | ||||||
|  |     args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont | ||||||
|  |     env: {NODE_ENV: 'test'}, | ||||||
|  |     waitTimeout: 10e3 | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Generates package.json for test app | ||||||
|  | // Reads app package.json and updates the productName to config.TEST_APP_PRODUCT_NAME  | ||||||
|  | // We do this so that the app integration doesn't doesn't share the same appDataDir as the dev application | ||||||
|  | function generateTestAppPackageJson () { | ||||||
|  |   let packageJson = require(path.join(__dirname, '../package.json')) | ||||||
|  |   packageJson.productName = config.TEST_APP_PRODUCT_NAME | ||||||
|  |   packageJson.main = '../app/main' | ||||||
|  |  | ||||||
|  |   const testPackageJsonPath = path.join(__dirname, 'package.json') | ||||||
|  |   fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Starts the app, waits for it to load, returns a promise | ||||||
|  | function waitForLoad (app, t, opts) { | ||||||
|  |   if (!opts) opts = {} | ||||||
|  |   return app.start().then(function () { | ||||||
|  |     return app.client.waitUntilWindowLoaded() | ||||||
|  |   }) | ||||||
|  |   .then(function() { | ||||||
|  |     return app.client.pause(2000); | ||||||
|  |   }) | ||||||
|  |   .then(function () { | ||||||
|  |     return app.webContents.getTitle() | ||||||
|  |   }).then(function (title) { | ||||||
|  |     t.equal(title, 'Zulip', 'html title') | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Returns a promise that resolves after 'ms' milliseconds. Default: 1 second | ||||||
|  | function wait (ms) { | ||||||
|  |   if (ms === undefined) ms = 1000 // Default: wait long enough for the UI to update | ||||||
|  |   return new Promise(function (resolve, reject) { | ||||||
|  |     setTimeout(resolve, ms) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Quit the app, end the test, either in success (!err) or failure (err) | ||||||
|  | function endTest (app, t, err) { | ||||||
|  |   return app.stop().then(function () { | ||||||
|  |     t.end(err) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getAppDataDir () { | ||||||
|  |   let base | ||||||
|  |  | ||||||
|  |   if (process.platform === 'darwin') { | ||||||
|  |     base = path.join(process.env.HOME, 'Library', 'Application Support') | ||||||
|  |   } else if (process.platform === 'linux') { | ||||||
|  |     base = process.env.XDG_CONFIG_HOME ? | ||||||
|  |       process.env.XDG_CONFIG_HOME : path.join(process.env.HOME, '.config') | ||||||
|  |   } else if (process.platform === 'win32') { | ||||||
|  |     base = process.env.APPDATA | ||||||
|  |   } else { | ||||||
|  |     console.log('Could not detect app data dir base. Exiting...') | ||||||
|  |     process.exit(1) | ||||||
|  |   } | ||||||
|  |   console.log('Detected App Data Dir base:', base) | ||||||
|  |   return path.join(base, config.TEST_APP_PRODUCT_NAME) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Resets the test directory, containing domain.json, window-state.json, etc | ||||||
|  | function resetTestDataDir () { | ||||||
|  |   appDataDir = getAppDataDir() | ||||||
|  |   rimraf.sync(appDataDir) | ||||||
|  |   rimraf.sync(path.join(__dirname, 'package.json')) | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								tests/test-add-organization.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								tests/test-add-organization.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | const test = require('tape') | ||||||
|  | const setup = require('./setup') | ||||||
|  |  | ||||||
|  | test('add-organization', function (t) { | ||||||
|  |   t.timeoutAfter(50e3) | ||||||
|  |   setup.resetTestDataDir() | ||||||
|  |   const app = setup.createApp() | ||||||
|  |   setup.waitForLoad(app, t) | ||||||
|  |     .then(() => app.client.windowByIndex(1)) // focus on webview | ||||||
|  |     .then(() => app.client.setValue('.setting-input-value', 'chat.zulip.org')) | ||||||
|  |     .then(() => app.client.click('.server-save-action')) | ||||||
|  |     .then(() => setup.wait(5000)) | ||||||
|  |     .then(() => app.client.windowByIndex(0)) // Switch focus back to main win | ||||||
|  |     .then(() => app.client.windowByIndex(1)) // Switch focus back to org webview | ||||||
|  |     .then(() => app.client.waitForExist('//*[@id="id_username"]')) | ||||||
|  |     .then(() => setup.endTest(app, t), | ||||||
|  |           (err) => setup.endTest(app, t, err || 'error')) | ||||||
|  | }) | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								tools/fetch-pull-request
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										16
									
								
								tools/fetch-pull-request
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  | set -x | ||||||
|  |  | ||||||
|  | if ! git diff-index --quiet HEAD; then | ||||||
|  |     set +x | ||||||
|  |     echo "There are uncommitted changes:" | ||||||
|  |     git status --short | ||||||
|  |     echo "Doing nothing to avoid losing your work." | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | request_id="$1" | ||||||
|  | remote=${2:-"upstream"} | ||||||
|  | git fetch "$remote" "pull/$request_id/head" | ||||||
|  | git checkout -B "review-original-${request_id}" | ||||||
|  | git reset --hard FETCH_HEAD | ||||||
							
								
								
									
										23
									
								
								tools/fetch-pull-request.cmd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tools/fetch-pull-request.cmd
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | @echo off | ||||||
|  | git diff-index --quiet HEAD | ||||||
|  | if %ERRORLEVEL% NEQ 0 ( | ||||||
|  |     echo "There are uncommitted changes:" | ||||||
|  |     git status --short | ||||||
|  |     echo "Doing nothing to avoid losing your work." | ||||||
|  |     exit /B 1 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%~1"=="" ( | ||||||
|  |     echo "Error you must specify the PR number" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%~2"=="" (  | ||||||
|  |     set remote="upstream" | ||||||
|  | ) else ( | ||||||
|  |     set remote=%2 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | set request_id="%1" | ||||||
|  | git fetch "%remote%" "pull/%request_id%/head" | ||||||
|  | git checkout -B "review-%request_id%" | ||||||
|  | git reset --hard FETCH_HEAD | ||||||
							
								
								
									
										17
									
								
								tools/fetch-rebase-pull-request
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								tools/fetch-rebase-pull-request
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  | set -x | ||||||
|  |  | ||||||
|  | if ! git diff-index --quiet HEAD; then | ||||||
|  |     set +x | ||||||
|  |     echo "There are uncommitted changes:" | ||||||
|  |     git status --short | ||||||
|  |     echo "Doing nothing to avoid losing your work." | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | request_id="$1" | ||||||
|  | remote=${2:-"upstream"} | ||||||
|  | git fetch "$remote" "pull/$request_id/head" | ||||||
|  | git checkout -B "review-${request_id}" $remote/master | ||||||
|  | git reset --hard FETCH_HEAD | ||||||
|  | git pull --rebase | ||||||
							
								
								
									
										24
									
								
								tools/fetch-rebase-pull-request.cmd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								tools/fetch-rebase-pull-request.cmd
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | @echo off | ||||||
|  | git diff-index --quiet HEAD | ||||||
|  | if %errorlevel% neq 0 ( | ||||||
|  |     echo "There are uncommitted changes:" | ||||||
|  |     git status --short | ||||||
|  |     echo "Doing nothing to avoid losing your work." | ||||||
|  |     exit \B 1 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%~1"=="" ( | ||||||
|  |     echo "Error you must specify the PR number" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%~2"=="" (  | ||||||
|  |     set remote="upstream" | ||||||
|  | ) else ( | ||||||
|  |     set remote=%2 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | set request_id="%1" | ||||||
|  | git fetch "%remote%" "pull/%request_id%/head" | ||||||
|  | git checkout -B "review-%request_id%" %remote%/master | ||||||
|  | git reset --hard FETCH_HEAD | ||||||
|  | git pull --rebase | ||||||
							
								
								
									
										10
									
								
								tools/reinstall-node-modules
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								tools/reinstall-node-modules
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  | set -x | ||||||
|  |  | ||||||
|  | echo "Removing node_modules and app/node_modules" | ||||||
|  | rm -rf node_modules | ||||||
|  | rm -rf app/node_modules | ||||||
|  |  | ||||||
|  | echo "node_modules removed reinstalling npm packages" | ||||||
|  | npm i | ||||||
							
								
								
									
										8
									
								
								tools/reinstall-node-modules.cmd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tools/reinstall-node-modules.cmd
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | @echo off | ||||||
|  |  | ||||||
|  | echo "Removing node_modules and app/node_modules" | ||||||
|  | rmdir /s /q node_modules | ||||||
|  | rmdir /s /q app/node_modules | ||||||
|  |  | ||||||
|  | echo "node_modules removed reinstalling npm packages" | ||||||
|  | npm i | ||||||
| @@ -5,7 +5,11 @@ | |||||||
| * Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I` | * Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I` | ||||||
|  |  | ||||||
| ### Error : ChecksumMismatchError | ### Error : ChecksumMismatchError | ||||||
| - Try deleteing `node_modules` && `app/node_modules` directories. Re-install dependencies using `npm install`. | - Try deleteing `node_modules` && `app/node_modules` directories. Re-install dependencies using `npm install` | ||||||
|  |  | ||||||
| ### Error : Module version mismatch. Expected 50, got 51 | ### Error : Module version mismatch. Expected 50, got 51 | ||||||
| - Make sure you have installed [node-gyp](https://github.com/nodejs/node-gyp#installation) dependencies properly. | - Make sure you have installed [node-gyp](https://github.com/nodejs/node-gyp#installation) dependencies properly | ||||||
|  |  | ||||||
|  | ### Error: Desktop Notifications not working | ||||||
|  | - Make sure the **Show Desktop Notifications** setting option is set to be true | ||||||
|  | - Check your OS notifications center settings | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user