mirror of
				https://github.com/zulip/zulip-desktop.git
				synced 2025-10-31 20:13:43 +00:00 
			
		
		
		
	Compare commits
	
		
			144 Commits
		
	
	
		
			disable-au
			...
			git-linter
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9e3cfbd887 | ||
|  | 720f42ca80 | ||
|  | e18ba5dab6 | ||
|  | 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 | ||
|  | dcd2abca6e | ||
|  | 2fb9efb981 | ||
|  | 7245b6a110 | ||
|  | bcb8ffb55f | ||
|  | 77094596a5 | ||
|  | 06ad44bdd7 | ||
|  | e719ba139c | ||
|  | 9853e9226c | ||
|  | f2c76b5ca3 | ||
|  | e6dbff995b | ||
|  | 4578d4a5f7 | ||
|  | 4b895a2312 | ||
|  | 53c0428a3a | ||
|  | 0a1866abb5 | ||
|  | ce862a4890 | ||
|  | 1b1ad2cd61 | ||
|  | ead7a06308 | ||
|  | 6659dd5097 | ||
|  | ed1f0f6d5b | ||
|  | 79acf8a6e1 | ||
|  | 8e0033f03e | ||
|  | 9144c2630d | ||
|  | fae05fc3b1 | ||
|  | 73603a4fd2 | ||
|  | a498ffc7d6 | ||
|  | 7afcf13401 | ||
|  | 89a292559d | ||
|  | be14517caf | ||
|  | 3b6c5ae532 | ||
|  | 40e3ed0f2f | ||
|  | 5d988858b0 | ||
|  | 3a974136a3 | ||
|  | 6ed5a5309c | ||
|  | 80c37fabb8 | ||
|  | 79366e19df | ||
|  | f409bb0449 | ||
|  | 45bdde951f | ||
|  | 6b627780f0 | ||
|  | 6f67553da5 | ||
|  | 2e710a9322 | ||
|  | 91f3afa8fe | ||
|  | f784345495 | ||
|  | 67da435154 | ||
|  | c89733610d | ||
|  | 8f272a67b5 | ||
|  | f6c4a76138 | ||
|  | b90a4c5254 | ||
|  | a06e09e565 | ||
|  | ad5bef821e | ||
|  | 58bbd7bf30 | ||
|  | 90d080dc96 | ||
|  | ad3fcf585e | ||
|  | 4b8f216bab | ||
|  | e620e0c428 | ||
|  | 50b3151b5d | ||
|  | 0c32756485 | ||
|  | 0c0835e364 | ||
|  | 9e962a5c44 | ||
|  | a218f7ea64 | ||
|  | 13a7f7475a | ||
|  | 48b17a1549 | ||
|  | 653598fd9e | ||
|  | ddbc282f49 | ||
|  | 992d92b06d | ||
|  | 45867ef15e | ||
|  | 6572c90d49 | ||
|  | 1ed0011c88 | 
							
								
								
									
										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,3 +24,8 @@ yarn-error.log* | ||||
| # miscellaneous | ||||
| .idea | ||||
| config.gypi | ||||
|  | ||||
| # Test generated files | ||||
| tests/package.json | ||||
|  | ||||
| .python-version | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| 2.7.9 | ||||
| @@ -18,6 +18,7 @@ node_js: | ||||
| - '6' | ||||
|  | ||||
| before_install: | ||||
|   - ./scripts/travis-xvfb.sh | ||||
|   - npm install -g gulp | ||||
|   - npm install | ||||
|  | ||||
| @@ -27,7 +28,13 @@ cache: | ||||
|   - app/node_modules | ||||
|  | ||||
| script: | ||||
|   - echo $TRAVIS_COMMIT_RANGE | ||||
|   - echo ${TRAVIS_COMMIT_RANGE/.../..} | ||||
|   - echo "test" | ||||
|   - git log -4 | ||||
|   - node ./tools/gitlint --ci-mode | ||||
|   - npm run travis | ||||
|  | ||||
| notifications: | ||||
|   webhooks: | ||||
|     urls: | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| --- | ||||
| Please include:  | ||||
| - `Operating System` | ||||
| - `Clear steps to reproduce the issue` | ||||
| - `Relevant error messages and/or screenshots` | ||||
|  | ||||
|  | ||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							| @@ -8,23 +8,16 @@ Desktop client for Zulip. Available for Mac, Linux and Windows. | ||||
| <img src="http://i.imgur.com/ChzTq4F.png"/> | ||||
|  | ||||
| # Download | ||||
| Please see [installation guide](./how-to-install.md). | ||||
| Please see [installation guide](https://zulipchat.com/help/desktop-app-install-guide). | ||||
|  | ||||
| # Features | ||||
| * Sign in to multiple teams | ||||
| * Native desktop Notifications | ||||
| * SpellChecker | ||||
| * Desktop Notifications with inline reply support | ||||
| * Multilanguage SpellChecker | ||||
| * OSX/Win/Linux installers | ||||
| * Automatic Updates (macOS/Windows) | ||||
| * Automatic Updates (macOS/Windows/Linux) | ||||
| * 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 | ||||
| Please see our [development guide](./development.md) to get started and run app locally. | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| 'use strict'; | ||||
| const fs = require('fs'); | ||||
| const { app, dialog } = require('electron'); | ||||
| const { autoUpdater } = require('electron-updater'); | ||||
| const isDev = require('electron-is-dev'); | ||||
| @@ -7,19 +6,14 @@ const isDev = require('electron-is-dev'); | ||||
| const ConfigUtil = require('./../renderer/js/utils/config-util.js'); | ||||
|  | ||||
| function appUpdater() { | ||||
| 	// Don't initiate auto-updates in development and on Linux system | ||||
| 	// since autoUpdater doesn't work on Linux | ||||
| 	if (isDev || process.platform === 'linux') { | ||||
| 	// Don't initiate auto-updates in development | ||||
| 	if (isDev) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Create Logs directory | ||||
| 	const LogsDir = `${app.getPath('userData')}/Logs`; | ||||
|  | ||||
| 	if (!fs.existsSync(LogsDir)) { | ||||
| 		fs.mkdirSync(LogsDir); | ||||
| 	} | ||||
|  | ||||
| 	// Log whats happening | ||||
| 	const log = require('electron-log'); | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								app/main/crash-reporter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/main/crash-reporter.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { crashReporter } = require('electron'); | ||||
|  | ||||
| const crashHandler = () => { | ||||
| 	crashReporter.start({ | ||||
| 		productName: 'zulip-electron', | ||||
| 		companyName: 'Kandra Labs, Inc.', | ||||
| 		submitURL: 'https://zulip-sentry.herokuapp.com/crashreport', | ||||
| 		autoSubmit: true | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
| 	crashHandler | ||||
| }; | ||||
| @@ -1,18 +1,24 @@ | ||||
| 'use strict'; | ||||
| const path = require('path'); | ||||
| const electron = require('electron'); | ||||
| const electronLocalshortcut = require('electron-localshortcut'); | ||||
| const windowStateKeeper = require('electron-window-state'); | ||||
| const isDev = require('electron-is-dev'); | ||||
| const appMenu = require('./menu'); | ||||
| const { appUpdater } = require('./autoupdater'); | ||||
| const { crashHandler } = require('./crash-reporter'); | ||||
|  | ||||
| const { setAutoLaunch } = require('./startup'); | ||||
|  | ||||
| const { app, ipcMain } = electron; | ||||
|  | ||||
| const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js'); | ||||
| const ConfigUtil = require('./../renderer/js/utils/config-util.js'); | ||||
|  | ||||
| // Adds debug features like hotkeys for triggering dev tools and reload | ||||
| // in development mode | ||||
| if (isDev) { | ||||
| 	require('electron-debug')(); | ||||
| } | ||||
|  | ||||
| // Prevent window being garbage collected | ||||
| let mainWindow; | ||||
| @@ -61,8 +67,8 @@ function createMainWindow() { | ||||
| 		y: mainWindowState.y, | ||||
| 		width: mainWindowState.width, | ||||
| 		height: mainWindowState.height, | ||||
| 		minWidth: 600, | ||||
| 		minHeight: 500, | ||||
| 		minWidth: 300, | ||||
| 		minHeight: 400, | ||||
| 		webPreferences: { | ||||
| 			plugins: true, | ||||
| 			allowDisplayingInsecureContent: true, | ||||
| @@ -76,7 +82,11 @@ function createMainWindow() { | ||||
| 	}); | ||||
|  | ||||
| 	win.once('ready-to-show', () => { | ||||
| 		if (ConfigUtil.getConfigItem('startMinimized')) { | ||||
| 			win.minimize(); | ||||
| 		} else { | ||||
| 			win.show(); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	win.loadURL(mainURL); | ||||
| @@ -92,9 +102,6 @@ function createMainWindow() { | ||||
| 				win.hide(); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Unregister all the shortcuts so that they don't interfare with other apps | ||||
| 		electronLocalshortcut.unregisterAll(mainWindow); | ||||
| 	}); | ||||
|  | ||||
| 	win.setTitle('Zulip'); | ||||
| @@ -122,21 +129,8 @@ function createMainWindow() { | ||||
| 	return win; | ||||
| } | ||||
|  | ||||
| function registerLocalShortcuts(page) { | ||||
| 	// Somehow, reload action cannot be overwritten by the menu item | ||||
| 	electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => { | ||||
| 		page.send('reload-viewer'); | ||||
| 	}); | ||||
|  | ||||
| 	// Also adding these shortcuts because some users might want to use it instead of CMD/Left-Right | ||||
| 	electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => { | ||||
| 		page.send('back'); | ||||
| 	}); | ||||
|  | ||||
| 	electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => { | ||||
| 		page.send('forward'); | ||||
| 	}); | ||||
| } | ||||
| // Decrease load on GPU (experimental) | ||||
| app.disableHardwareAcceleration(); | ||||
|  | ||||
| // eslint-disable-next-line max-params | ||||
| app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { | ||||
| @@ -144,11 +138,6 @@ app.on('certificate-error', (event, webContents, url, error, certificate, callba | ||||
| 	callback(true); | ||||
| }); | ||||
|  | ||||
| app.on('window-all-closed', () => { | ||||
| 	// Unregister all the shortcuts so that they don't interfare with other apps | ||||
| 	electronLocalshortcut.unregisterAll(mainWindow); | ||||
| }); | ||||
|  | ||||
| app.on('activate', () => { | ||||
| 	if (!mainWindow) { | ||||
| 		mainWindow = createMainWindow(); | ||||
| @@ -163,19 +152,23 @@ app.on('ready', () => { | ||||
|  | ||||
| 	const page = mainWindow.webContents; | ||||
|  | ||||
| 	registerLocalShortcuts(page); | ||||
|  | ||||
| 	page.on('dom-ready', () => { | ||||
| 		if (ConfigUtil.getConfigItem('startMinimized')) { | ||||
| 			mainWindow.minimize(); | ||||
| 		} else { | ||||
| 			mainWindow.show(); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	page.once('did-frame-finish-load', () => { | ||||
| 		// Initate auto-updates on MacOS and Windows | ||||
| 		appUpdater(); | ||||
| 		crashHandler(); | ||||
| 	}); | ||||
|  | ||||
| 	electron.powerMonitor.on('resume', () => { | ||||
| 		page.send('reload-viewer'); | ||||
| 		mainWindow.reload(); | ||||
| 		page.send('destroytray'); | ||||
| 	}); | ||||
|  | ||||
| 	ipcMain.on('focus-app', () => { | ||||
| @@ -229,30 +222,15 @@ app.on('ready', () => { | ||||
| 	}); | ||||
|  | ||||
| 	ipcMain.on('register-server-tab-shortcut', (event, index) => { | ||||
| 		electronLocalshortcut.register(mainWindow, `CommandOrControl+${index}`, () => { | ||||
| 		// Array index == Shown index - 1 | ||||
| 		page.send('switch-server-tab', index - 1); | ||||
| 	}); | ||||
| 	}); | ||||
|  | ||||
| 	ipcMain.on('local-shortcuts', (event, enable) => { | ||||
| 		if (enable) { | ||||
| 			registerLocalShortcuts(page); | ||||
| 		} else { | ||||
| 			electronLocalshortcut.unregisterAll(mainWindow); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => { | ||||
| 		setAutoLaunch(AutoLaunchValue); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| app.on('will-quit', () => { | ||||
| 	// Unregister all the shortcuts so that they don't interfare with other apps | ||||
| 	electronLocalshortcut.unregisterAll(mainWindow); | ||||
| }); | ||||
|  | ||||
| app.on('before-quit', () => { | ||||
| 	isQuitting = true; | ||||
| }); | ||||
|   | ||||
| @@ -37,7 +37,7 @@ class AppMenu { | ||||
| 			accelerator: 'CommandOrControl+R', | ||||
| 			click(item, focusedWindow) { | ||||
| 				if (focusedWindow) { | ||||
| 					AppMenu.sendAction('reload-viewer'); | ||||
| 					AppMenu.sendAction('reload-current-viewer'); | ||||
| 				} | ||||
| 			} | ||||
| 		}, { | ||||
| @@ -54,7 +54,7 @@ class AppMenu { | ||||
| 			role: 'togglefullscreen' | ||||
| 		}, { | ||||
| 			label: 'Zoom In', | ||||
| 			accelerator: 'CommandOrControl+=', | ||||
| 			accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=', | ||||
| 			click(item, focusedWindow) { | ||||
| 				if (focusedWindow) { | ||||
| 					AppMenu.sendAction('zoomIn'); | ||||
| @@ -173,7 +173,7 @@ class AppMenu { | ||||
| 		return [{ | ||||
| 			label: `${app.getName()}`, | ||||
| 			submenu: [{ | ||||
| 				label: 'Zulip Desktop', | ||||
| 				label: 'About Zulip', | ||||
| 				click(item, focusedWindow) { | ||||
| 					if (focusedWindow) { | ||||
| 						AppMenu.sendAction('open-about'); | ||||
| @@ -273,7 +273,7 @@ class AppMenu { | ||||
| 		return [{ | ||||
| 			label: 'File', | ||||
| 			submenu: [{ | ||||
| 				label: 'Zulip Desktop', | ||||
| 				label: 'About Zulip', | ||||
| 				click(item, focusedWindow) { | ||||
| 					if (focusedWindow) { | ||||
| 						AppMenu.sendAction('open-about'); | ||||
| @@ -370,10 +370,20 @@ class AppMenu { | ||||
| 	} | ||||
|  | ||||
| 	static resetAppSettings() { | ||||
| 		const getAppPath = path.join(app.getPath('appData'), appName, 'window-state.json'); | ||||
| 		// We save App's settings/configurations in following files | ||||
| 		const settingFiles = ['window-state.json', 'domain.json', 'settings.json']; | ||||
|  | ||||
| 		fs.unlink(getAppPath, () => { | ||||
| 			setTimeout(() => AppMenu.sendAction('clear-app-data'), 1000); | ||||
| 		settingFiles.forEach(settingFileName => { | ||||
| 			const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName); | ||||
| 			fs.access(getSettingFilesPath, error => { | ||||
| 				if (error) { | ||||
| 					console.log(error); | ||||
| 				} else { | ||||
| 					fs.unlink(getSettingFilesPath, () => { | ||||
| 						AppMenu.sendAction('clear-app-data'); | ||||
| 					}); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										935
									
								
								app/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										935
									
								
								app/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,935 @@ | ||||
| { | ||||
|   "name": "zulip", | ||||
|   "version": "1.7.0", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|     "@paulcbetts/cld": { | ||||
|       "version": "2.4.6", | ||||
|       "resolved": "https://registry.npmjs.org/@paulcbetts/cld/-/cld-2.4.6.tgz", | ||||
|       "integrity": "sha1-qZL2vEPKshKsLESIpnHPMC+LYuc=", | ||||
|       "requires": { | ||||
|         "glob": "5.0.15", | ||||
|         "nan": "2.8.0", | ||||
|         "rimraf": "2.6.2", | ||||
|         "underscore": "1.8.3" | ||||
|       } | ||||
|     }, | ||||
|     "@paulcbetts/spellchecker": { | ||||
|       "version": "4.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/@paulcbetts/spellchecker/-/spellchecker-4.0.6.tgz", | ||||
|       "integrity": "sha512-9lhLEvWfAB00n2oOM/S08sna9AuFk+b+bPk8ficpSa2X0Ll40PahMwfFS3G54nqQBIFFZgTPrhoHtCLAao0xmg==", | ||||
|       "requires": { | ||||
|         "nan": "2.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "ajv": { | ||||
|       "version": "4.11.8", | ||||
|       "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", | ||||
|       "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", | ||||
|       "requires": { | ||||
|         "co": "4.6.0", | ||||
|         "json-stable-stringify": "1.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "applescript": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz", | ||||
|       "integrity": "sha1-u4evVoytA0pOSMS9r2Bno6JwExc=" | ||||
|     }, | ||||
|     "argparse": { | ||||
|       "version": "1.0.9", | ||||
|       "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", | ||||
|       "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", | ||||
|       "requires": { | ||||
|         "sprintf-js": "1.0.3" | ||||
|       } | ||||
|     }, | ||||
|     "asn1": { | ||||
|       "version": "0.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", | ||||
|       "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" | ||||
|     }, | ||||
|     "assert-plus": { | ||||
|       "version": "0.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", | ||||
|       "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" | ||||
|     }, | ||||
|     "asynckit": { | ||||
|       "version": "0.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", | ||||
|       "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" | ||||
|     }, | ||||
|     "auto-launch": { | ||||
|       "version": "5.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/auto-launch/-/auto-launch-5.0.1.tgz", | ||||
|       "integrity": "sha1-IBWoowOEq+Dn+Yy9yoFFzxVHU64=", | ||||
|       "requires": { | ||||
|         "applescript": "1.0.0", | ||||
|         "mkdirp": "0.5.1", | ||||
|         "path-is-absolute": "1.0.1", | ||||
|         "untildify": "3.0.2", | ||||
|         "winreg": "1.2.2" | ||||
|       } | ||||
|     }, | ||||
|     "aws-sign2": { | ||||
|       "version": "0.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", | ||||
|       "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" | ||||
|     }, | ||||
|     "aws4": { | ||||
|       "version": "1.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", | ||||
|       "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" | ||||
|     }, | ||||
|     "balanced-match": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", | ||||
|       "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" | ||||
|     }, | ||||
|     "bcp47": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", | ||||
|       "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=" | ||||
|     }, | ||||
|     "bcrypt-pbkdf": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", | ||||
|       "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", | ||||
|       "optional": true, | ||||
|       "requires": { | ||||
|         "tweetnacl": "0.14.5" | ||||
|       } | ||||
|     }, | ||||
|     "bindings": { | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", | ||||
|       "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==", | ||||
|       "optional": true | ||||
|     }, | ||||
|     "bluebird": { | ||||
|       "version": "3.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", | ||||
|       "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" | ||||
|     }, | ||||
|     "bluebird-lst": { | ||||
|       "version": "1.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.5.tgz", | ||||
|       "integrity": "sha512-Ey0bDNys5qpYPhZ/oQ9vOEvD0TYQDTILMXWP2iGfvMg7rSDde+oV4aQQgqRH+CvBFNz2BSDQnPGMUl6LKBUUQA==", | ||||
|       "requires": { | ||||
|         "bluebird": "3.5.1" | ||||
|       } | ||||
|     }, | ||||
|     "boom": { | ||||
|       "version": "2.10.1", | ||||
|       "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", | ||||
|       "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", | ||||
|       "requires": { | ||||
|         "hoek": "2.16.3" | ||||
|       } | ||||
|     }, | ||||
|     "brace-expansion": { | ||||
|       "version": "1.1.8", | ||||
|       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", | ||||
|       "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", | ||||
|       "requires": { | ||||
|         "balanced-match": "1.0.0", | ||||
|         "concat-map": "0.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "builder-util-runtime": { | ||||
|       "version": "3.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-3.2.0.tgz", | ||||
|       "integrity": "sha512-VRvyyLiZZSBjcUTqEsHlBJSK0s6uVQChO7kbmVeU6QmSJ7TtsotNQELO6lbahwZMAQ4Z/haCKhlLBDdhW+3aqA==", | ||||
|       "requires": { | ||||
|         "bluebird-lst": "1.0.5", | ||||
|         "debug": "3.1.0", | ||||
|         "fs-extra-p": "4.4.4", | ||||
|         "sax": "1.2.4" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "3.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", | ||||
|           "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "caseless": { | ||||
|       "version": "0.12.0", | ||||
|       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", | ||||
|       "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" | ||||
|     }, | ||||
|     "co": { | ||||
|       "version": "4.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", | ||||
|       "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" | ||||
|     }, | ||||
|     "combined-stream": { | ||||
|       "version": "1.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", | ||||
|       "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", | ||||
|       "requires": { | ||||
|         "delayed-stream": "1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "concat-map": { | ||||
|       "version": "0.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | ||||
|       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" | ||||
|     }, | ||||
|     "core-util-is": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", | ||||
|       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" | ||||
|     }, | ||||
|     "cryptiles": { | ||||
|       "version": "2.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", | ||||
|       "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", | ||||
|       "requires": { | ||||
|         "boom": "2.10.1" | ||||
|       } | ||||
|     }, | ||||
|     "dashdash": { | ||||
|       "version": "1.14.1", | ||||
|       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", | ||||
|       "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", | ||||
|       "requires": { | ||||
|         "assert-plus": "1.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "assert-plus": { | ||||
|           "version": "1.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", | ||||
|           "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "debug": { | ||||
|       "version": "2.6.9", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|       "requires": { | ||||
|         "ms": "2.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "deep-equal": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", | ||||
|       "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" | ||||
|     }, | ||||
|     "delayed-stream": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", | ||||
|       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" | ||||
|     }, | ||||
|     "ecc-jsbn": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", | ||||
|       "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", | ||||
|       "optional": true, | ||||
|       "requires": { | ||||
|         "jsbn": "0.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "electron-is-dev": { | ||||
|       "version": "0.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.3.0.tgz", | ||||
|       "integrity": "sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4=" | ||||
|     }, | ||||
|     "electron-log": { | ||||
|       "version": "2.2.7", | ||||
|       "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-2.2.7.tgz", | ||||
|       "integrity": "sha512-pRfRn53MQGJ9L1+aC0VFcps0Uo5NM4RYsdvIdnjiV6J+krMr4cgBZ/DDA3kjNsr0D0kzo2WKpMgn2fAVra99rg==" | ||||
|     }, | ||||
|     "electron-remote": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/electron-remote/-/electron-remote-1.2.0.tgz", | ||||
|       "integrity": "sha512-Fo2wnwK2tzd81Ux4pfNhz9DwHBoooZahdWBqOh9HtESYh2jrcsjc6FAibIu2oIOk71T1USyC7OBcqE8BZw3FGQ==", | ||||
|       "requires": { | ||||
|         "debug": "2.6.9", | ||||
|         "hashids": "1.1.4", | ||||
|         "lodash.get": "4.4.2", | ||||
|         "pify": "2.3.0", | ||||
|         "rxjs": "5.5.2", | ||||
|         "xmlhttprequest": "1.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "electron-spellchecker": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/electron-spellchecker/-/electron-spellchecker-1.1.2.tgz", | ||||
|       "integrity": "sha512-AdzD/Q82Svk9EDTc65vRr271UPLVIxsruKJM0iwqxEG9Y/CogNhEAJz/asV0BFWom4tpdB6cHcLbYePb11Musw==", | ||||
|       "requires": { | ||||
|         "@paulcbetts/cld": "2.4.6", | ||||
|         "@paulcbetts/spellchecker": "4.0.6", | ||||
|         "bcp47": "1.1.2", | ||||
|         "debug": "2.6.9", | ||||
|         "electron-remote": "1.2.0", | ||||
|         "keyboard-layout": "2.0.13", | ||||
|         "lru-cache": "4.1.1", | ||||
|         "mkdirp": "0.5.1", | ||||
|         "pify": "2.3.0", | ||||
|         "rxjs": "5.5.2", | ||||
|         "rxjs-serial-subscription": "0.1.1", | ||||
|         "spawn-rx": "2.0.12" | ||||
|       } | ||||
|     }, | ||||
|     "electron-updater": { | ||||
|       "version": "2.16.2", | ||||
|       "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-2.16.2.tgz", | ||||
|       "integrity": "sha512-gv1kezjdXR6sw266aTzfs7HgPO5vaf5TsBFh7kMi47JABAIJSO3n+U79pSBperPVtGdqWQ4WfM6+2irrFvYXLw==", | ||||
|       "requires": { | ||||
|         "bluebird-lst": "1.0.5", | ||||
|         "builder-util-runtime": "3.2.0", | ||||
|         "electron-is-dev": "0.3.0", | ||||
|         "fs-extra-p": "4.4.4", | ||||
|         "js-yaml": "3.10.0", | ||||
|         "lazy-val": "1.0.2", | ||||
|         "lodash.isequal": "4.5.0", | ||||
|         "semver": "5.4.1", | ||||
|         "source-map-support": "0.5.0" | ||||
|       } | ||||
|     }, | ||||
|     "electron-window-state": { | ||||
|       "version": "4.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/electron-window-state/-/electron-window-state-4.1.1.tgz", | ||||
|       "integrity": "sha1-azT9wxs4UU3+yLfI97XUrdtnYy0=", | ||||
|       "requires": { | ||||
|         "deep-equal": "1.0.1", | ||||
|         "jsonfile": "2.4.0", | ||||
|         "mkdirp": "0.5.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "jsonfile": { | ||||
|           "version": "2.4.0", | ||||
|           "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", | ||||
|           "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", | ||||
|           "requires": { | ||||
|             "graceful-fs": "4.1.11" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "esprima": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", | ||||
|       "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" | ||||
|     }, | ||||
|     "event-kit": { | ||||
|       "version": "2.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.4.0.tgz", | ||||
|       "integrity": "sha512-ZXd9jxUoc/f/zdLdR3OUcCzT84WnpaNWefquLyE125akIC90sDs8S3T/qihliuVsaj7Osc0z8lLL2fjooE9Z4A==" | ||||
|     }, | ||||
|     "event-target-shim": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.1.1.tgz", | ||||
|       "integrity": "sha1-qG5e5r2qFgVEddp5fM3fDFVphJE=", | ||||
|       "optional": true | ||||
|     }, | ||||
|     "extend": { | ||||
|       "version": "3.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", | ||||
|       "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" | ||||
|     }, | ||||
|     "extsprintf": { | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", | ||||
|       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" | ||||
|     }, | ||||
|     "forever-agent": { | ||||
|       "version": "0.6.1", | ||||
|       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", | ||||
|       "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" | ||||
|     }, | ||||
|     "form-data": { | ||||
|       "version": "2.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", | ||||
|       "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", | ||||
|       "requires": { | ||||
|         "asynckit": "0.4.0", | ||||
|         "combined-stream": "1.0.5", | ||||
|         "mime-types": "2.1.17" | ||||
|       } | ||||
|     }, | ||||
|     "fs-extra": { | ||||
|       "version": "4.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", | ||||
|       "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", | ||||
|       "requires": { | ||||
|         "graceful-fs": "4.1.11", | ||||
|         "jsonfile": "4.0.0", | ||||
|         "universalify": "0.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "fs-extra-p": { | ||||
|       "version": "4.4.4", | ||||
|       "resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.4.4.tgz", | ||||
|       "integrity": "sha512-zHsMNJWhXD184QfHKEIFSQSgAFNV7v9J+Nt2XpaLZp2nTz6WxZNV+R4G2uYeGeLTMaKvUZiqGKrH/4iFCupcUA==", | ||||
|       "requires": { | ||||
|         "bluebird-lst": "1.0.5", | ||||
|         "fs-extra": "4.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "fs.realpath": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | ||||
|       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" | ||||
|     }, | ||||
|     "getpass": { | ||||
|       "version": "0.1.7", | ||||
|       "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", | ||||
|       "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", | ||||
|       "requires": { | ||||
|         "assert-plus": "1.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "assert-plus": { | ||||
|           "version": "1.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", | ||||
|           "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "glob": { | ||||
|       "version": "5.0.15", | ||||
|       "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", | ||||
|       "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", | ||||
|       "requires": { | ||||
|         "inflight": "1.0.6", | ||||
|         "inherits": "2.0.3", | ||||
|         "minimatch": "3.0.4", | ||||
|         "once": "1.4.0", | ||||
|         "path-is-absolute": "1.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "graceful-fs": { | ||||
|       "version": "4.1.11", | ||||
|       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", | ||||
|       "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" | ||||
|     }, | ||||
|     "har-schema": { | ||||
|       "version": "1.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", | ||||
|       "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" | ||||
|     }, | ||||
|     "har-validator": { | ||||
|       "version": "4.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", | ||||
|       "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", | ||||
|       "requires": { | ||||
|         "ajv": "4.11.8", | ||||
|         "har-schema": "1.0.5" | ||||
|       } | ||||
|     }, | ||||
|     "hashids": { | ||||
|       "version": "1.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/hashids/-/hashids-1.1.4.tgz", | ||||
|       "integrity": "sha512-U/fnTE3edW0AV92ZI/BfEluMZuVcu3MDOopsN7jS+HqDYcarQo8rXQiWlsBlm0uX48/taYSdxRsfzh2HRg5Z6w==" | ||||
|     }, | ||||
|     "hawk": { | ||||
|       "version": "3.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", | ||||
|       "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", | ||||
|       "requires": { | ||||
|         "boom": "2.10.1", | ||||
|         "cryptiles": "2.0.5", | ||||
|         "hoek": "2.16.3", | ||||
|         "sntp": "1.0.9" | ||||
|       } | ||||
|     }, | ||||
|     "hoek": { | ||||
|       "version": "2.16.3", | ||||
|       "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", | ||||
|       "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" | ||||
|     }, | ||||
|     "http-signature": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", | ||||
|       "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", | ||||
|       "requires": { | ||||
|         "assert-plus": "0.2.0", | ||||
|         "jsprim": "1.4.1", | ||||
|         "sshpk": "1.13.1" | ||||
|       } | ||||
|     }, | ||||
|     "inflight": { | ||||
|       "version": "1.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", | ||||
|       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", | ||||
|       "requires": { | ||||
|         "once": "1.4.0", | ||||
|         "wrappy": "1.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "inherits": { | ||||
|       "version": "2.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", | ||||
|       "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" | ||||
|     }, | ||||
|     "is-typedarray": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", | ||||
|       "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" | ||||
|     }, | ||||
|     "isstream": { | ||||
|       "version": "0.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", | ||||
|       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" | ||||
|     }, | ||||
|     "js-yaml": { | ||||
|       "version": "3.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", | ||||
|       "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", | ||||
|       "requires": { | ||||
|         "argparse": "1.0.9", | ||||
|         "esprima": "4.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "jsbn": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", | ||||
|       "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", | ||||
|       "optional": true | ||||
|     }, | ||||
|     "json-schema": { | ||||
|       "version": "0.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", | ||||
|       "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" | ||||
|     }, | ||||
|     "json-stable-stringify": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", | ||||
|       "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", | ||||
|       "requires": { | ||||
|         "jsonify": "0.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "json-stringify-safe": { | ||||
|       "version": "5.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", | ||||
|       "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" | ||||
|     }, | ||||
|     "jsonfile": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", | ||||
|       "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", | ||||
|       "requires": { | ||||
|         "graceful-fs": "4.1.11" | ||||
|       } | ||||
|     }, | ||||
|     "jsonify": { | ||||
|       "version": "0.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", | ||||
|       "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" | ||||
|     }, | ||||
|     "jsprim": { | ||||
|       "version": "1.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", | ||||
|       "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", | ||||
|       "requires": { | ||||
|         "assert-plus": "1.0.0", | ||||
|         "extsprintf": "1.3.0", | ||||
|         "json-schema": "0.2.3", | ||||
|         "verror": "1.10.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "assert-plus": { | ||||
|           "version": "1.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", | ||||
|           "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "keyboard-layout": { | ||||
|       "version": "2.0.13", | ||||
|       "resolved": "https://registry.npmjs.org/keyboard-layout/-/keyboard-layout-2.0.13.tgz", | ||||
|       "integrity": "sha512-WxVc3bBITttHozSyEYPsyr5rN2KQuXtEaXMlQfQjEze1JrkLw30yH/bcNn1IGx48b+tdOdybpnq++JFLU2FaZg==", | ||||
|       "requires": { | ||||
|         "event-kit": "2.4.0", | ||||
|         "nan": "2.8.0" | ||||
|       } | ||||
|     }, | ||||
|     "lazy-val": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.2.tgz", | ||||
|       "integrity": "sha512-2BaSu6qVnicKdWQPysrffZVFAKcPcZQ/q2YyeSjAxWaJlvCvKSrkcvsSHlleeIfA//fW2goTcYDTy2cBLN7+PQ==" | ||||
|     }, | ||||
|     "lodash.assign": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", | ||||
|       "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" | ||||
|     }, | ||||
|     "lodash.get": { | ||||
|       "version": "4.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", | ||||
|       "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" | ||||
|     }, | ||||
|     "lodash.isequal": { | ||||
|       "version": "4.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", | ||||
|       "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" | ||||
|     }, | ||||
|     "lru-cache": { | ||||
|       "version": "4.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", | ||||
|       "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", | ||||
|       "requires": { | ||||
|         "pseudomap": "1.0.2", | ||||
|         "yallist": "2.1.2" | ||||
|       } | ||||
|     }, | ||||
|     "mime-db": { | ||||
|       "version": "1.30.0", | ||||
|       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", | ||||
|       "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" | ||||
|     }, | ||||
|     "mime-types": { | ||||
|       "version": "2.1.17", | ||||
|       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", | ||||
|       "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", | ||||
|       "requires": { | ||||
|         "mime-db": "1.30.0" | ||||
|       } | ||||
|     }, | ||||
|     "minimatch": { | ||||
|       "version": "3.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", | ||||
|       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", | ||||
|       "requires": { | ||||
|         "brace-expansion": "1.1.8" | ||||
|       } | ||||
|     }, | ||||
|     "minimist": { | ||||
|       "version": "0.0.8", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", | ||||
|       "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" | ||||
|     }, | ||||
|     "mkdirp": { | ||||
|       "version": "0.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", | ||||
|       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", | ||||
|       "requires": { | ||||
|         "minimist": "0.0.8" | ||||
|       } | ||||
|     }, | ||||
|     "ms": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||
|     }, | ||||
|     "nan": { | ||||
|       "version": "2.8.0", | ||||
|       "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", | ||||
|       "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" | ||||
|     }, | ||||
|     "node-json-db": { | ||||
|       "version": "0.7.3", | ||||
|       "resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-0.7.3.tgz", | ||||
|       "integrity": "sha1-v2Mf9NTPQhHL3/5srmqq/m7lTN8=", | ||||
|       "requires": { | ||||
|         "mkdirp": "0.5.1" | ||||
|       } | ||||
|     }, | ||||
|     "node-mac-notifier": { | ||||
|       "version": "0.0.13", | ||||
|       "resolved": "https://registry.npmjs.org/node-mac-notifier/-/node-mac-notifier-0.0.13.tgz", | ||||
|       "integrity": "sha1-1kt27RgfR5XURFui060Nb3KY9+I=", | ||||
|       "optional": true, | ||||
|       "requires": { | ||||
|         "bindings": "1.3.0", | ||||
|         "event-target-shim": "1.1.1", | ||||
|         "node-uuid": "1.4.8" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "node-uuid": { | ||||
|           "version": "1.4.8", | ||||
|           "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", | ||||
|           "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "oauth-sign": { | ||||
|       "version": "0.8.2", | ||||
|       "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", | ||||
|       "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" | ||||
|     }, | ||||
|     "once": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | ||||
|       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", | ||||
|       "requires": { | ||||
|         "wrappy": "1.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "path-is-absolute": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", | ||||
|       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" | ||||
|     }, | ||||
|     "performance-now": { | ||||
|       "version": "0.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", | ||||
|       "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" | ||||
|     }, | ||||
|     "pify": { | ||||
|       "version": "2.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", | ||||
|       "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" | ||||
|     }, | ||||
|     "pseudomap": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", | ||||
|       "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" | ||||
|     }, | ||||
|     "punycode": { | ||||
|       "version": "1.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", | ||||
|       "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" | ||||
|     }, | ||||
|     "qs": { | ||||
|       "version": "6.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", | ||||
|       "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" | ||||
|     }, | ||||
|     "request": { | ||||
|       "version": "2.81.0", | ||||
|       "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", | ||||
|       "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", | ||||
|       "requires": { | ||||
|         "aws-sign2": "0.6.0", | ||||
|         "aws4": "1.6.0", | ||||
|         "caseless": "0.12.0", | ||||
|         "combined-stream": "1.0.5", | ||||
|         "extend": "3.0.1", | ||||
|         "forever-agent": "0.6.1", | ||||
|         "form-data": "2.1.4", | ||||
|         "har-validator": "4.2.1", | ||||
|         "hawk": "3.1.3", | ||||
|         "http-signature": "1.1.1", | ||||
|         "is-typedarray": "1.0.0", | ||||
|         "isstream": "0.1.2", | ||||
|         "json-stringify-safe": "5.0.1", | ||||
|         "mime-types": "2.1.17", | ||||
|         "oauth-sign": "0.8.2", | ||||
|         "performance-now": "0.2.0", | ||||
|         "qs": "6.4.0", | ||||
|         "safe-buffer": "5.1.1", | ||||
|         "stringstream": "0.0.5", | ||||
|         "tough-cookie": "2.3.3", | ||||
|         "tunnel-agent": "0.6.0", | ||||
|         "uuid": "3.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "rimraf": { | ||||
|       "version": "2.6.2", | ||||
|       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", | ||||
|       "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", | ||||
|       "requires": { | ||||
|         "glob": "7.1.2" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "glob": { | ||||
|           "version": "7.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", | ||||
|           "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", | ||||
|           "requires": { | ||||
|             "fs.realpath": "1.0.0", | ||||
|             "inflight": "1.0.6", | ||||
|             "inherits": "2.0.3", | ||||
|             "minimatch": "3.0.4", | ||||
|             "once": "1.4.0", | ||||
|             "path-is-absolute": "1.0.1" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "rxjs": { | ||||
|       "version": "5.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz", | ||||
|       "integrity": "sha512-oRYoIKWBU3Ic37fLA5VJu31VqQO4bWubRntcHSJ+cwaDQBwdnZ9x4zmhJfm/nFQ2E82/I4loSioHnACamrKGgA==", | ||||
|       "requires": { | ||||
|         "symbol-observable": "1.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "rxjs-serial-subscription": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/rxjs-serial-subscription/-/rxjs-serial-subscription-0.1.1.tgz", | ||||
|       "integrity": "sha1-pCsdsL8QlLCSMRkeJ3jKP8+e0Uc=", | ||||
|       "requires": { | ||||
|         "rxjs": "5.5.2" | ||||
|       } | ||||
|     }, | ||||
|     "safe-buffer": { | ||||
|       "version": "5.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", | ||||
|       "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" | ||||
|     }, | ||||
|     "sax": { | ||||
|       "version": "1.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", | ||||
|       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" | ||||
|     }, | ||||
|     "semver": { | ||||
|       "version": "5.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", | ||||
|       "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" | ||||
|     }, | ||||
|     "sntp": { | ||||
|       "version": "1.0.9", | ||||
|       "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", | ||||
|       "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", | ||||
|       "requires": { | ||||
|         "hoek": "2.16.3" | ||||
|       } | ||||
|     }, | ||||
|     "source-map": { | ||||
|       "version": "0.6.1", | ||||
|       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", | ||||
|       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" | ||||
|     }, | ||||
|     "source-map-support": { | ||||
|       "version": "0.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.0.tgz", | ||||
|       "integrity": "sha512-vUoN3I7fHQe0R/SJLKRdKYuEdRGogsviXFkHHo17AWaTGv17VLnxw+CFXvqy+y4ORZ3doWLQcxRYfwKrsd/H7Q==", | ||||
|       "requires": { | ||||
|         "source-map": "0.6.1" | ||||
|       } | ||||
|     }, | ||||
|     "spawn-rx": { | ||||
|       "version": "2.0.12", | ||||
|       "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz", | ||||
|       "integrity": "sha512-gOPXiQQFQ9lTOLuys0iMn3jfxxv9c7zzwhbYLOEbQGvEShHVJ5sSR1oD3Daj88os7jKArDYT7rbOKdvNhe7iEg==", | ||||
|       "requires": { | ||||
|         "debug": "2.6.9", | ||||
|         "lodash.assign": "4.2.0", | ||||
|         "rxjs": "5.5.2" | ||||
|       } | ||||
|     }, | ||||
|     "sprintf-js": { | ||||
|       "version": "1.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", | ||||
|       "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" | ||||
|     }, | ||||
|     "sshpk": { | ||||
|       "version": "1.13.1", | ||||
|       "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", | ||||
|       "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", | ||||
|       "requires": { | ||||
|         "asn1": "0.2.3", | ||||
|         "assert-plus": "1.0.0", | ||||
|         "bcrypt-pbkdf": "1.0.1", | ||||
|         "dashdash": "1.14.1", | ||||
|         "ecc-jsbn": "0.1.1", | ||||
|         "getpass": "0.1.7", | ||||
|         "jsbn": "0.1.1", | ||||
|         "tweetnacl": "0.14.5" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "assert-plus": { | ||||
|           "version": "1.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", | ||||
|           "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "stringstream": { | ||||
|       "version": "0.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", | ||||
|       "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" | ||||
|     }, | ||||
|     "symbol-observable": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.1.0.tgz", | ||||
|       "integrity": "sha512-dQoid9tqQ+uotGhuTKEY11X4xhyYePVnqGSoSm3OGKh2E8LZ6RPULp1uXTctk33IeERlrRJYoVSBglsL05F5Uw==" | ||||
|     }, | ||||
|     "tough-cookie": { | ||||
|       "version": "2.3.3", | ||||
|       "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", | ||||
|       "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", | ||||
|       "requires": { | ||||
|         "punycode": "1.4.1" | ||||
|       } | ||||
|     }, | ||||
|     "tunnel-agent": { | ||||
|       "version": "0.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", | ||||
|       "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", | ||||
|       "requires": { | ||||
|         "safe-buffer": "5.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "tweetnacl": { | ||||
|       "version": "0.14.5", | ||||
|       "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", | ||||
|       "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", | ||||
|       "optional": true | ||||
|     }, | ||||
|     "underscore": { | ||||
|       "version": "1.8.3", | ||||
|       "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", | ||||
|       "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" | ||||
|     }, | ||||
|     "universalify": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", | ||||
|       "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" | ||||
|     }, | ||||
|     "untildify": { | ||||
|       "version": "3.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.2.tgz", | ||||
|       "integrity": "sha1-fx8wIFWz/qDz6B3HjrNnZstl4/E=" | ||||
|     }, | ||||
|     "uuid": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", | ||||
|       "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" | ||||
|     }, | ||||
|     "verror": { | ||||
|       "version": "1.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", | ||||
|       "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", | ||||
|       "requires": { | ||||
|         "assert-plus": "1.0.0", | ||||
|         "core-util-is": "1.0.2", | ||||
|         "extsprintf": "1.3.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "assert-plus": { | ||||
|           "version": "1.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", | ||||
|           "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "winreg": { | ||||
|       "version": "1.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.2.tgz", | ||||
|       "integrity": "sha1-hQmvo7ccW70RCm18YkfsZ3NsWY8=" | ||||
|     }, | ||||
|     "wrappy": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | ||||
|       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" | ||||
|     }, | ||||
|     "wurl": { | ||||
|       "version": "2.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/wurl/-/wurl-2.5.0.tgz", | ||||
|       "integrity": "sha1-g7qrSEi5hmnSFISg/NmjSrpXKOk=" | ||||
|     }, | ||||
|     "xmlhttprequest": { | ||||
|       "version": "1.8.0", | ||||
|       "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", | ||||
|       "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" | ||||
|     }, | ||||
|     "yallist": { | ||||
|       "version": "2.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", | ||||
|       "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,14 +1,13 @@ | ||||
| { | ||||
|   "name": "zulip", | ||||
|   "productName": "Zulip", | ||||
|   "version": "1.4.0", | ||||
|   "version": "1.8.1", | ||||
|   "description": "Zulip Desktop App", | ||||
|   "license": "Apache-2.0", | ||||
|   "email": "<svnitakash@gmail.com>", | ||||
|   "copyright": "©2017 Kandra Labs, Inc.", | ||||
|   "copyright": "Kandra Labs, Inc.", | ||||
|   "author": { | ||||
|     "name": "Kandra Labs, Inc.", | ||||
|     "email": "svnitakash@gmail.com" | ||||
|     "email": "support@zulipchat.com" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
| @@ -27,16 +26,17 @@ | ||||
|     "InstantMessaging" | ||||
|   ], | ||||
|   "dependencies": { | ||||
|     "electron-debug": "1.4.0", | ||||
|     "auto-launch": "5.0.1", | ||||
|     "electron-is-dev": "0.3.0", | ||||
|     "electron-localshortcut": "2.0.2", | ||||
|     "electron-log": "2.2.7", | ||||
|     "electron-spellchecker": "1.2.0", | ||||
|     "electron-updater": "2.8.9", | ||||
|     "electron-spellchecker": "1.1.2", | ||||
|     "electron-updater": "2.18.2", | ||||
|     "electron-window-state": "4.1.1", | ||||
|     "node-json-db": "0.7.3", | ||||
|     "request": "2.81.0", | ||||
|     "wurl": "2.5.0", | ||||
|     "electron-window-state": "4.1.1", | ||||
|     "auto-launch": "5.0.1" | ||||
|     "wurl": "2.5.0" | ||||
|   }, | ||||
|   "optionalDependencies": { | ||||
|     "node-mac-notifier": "0.0.13" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -7,25 +7,37 @@ | ||||
| 	<body> | ||||
| 	<div class="about"> | ||||
| 		<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"> | ||||
| 			<p class="detail maintainer">Maintained by Zulip</p> | ||||
| 			<p class="detail license">Available under the Apache License</p> | ||||
| 			<a class="bug" onclick="linkInBrowser()" href="#">Found bug?</a> | ||||
| 			<p class="detail maintainer"> | ||||
| 				Maintained by <a onclick="linkInBrowser('website')">Zulip</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> | ||||
| 	<script> | ||||
|  | ||||
| 	const app = require('electron').remote.app; | ||||
| 	const version_tag = document.getElementById('version'); | ||||
| 	version_tag.innerHTML = 'version ' + 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." | ||||
| 	const { app } = require('electron').remote; | ||||
| 	const { shell } = require('electron'); | ||||
| 	const version_tag = document.querySelector('#version'); | ||||
| 	version_tag.innerHTML = 'v' + app.getVersion(); | ||||
|  | ||||
| 	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); | ||||
| 	} | ||||
| 	</script> | ||||
|   | ||||
| @@ -1,21 +1,23 @@ | ||||
| body { | ||||
| 	background: #fafafa; | ||||
| 	font-family: menu, "Helvetica Neue", sans-serif; | ||||
| 	-webkit-font-smoothing: antialiased; | ||||
| 	-webkit-font-smoothing: subpixel-antialiased; | ||||
| } | ||||
|  | ||||
| .logo { | ||||
| 	display: block; | ||||
| 	margin: 0 auto; | ||||
| 	margin: -40px auto; | ||||
| } | ||||
|  | ||||
| #version { | ||||
| 	color: #aaa; | ||||
| 	font-size: 0.9em; | ||||
| 	color: #444343; | ||||
| 	font-size: 1.3em; | ||||
| 	padding-top: 40px; | ||||
| } | ||||
|  | ||||
| .about { | ||||
| 	margin-top: 50px; | ||||
| 	margin: 25vh auto; | ||||
| 	height: 25vh; | ||||
| 	text-align: center; | ||||
| } | ||||
|  | ||||
| @@ -42,9 +44,9 @@ body { | ||||
| } | ||||
|  | ||||
| .maintenance-info { | ||||
| 	cursor: pointer; | ||||
| 	position: absolute; | ||||
| 	width: 100%; | ||||
| 	bottom: 20px; | ||||
| 	left: 0px; | ||||
| 	color: #444; | ||||
| } | ||||
| @@ -52,7 +54,6 @@ body { | ||||
| .maintenance-info p { | ||||
| 	margin: 0; | ||||
| 	font-size: 1em; | ||||
|  | ||||
| 	width: 100%; | ||||
| } | ||||
|  | ||||
| @@ -71,3 +72,11 @@ body { | ||||
| .maintenance-info .bug:hover { | ||||
| 	background-color: #32a692; | ||||
| } | ||||
|  | ||||
| p.detail a { | ||||
| 	color: #355f4c; | ||||
| } | ||||
|  | ||||
| p.detail a:hover { | ||||
| 	text-decoration: underline; | ||||
| } | ||||
|   | ||||
| @@ -18,7 +18,7 @@ body { | ||||
|     background-position: center; | ||||
| } | ||||
|  | ||||
| #sidebar { | ||||
| .toggle-sidebar { | ||||
|     background: #222c31; | ||||
|     width: 54px; | ||||
|     padding: 27px 0 20px 0; | ||||
| @@ -27,6 +27,21 @@ body { | ||||
|     flex-direction: column; | ||||
|     -webkit-app-region: drag; | ||||
|     overflow: hidden; | ||||
|     transition: all 0.5s ease; | ||||
| } | ||||
|  | ||||
| .toggle-sidebar div { | ||||
|     transition: all 0.5s ease-out; | ||||
| } | ||||
|  | ||||
| .sidebar-hide { | ||||
|     width: 0; | ||||
|     transition: all 0.8s ease; | ||||
| } | ||||
|  | ||||
| .sidebar-hide div { | ||||
|     transform: translateX(-100%); | ||||
|     transition: all 0.6s ease-out; | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
| @@ -258,6 +273,33 @@ webview:focus { | ||||
|     right: 68px; | ||||
| } | ||||
|  | ||||
| #add-server-tooltip, | ||||
| .server-tooltip { | ||||
|     font-family: 'arial'; | ||||
|     background: #222c31; | ||||
|     left: 56px; | ||||
|     padding: 10px 20px; | ||||
|     position: fixed; | ||||
|     margin-top: 8px; | ||||
|     z-index: 5000 !important; | ||||
|     color: #fff; | ||||
|     border-radius: 4px; | ||||
|     text-align: center; | ||||
|     width: max-content; | ||||
|     font-size: 14px; | ||||
| } | ||||
|  | ||||
| #add-server-tooltip:after, | ||||
| .server-tooltip:after { | ||||
|     content: " "; | ||||
|     border-top: 8px solid transparent; | ||||
|     border-bottom: 8px solid transparent; | ||||
|     border-right: 8px solid #222c31; | ||||
|     position: absolute; | ||||
|     top: 10px; | ||||
|     left: -5px; | ||||
| } | ||||
|  | ||||
| #collapse-button { | ||||
|     bottom: 30px; | ||||
|     left: 20px; | ||||
| @@ -285,7 +327,9 @@ webview:focus { | ||||
|     display: none !important; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Full screen Popup container  */ | ||||
|  | ||||
| .popup .popuptext { | ||||
|     visibility: hidden; | ||||
|     background-color: #555; | ||||
|   | ||||
| @@ -12,35 +12,42 @@ body { | ||||
| } | ||||
|  | ||||
| kbd { | ||||
|     padding: 0.1em 0.6em; | ||||
|     padding: 0.3em 0.8em; | ||||
|     border: 1px solid #ccc; | ||||
|     font-size: 12px; | ||||
|     font-family: Arial,Helvetica,sans-serif; | ||||
|     background-color: #f7f7f7; | ||||
|     color: #333; | ||||
|     -moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; | ||||
|     -webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; | ||||
|     box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; | ||||
|     -moz-border-radius: 3px; | ||||
|     -webkit-border-radius: 3px; | ||||
|     border-radius: 3px; | ||||
|     font-size: 15px; | ||||
|     font-family: Courier New, Courier, monospace; | ||||
|     background-color: #383430; | ||||
|     color: #ededed; | ||||
|     display: inline-block; | ||||
|     margin: 0 0.1em; | ||||
|     text-shadow: 0 1px 0 #fff; | ||||
|     line-height: 1.4; | ||||
|     font-weight: bold; | ||||
|     white-space: nowrap; | ||||
| } | ||||
|  | ||||
| table, th, td { | ||||
|     border: 1px solid #ddd; | ||||
| table, | ||||
| th, | ||||
| td { | ||||
|     border-collapse: collapse; | ||||
|     color: #383430; | ||||
| } | ||||
|  | ||||
| table { width: 85%; } | ||||
| table { | ||||
|     width: 100%; | ||||
|     margin-top: 18px; | ||||
|     margin-bottom: 18px; | ||||
| } | ||||
|  | ||||
| table tr:nth-child(even) { background-color: #f2f2f2; } | ||||
| table tr:nth-child(even) { | ||||
|     background-color: #f7eee6; | ||||
| } | ||||
|  | ||||
| td { padding: 5px; } | ||||
| table tr:nth-child(odd) { | ||||
|     background-color: #fff8ef; | ||||
| } | ||||
|  | ||||
| td { | ||||
|     padding: 5px; | ||||
| } | ||||
|  | ||||
| td:nth-child(odd) { | ||||
|     text-align: right; | ||||
| @@ -51,9 +58,12 @@ td:nth-child(odd) { | ||||
|     font-family: 'Material Icons'; | ||||
|     font-style: normal; | ||||
|     font-weight: 400; | ||||
|   src: local('Material Icons'), | ||||
|        local('MaterialIcons-Regular'), | ||||
|        url(../fonts/MaterialIcons-Regular.ttf) format('truetype'); | ||||
|     src: local('Material Icons'), local('MaterialIcons-Regular'), url(../fonts/MaterialIcons-Regular.ttf) format('truetype'); | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|     font-family: 'Montserrat'; | ||||
|     src: url(../fonts/Montserrat-Regular.ttf) format('truetype'); | ||||
| } | ||||
|  | ||||
| .material-icons { | ||||
| @@ -78,7 +88,7 @@ td:nth-child(odd) { | ||||
| #content { | ||||
|     display: flex; | ||||
|     height: 100%; | ||||
|     font-family: sans-serif; | ||||
|     font-family: 'Montserrat'; | ||||
| } | ||||
|  | ||||
| #sidebar { | ||||
| @@ -116,7 +126,9 @@ td:nth-child(odd) { | ||||
|  | ||||
| #settings-header { | ||||
|     font-size: 22px; | ||||
|     color: #5c6166; | ||||
|     color: #222c31; | ||||
|     font-weight: bold; | ||||
|     text-transform: uppercase; | ||||
| } | ||||
|  | ||||
| #settings-container { | ||||
| @@ -133,8 +145,15 @@ td:nth-child(odd) { | ||||
|  | ||||
| .title { | ||||
|     padding: 4px 0 6px 0; | ||||
|     font-weight: 500; | ||||
|     color: #222c31; | ||||
| } | ||||
|  | ||||
| .page-title { | ||||
|     color: #222c31; | ||||
|     font-size: 15px; | ||||
|     font-weight: bold; | ||||
|     color: #1e1e1e; | ||||
|     padding: 4px 0 6px 0; | ||||
| } | ||||
|  | ||||
| .sub-title { | ||||
| @@ -181,16 +200,18 @@ img.server-info-icon { | ||||
| .setting-input-value { | ||||
|     flex-grow: 1; | ||||
|     font-size: 14px; | ||||
|     height: 24px; | ||||
|     border: none; | ||||
|     border-bottom: #ededed 1px solid; | ||||
|     height: 22px; | ||||
|     border-radius: 3px; | ||||
|     padding: 7px; | ||||
|     border: #ededed 2px solid; | ||||
|     outline-width: 0; | ||||
|     background: transparent; | ||||
|     max-width: 500px; | ||||
| } | ||||
|  | ||||
| .setting-input-value:focus { | ||||
|     border-bottom: #7cb980 1px solid; | ||||
|     border: #7cb980 2px solid; | ||||
|     border-radius: 3px; | ||||
| } | ||||
|  | ||||
| .setting-block { | ||||
| @@ -210,14 +231,13 @@ img.server-info-icon { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     padding: 0 10px; | ||||
|     border-radius: 2px; | ||||
|     margin-right: 10px; | ||||
| } | ||||
|  | ||||
| .action i { | ||||
|     margin-right: 5px; | ||||
|     font-size: 18px; | ||||
|     line-height: 27px; | ||||
|     line-height: 26px; | ||||
| } | ||||
|  | ||||
| .settings-pane { | ||||
| @@ -242,9 +262,14 @@ img.server-info-icon { | ||||
|     padding: 12px 30px; | ||||
|     margin: 10px 0 20px 0; | ||||
|     background: #fff; | ||||
|     border-radius: 2px; | ||||
|     width: 540px; | ||||
|     box-shadow: 1px 2px 4px #bcbcbc; | ||||
|     width: 70%; | ||||
|     transition: all 0.2s; | ||||
| } | ||||
|  | ||||
| .settings-card:hover { | ||||
|     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 { | ||||
| @@ -253,15 +278,19 @@ img.server-info-icon { | ||||
| } | ||||
|  | ||||
| .red { | ||||
|     color: #ef5350; | ||||
|     background: #ffebee; | ||||
|     border: 1px solid #ef5350; | ||||
|     color: #ffffff; | ||||
|     background: #ef5350; | ||||
|     padding: 3px; | ||||
|     padding-right: 10px; | ||||
|     padding-left: 10px; | ||||
| } | ||||
|  | ||||
| .green { | ||||
|     color: #388E3C; | ||||
|     background: #E8F5E9; | ||||
|     border: 1px solid #388E3C; | ||||
| .blue { | ||||
|     color: #ffffff; | ||||
|     background: #4EBFAC; | ||||
|     padding: 3px; | ||||
|     padding-right: 10px; | ||||
|     padding-left: 10px; | ||||
| } | ||||
|  | ||||
| .grey { | ||||
| @@ -272,9 +301,10 @@ img.server-info-icon { | ||||
|  | ||||
| .setting-row { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
|     width: 100%; | ||||
|     margin: 6px 0; | ||||
|     margin: 6px; | ||||
| } | ||||
|  | ||||
| .code { | ||||
| @@ -289,26 +319,98 @@ i.open-tab-button { | ||||
|  | ||||
| .reset-data-button { | ||||
|     display: inline-block; | ||||
|     border: none; | ||||
|     padding: 10px; | ||||
|     width: 120px; | ||||
|     cursor: pointer; | ||||
|     font-size: 11px; | ||||
|     font-size: 13px; | ||||
|     transition: background-color 0.2s ease; | ||||
|     text-decoration: none; | ||||
|  | ||||
| } | ||||
|  | ||||
| .reset-data-button:hover { | ||||
|     background-color: #32a692; | ||||
|     background-color: #3c9f8d; | ||||
|     color: #fff; | ||||
| } | ||||
|  | ||||
| #open-shortcuts-url { | ||||
| 		color: #08c; | ||||
| #server-info-container { | ||||
|     min-height: calc(100% - 235px); | ||||
| } | ||||
|  | ||||
| #create-organization-container { | ||||
|     font-size: 1.15em; | ||||
|     position: fixed; | ||||
|     bottom: 15px; | ||||
| } | ||||
|  | ||||
| #create-organization-container i { | ||||
|     position: relative; | ||||
|     top: 3px; | ||||
| } | ||||
|  | ||||
| #open-create-org-link { | ||||
|     color: #666; | ||||
|     cursor: pointer; | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| #open-shortcuts-url:hover { | ||||
| 		color: #005580;; | ||||
| #open-create-org-link:hover { | ||||
|     color: #005580; | ||||
|     ; | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .toggle { | ||||
|     position: absolute; | ||||
|     margin-left: -9999px; | ||||
|     visibility: hidden; | ||||
| } | ||||
|  | ||||
| .toggle+label { | ||||
|     display: block; | ||||
|     position: relative; | ||||
|     cursor: pointer; | ||||
|     outline: none; | ||||
|     user-select: none; | ||||
| } | ||||
|  | ||||
| input.toggle-round+label { | ||||
|     padding: 2px; | ||||
|     width: 50px; | ||||
|     height: 25px; | ||||
|     background-color: #dddddd; | ||||
|     border-radius: 25px; | ||||
| } | ||||
|  | ||||
| input.toggle-round+label:before, | ||||
| input.toggle-round+label:after { | ||||
|     display: block; | ||||
|     position: absolute; | ||||
|     top: 2px; | ||||
|     left: 2px; | ||||
|     bottom: 2px; | ||||
|     content: ""; | ||||
| } | ||||
|  | ||||
| input.toggle-round+label:before { | ||||
|     right: 2px; | ||||
|     background-color: #f1f1f1; | ||||
|     border-radius: 25px; | ||||
|     transition: background 0.4s; | ||||
| } | ||||
|  | ||||
| input.toggle-round+label:after { | ||||
|     width: 25px; | ||||
|     height: 25px; | ||||
|     background-color: #fff; | ||||
|     border-radius: 100%; | ||||
|     transition: margin 0.4s; | ||||
| } | ||||
|  | ||||
| input.toggle-round:checked+label:before { | ||||
|     background-color: #4EBFAC; | ||||
| } | ||||
|  | ||||
| input.toggle-round:checked+label:after { | ||||
|     margin-left: 25px; | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								app/renderer/fonts/Montserrat-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/renderer/fonts/Montserrat-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -4,7 +4,7 @@ const Tab = require(__dirname + '/../components/tab.js'); | ||||
|  | ||||
| class FunctionalTab extends Tab { | ||||
| 	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"> | ||||
| 						<i class="material-icons">close</i> | ||||
| 					</div> | ||||
|   | ||||
| @@ -7,7 +7,8 @@ const {ipcRenderer} = require('electron'); | ||||
|  | ||||
| class ServerTab extends Tab { | ||||
| 	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-tab-badge"></div> | ||||
| 					<div class="server-tab"> | ||||
| 					<img class="server-icons" src='${this.props.icon}'/> | ||||
|   | ||||
| @@ -21,6 +21,8 @@ class Tab extends BaseComponent { | ||||
|  | ||||
| 	registerListeners() { | ||||
| 		this.$el.addEventListener('click', this.props.onClick); | ||||
| 		this.$el.addEventListener('mouseover', this.props.onHover); | ||||
| 		this.$el.addEventListener('mouseout', this.props.onHoverOut); | ||||
| 	} | ||||
|  | ||||
| 	isLoading() { | ||||
|   | ||||
| @@ -4,12 +4,14 @@ const path = require('path'); | ||||
| const fs = require('fs'); | ||||
|  | ||||
| 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 LinkUtil = require(__dirname + '/../utils/link-util.js'); | ||||
| const {shell} = require('electron').remote; | ||||
| const { shell, app } = require('electron').remote; | ||||
|  | ||||
| const BaseComponent = require(__dirname + '/../components/base.js'); | ||||
|  | ||||
| const shouldSilentWebview = ConfigUtil.getConfigItem('silent'); | ||||
| class WebView extends BaseComponent { | ||||
| 	constructor(props) { | ||||
| 		super(); | ||||
| @@ -24,6 +26,7 @@ class WebView extends BaseComponent { | ||||
| 	template() { | ||||
| 		return `<webview | ||||
| 					class="disabled" | ||||
| 					data-tab-id="${this.props.tabIndex}" | ||||
| 					src="${this.props.url}" | ||||
| 					${this.props.nodeIntegration ? 'nodeIntegration' : ''} | ||||
| 					disablewebsecurity | ||||
| @@ -54,12 +57,28 @@ class WebView extends BaseComponent { | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		if (shouldSilentWebview) { | ||||
| 			this.$el.addEventListener('dom-ready', () => { | ||||
| 				this.$el.setAudioMuted(true); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		this.$el.addEventListener('page-title-updated', event => { | ||||
| 			const { title } = event; | ||||
| 			this.badgeCount = this.getBadgeCount(title); | ||||
| 			this.props.onTitleChange(); | ||||
| 		}); | ||||
|  | ||||
| 		this.$el.addEventListener('page-favicon-updated', event => { | ||||
| 			const { favicons } = event; | ||||
| 			// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like | ||||
| 			// https://chat.zulip.org/static/images/favicon/favicon-pms.png | ||||
| 			if (favicons[0].indexOf('favicon-pms') > 0 && process.platform === 'darwin') { | ||||
| 				// This api is only supported on macOS | ||||
| 				app.dock.setBadge('●'); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		this.$el.addEventListener('dom-ready', () => { | ||||
| 			if (this.props.role === 'server') { | ||||
| 				this.$el.classList.add('onload'); | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| require(__dirname + '/js/tray.js'); | ||||
| const { ipcRenderer, remote } = require('electron'); | ||||
|  | ||||
| const { session } = remote; | ||||
|  | ||||
| require(__dirname + '/js/tray.js'); | ||||
| const DomainUtil = require(__dirname + '/js/utils/domain-util.js'); | ||||
| const WebView = require(__dirname + '/js/components/webview.js'); | ||||
| const ServerTab = require(__dirname + '/js/components/server-tab.js'); | ||||
| @@ -21,8 +21,11 @@ class ServerManagerView { | ||||
| 		this.$settingsButton = $actionsContainer.querySelector('#settings-action'); | ||||
| 		this.$webviewsContainer = document.getElementById('webviews-container'); | ||||
|  | ||||
| 		this.$addServerTooltip = document.getElementById('add-server-tooltip'); | ||||
| 		this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip'); | ||||
| 		this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip'); | ||||
| 		this.$serverIconTooltip = document.getElementsByClassName('server-tooltip'); | ||||
|  | ||||
| 		this.$sidebar = document.getElementById('sidebar'); | ||||
|  | ||||
| 		this.$fullscreenPopup = document.getElementById('fullscreen-popup'); | ||||
| @@ -32,6 +35,7 @@ class ServerManagerView { | ||||
| 		this.activeTabIndex = -1; | ||||
| 		this.tabs = []; | ||||
| 		this.functionalTabs = {}; | ||||
| 		this.tabIndex = 0; | ||||
| 	} | ||||
|  | ||||
| 	init() { | ||||
| @@ -40,6 +44,7 @@ class ServerManagerView { | ||||
| 			this.initTabs(); | ||||
| 			this.initActions(); | ||||
| 			this.registerIpcs(); | ||||
| 			this.initDefaultSettings(); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| @@ -62,6 +67,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() { | ||||
| 		const showSidebar = ConfigUtil.getConfigItem('showSidebar', true); | ||||
| 		this.toggleSidebar(showSidebar); | ||||
| @@ -75,24 +113,28 @@ class ServerManagerView { | ||||
| 				DomainUtil.updateSavedServer(servers[i].url, i); | ||||
| 				this.activateTab(i); | ||||
| 			} | ||||
| 			this.activateTab(0); | ||||
| 			// Open last active tab | ||||
| 			this.activateTab(ConfigUtil.getConfigItem('lastActiveTab')); | ||||
| 		} else { | ||||
| 			this.openSettings('Servers'); | ||||
| 		} | ||||
|  | ||||
| 		ipcRenderer.send('local-shortcuts', true); | ||||
| 	} | ||||
|  | ||||
| 	initServer(server, index) { | ||||
| 		const tabIndex = this.getTabIndex(); | ||||
| 		this.tabs.push(new ServerTab({ | ||||
| 			role: 'server', | ||||
| 			icon: server.icon, | ||||
| 			$root: this.$tabsContainer, | ||||
| 			onClick: this.activateTab.bind(this, index), | ||||
| 			onClick: this.activateLastTab.bind(this, index), | ||||
| 			index, | ||||
| 			tabIndex, | ||||
| 			onHover: this.onHover.bind(this, index, server.alias), | ||||
| 			onHoverOut: this.onHoverOut.bind(this, index), | ||||
| 			webview: new WebView({ | ||||
| 				$root: this.$webviewsContainer, | ||||
| 				index, | ||||
| 				tabIndex, | ||||
| 				url: server.url, | ||||
| 				name: server.alias, | ||||
| 				isActive: () => { | ||||
| @@ -117,10 +159,24 @@ class ServerManagerView { | ||||
| 			this.openSettings('General'); | ||||
| 		}); | ||||
|  | ||||
| 		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.$settingsButton, this.$settingsTooltip); | ||||
| 		this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip); | ||||
| 	} | ||||
|  | ||||
| 	getTabIndex() { | ||||
| 		const currentIndex = this.tabIndex; | ||||
| 		this.tabIndex++; | ||||
| 		return currentIndex; | ||||
| 	} | ||||
|  | ||||
| 	sidebarHoverEvent(SidebarButton, SidebarTooltip) { | ||||
| 		SidebarButton.addEventListener('mouseover', () => { | ||||
| 			SidebarTooltip.removeAttribute('style'); | ||||
| @@ -130,6 +186,15 @@ class ServerManagerView { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	onHover(index, serverName) { | ||||
| 		this.$serverIconTooltip[index].innerHTML = serverName; | ||||
| 		this.$serverIconTooltip[index].removeAttribute('style'); | ||||
| 	} | ||||
|  | ||||
| 	onHoverOut(index) { | ||||
| 		this.$serverIconTooltip[index].style.display = 'none'; | ||||
| 	} | ||||
|  | ||||
| 	openFunctionalTab(tabProps) { | ||||
| 		if (this.functionalTabs[tabProps.name] !== undefined) { | ||||
| 			this.activateTab(this.functionalTabs[tabProps.name]); | ||||
| @@ -138,16 +203,19 @@ class ServerManagerView { | ||||
|  | ||||
| 		this.functionalTabs[tabProps.name] = this.tabs.length; | ||||
|  | ||||
| 		const tabIndex = this.getTabIndex(); | ||||
| 		this.tabs.push(new FunctionalTab({ | ||||
| 			role: 'function', | ||||
| 			materialIcon: tabProps.materialIcon, | ||||
| 			$root: this.$tabsContainer, | ||||
| 			index: this.functionalTabs[tabProps.name], | ||||
| 			tabIndex, | ||||
| 			onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]), | ||||
| 			onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]), | ||||
| 			webview: new WebView({ | ||||
| 				$root: this.$webviewsContainer, | ||||
| 				index: this.functionalTabs[tabProps.name], | ||||
| 				tabIndex, | ||||
| 				url: tabProps.url, | ||||
| 				name: tabProps.name, | ||||
| 				isActive: () => { | ||||
| @@ -188,8 +256,15 @@ class ServerManagerView { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	activateLastTab(index) { | ||||
| 		// Open last active tab | ||||
| 		ConfigUtil.setConfigItem('lastActiveTab', index); | ||||
| 		// Open all the tabs in background | ||||
| 		this.activateTab(index); | ||||
| 	} | ||||
|  | ||||
| 	activateTab(index, hideOldTab = true) { | ||||
| 		if (this.tabs[index].webview.loading) { | ||||
| 		if (!this.tabs[index]) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| @@ -208,6 +283,27 @@ class ServerManagerView { | ||||
| 			tabs: this.tabs, | ||||
| 			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) { | ||||
| @@ -235,16 +331,24 @@ class ServerManagerView { | ||||
| 		// Clear DOM elements | ||||
| 		this.$tabsContainer.innerHTML = ''; | ||||
| 		this.$webviewsContainer.innerHTML = ''; | ||||
|  | ||||
| 		// Destroy shortcuts | ||||
| 		ipcRenderer.send('local-shortcuts', false); | ||||
| 	} | ||||
|  | ||||
| 	reloadView() { | ||||
| 		// Save and remember the index of last active tab so that we can use it later | ||||
| 		const lastActiveTab = this.tabs[this.activeTabIndex].props.index; | ||||
| 		ConfigUtil.setConfigItem('lastActiveTab', lastActiveTab); | ||||
|  | ||||
| 		// Destroy the current view and re-initiate it | ||||
| 		this.destroyView(); | ||||
| 		this.initTabs(); | ||||
| 	} | ||||
|  | ||||
| 	// This will trigger when pressed CTRL/CMD + R [WIP] | ||||
| 	// It won't reload the current view properly when you add/delete a server. | ||||
| 	reloadCurrentView() { | ||||
| 		this.$reloadButton.click(); | ||||
| 	} | ||||
|  | ||||
| 	updateBadge() { | ||||
| 		let messageCountAll = 0; | ||||
| 		for (let i = 0; i < this.tabs.length; i++) { | ||||
| @@ -260,9 +364,9 @@ class ServerManagerView { | ||||
|  | ||||
| 	toggleSidebar(show) { | ||||
| 		if (show) { | ||||
| 			this.$sidebar.classList.remove('hidden'); | ||||
| 			this.$sidebar.classList.remove('sidebar-hide'); | ||||
| 		} else { | ||||
| 			this.$sidebar.classList.add('hidden'); | ||||
| 			this.$sidebar.classList.add('sidebar-hide'); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -295,7 +399,9 @@ class ServerManagerView { | ||||
|  | ||||
| 		ipcRenderer.on('open-about', this.openAbout.bind(this)); | ||||
|  | ||||
| 		ipcRenderer.on('reload-viewer', this.reloadView.bind(this)); | ||||
| 		ipcRenderer.on('reload-viewer', this.reloadView.bind(this, this.tabs[this.activeTabIndex].props.index)); | ||||
|  | ||||
| 		ipcRenderer.on('reload-current-viewer', this.reloadCurrentView.bind(this)); | ||||
|  | ||||
| 		ipcRenderer.on('hard-reload', () => { | ||||
| 			ipcRenderer.send('reload-full-app'); | ||||
| @@ -330,6 +436,18 @@ class ServerManagerView { | ||||
| 			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) => { | ||||
| 			// Create a canvas from unread messagecounts | ||||
| 			function createOverlayIcon(messageCount) { | ||||
|   | ||||
| @@ -1,24 +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; | ||||
| 		// calling super without passing arguments will fail to constuct Notification | ||||
| 		// and will result in no notification. It's a hack (not an ideal way) for deleting the window notification | ||||
| 		ConfigUtil.getConfigItem('showNotification') ? super(title, opts) : super(); // eslint-disable-line no-unused-expressions | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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; | ||||
							
								
								
									
										102
									
								
								app/renderer/js/notification/helpers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								app/renderer/js/notification/helpers.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| const { remote } = require('electron'); | ||||
|  | ||||
| // Do not change this | ||||
| const appId = 'org.zulip.zulip-electron'; | ||||
|  | ||||
| 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); | ||||
| 	}); | ||||
|  | ||||
| 	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 | ||||
| }; | ||||
							
								
								
									
										18
									
								
								app/renderer/js/notification/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/renderer/js/notification/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { | ||||
|   remote: { app } | ||||
| } = require('electron'); | ||||
|  | ||||
| const DefaultNotification = require('./default-notification'); | ||||
| const { appId } = 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; | ||||
| } | ||||
| @@ -45,7 +45,7 @@ class BadgeSettings { | ||||
|  | ||||
| 	updateOverlayIcon(messageCount, mainWindow) { | ||||
| 		if (!mainWindow.isFocused()) { | ||||
| 			mainWindow.flashFrame(true); | ||||
| 			mainWindow.flashFrame(ConfigUtil.getConfigItem('flashTaskbarOnMessage')); | ||||
| 		} | ||||
| 		if (messageCount === 0) { | ||||
| 			mainWindow.setOverlayIcon(null, ''); | ||||
|   | ||||
| @@ -19,14 +19,20 @@ class BaseSection extends BaseComponent { | ||||
| 	generateOptionTemplate(settingOption) { | ||||
| 		if (settingOption) { | ||||
| 			return ` | ||||
| 				<div class="action green"> | ||||
| 					<span>On</span> | ||||
| 				<div class="action"> | ||||
| 					<div class="switch"> | ||||
| 					  <input class="toggle toggle-round" type="checkbox" checked> | ||||
| 					  <label></label> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			`; | ||||
| 		} else { | ||||
| 			return ` | ||||
| 				<div class="action red"> | ||||
| 					<span>Off</span> | ||||
| 				<div class="action"> | ||||
| 					<div class="switch"> | ||||
| 					  <input class="toggle toggle-round" type="checkbox"> | ||||
| 					  <label></label> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			`; | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										37
									
								
								app/renderer/js/pages/preference/create-new-org.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/renderer/js/pages/preference/create-new-org.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const BaseComponent = require(__dirname + '/../../components/base.js'); | ||||
| const shell = require('electron').shell; | ||||
|  | ||||
| class CreateOrganziation extends BaseComponent { | ||||
| 	constructor(props) { | ||||
| 		super(); | ||||
| 		this.props = props; | ||||
| 	} | ||||
|  | ||||
| 	template() { | ||||
| 		return ` | ||||
| 			<div class="setting-row"> | ||||
| 				<div class="setting-description"> | ||||
| 					<span id="open-create-org-link">Or create a new organization on zulipchat.com<i class="material-icons open-tab-button">open_in_new</i></span> | ||||
| 				</div> | ||||
| 				<div class="setting-control"></div> | ||||
| 			</div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	init() { | ||||
| 		this.props.$root.innerHTML = this.template(); | ||||
| 		this.openCreateNewOrgExternalLink(); | ||||
| 	} | ||||
|  | ||||
| 	openCreateNewOrgExternalLink() { | ||||
| 		const link = 'https://zulipchat.com/beta/'; | ||||
| 		const externalCreateNewOrgEl = document.getElementById('open-create-org-link'); | ||||
| 		externalCreateNewOrgEl.addEventListener('click', () => { | ||||
| 			shell.openExternal(link); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = CreateOrganziation; | ||||
| @@ -1,11 +1,10 @@ | ||||
| 'use strict'; | ||||
| const path = require('path'); | ||||
|  | ||||
| const { ipcRenderer } = require('electron'); | ||||
| const { app, dialog } = require('electron').remote; | ||||
|  | ||||
| const { ipcRenderer, remote } = require('electron'); | ||||
| const fs = require('fs-extra'); | ||||
|  | ||||
| const { app, dialog } = remote; | ||||
| const currentBrowserWindow = remote.getCurrentWindow(); | ||||
| const BaseSection = require(__dirname + '/base-section.js'); | ||||
| const ConfigUtil = require(__dirname + '/../../utils/config-util.js'); | ||||
|  | ||||
| @@ -32,6 +31,10 @@ class GeneralSection extends BaseSection { | ||||
| 						<div class="setting-description">Show app unread badge</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 					<div class="setting-row" id="flash-taskbar-option" style= "display:${process.platform === 'win32' ? '' : 'none'}"> | ||||
| 						<div class="setting-description">Flash taskbar on new message</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div class="title">Desktop Notification</div> | ||||
| 				<div class="settings-card"> | ||||
| @@ -50,10 +53,6 @@ class GeneralSection extends BaseSection { | ||||
| 						<div class="setting-description">Get beta updates</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 					<div class="setting-row" id="autoupdate-option"> | ||||
| 					<div class="setting-description">Automatically install new updates</div> | ||||
| 					<div class="setting-control"></div> | ||||
| 				</div> | ||||
| 				</div> | ||||
| 				<div class="title">Functionality</div> | ||||
|                 <div class="settings-card"> | ||||
| @@ -61,13 +60,21 @@ class GeneralSection extends BaseSection { | ||||
| 						<div class="setting-description">Start app at login</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 					<div class="setting-row" id="start-minimize-option"> | ||||
| 						<div class="setting-description">Always start minimized</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 					<div class="setting-row" id="enable-spellchecker-option"> | ||||
| 						<div class="setting-description">Enable Spellchecker (requires restart)</div> | ||||
| 						<div class="setting-control"></div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div class="title">Reset Application Data</div> | ||||
|                 <div class="settings-card"> | ||||
| 					<div class="setting-row" id="resetdata-option"> | ||||
| 						<div class="setting-description">This will delete all application data including all added accounts and preferences | ||||
| 						</div> | ||||
| 						<button class="reset-data-button green">Reset App Data</button> | ||||
| 						<button class="reset-data-button blue">Reset App Data</button> | ||||
| 					</div> | ||||
| 				</div> | ||||
|             </div> | ||||
| @@ -78,13 +85,20 @@ class GeneralSection extends BaseSection { | ||||
| 		this.props.$root.innerHTML = this.template(); | ||||
| 		this.updateTrayOption(); | ||||
| 		this.updateBadgeOption(); | ||||
| 		this.updateUpdateOption(); | ||||
| 		this.updateAutoUpdateOption(); | ||||
| 		this.updateSilentOption(); | ||||
| 		this.updateUpdateOption(); | ||||
| 		this.updateSidebarOption(); | ||||
| 		this.updateStartAtLoginOption(); | ||||
| 		this.updateResetDataOption(); | ||||
| 		this.showDesktopNotification(); | ||||
| 		this.enableSpellchecker(); | ||||
| 		this.minimizeOnStart(); | ||||
|  | ||||
| 		// Platform specific settings | ||||
| 		// Flashing taskbar on Windows | ||||
| 		if (process.platform === 'win32') { | ||||
| 			this.updateFlashTaskbar(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	updateTrayOption() { | ||||
| @@ -113,6 +127,18 @@ class GeneralSection extends BaseSection { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateFlashTaskbar() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#flash-taskbar-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('flashTaskbarOnMessage'); | ||||
| 				ConfigUtil.setConfigItem('flashTaskbarOnMessage', newValue); | ||||
| 				this.updateFlashTaskbar(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateUpdateOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#betaupdate-option .setting-control'), | ||||
| @@ -125,18 +151,6 @@ class GeneralSection extends BaseSection { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateAutoUpdateOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#autoupdate-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('autoUpdate', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('autoUpdate'); | ||||
| 				ConfigUtil.setConfigItem('autoUpdate', newValue); | ||||
| 				this.updateAutoUpdateOption(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateSilentOption() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#silent-option .setting-control'), | ||||
| @@ -145,6 +159,7 @@ class GeneralSection extends BaseSection { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('silent', true); | ||||
| 				ConfigUtil.setConfigItem('silent', newValue); | ||||
| 				this.updateSilentOption(); | ||||
| 				currentBrowserWindow.send('toogle-silent', newValue); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| @@ -187,6 +202,18 @@ class GeneralSection extends BaseSection { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	enableSpellchecker() { | ||||
| 		this.generateSettingOption({ | ||||
| 			$element: document.querySelector('#enable-spellchecker-option .setting-control'), | ||||
| 			value: ConfigUtil.getConfigItem('enableSpellchecker', true), | ||||
| 			clickHandler: () => { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('enableSpellchecker'); | ||||
| 				ConfigUtil.setConfigItem('enableSpellchecker', newValue); | ||||
| 				this.enableSpellchecker(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	clearAppDataDialog() { | ||||
| 		const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.'; | ||||
| 		const getAppPath = path.join(app.getPath('appData'), app.getName()); | ||||
| @@ -212,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; | ||||
|   | ||||
| @@ -34,7 +34,7 @@ class NetworkSection extends BaseSection { | ||||
| 							<input class="setting-input-value" placeholder="e.g. foobar.com"/> | ||||
| 						</div> | ||||
| 						<div class="setting-row"> | ||||
| 							<div class="action green" id="proxy-save-action"> | ||||
| 							<div class="action blue" id="proxy-save-action"> | ||||
| 								<i class="material-icons">check_box</i> | ||||
| 								<span>Save</span> | ||||
| 							</div> | ||||
|   | ||||
| @@ -13,13 +13,14 @@ class NewServerForm extends BaseComponent { | ||||
| 		return ` | ||||
| 			<div class="settings-card"> | ||||
| 				<div class="server-info-right"> | ||||
| 					<div class="title">URL of Zulip organization</div> | ||||
| 					<div class="server-info-row"> | ||||
| 						<input class="setting-input-value" autofocus placeholder="Entert the URL of your Zulip organization..."/> | ||||
| 						<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or chat.your-organization.com"/> | ||||
| 					</div> | ||||
| 					<div class="server-info-row"> | ||||
| 						<div class="action green server-save-action"> | ||||
| 							<i class="material-icons">check_box</i> | ||||
| 							<span>Save</span> | ||||
| 						<div class="action blue server-save-action"> | ||||
| 							<i class="material-icons">add_box</i> | ||||
| 							<span>Add</span> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| @@ -42,11 +43,13 @@ class NewServerForm extends BaseComponent { | ||||
| 	} | ||||
|  | ||||
| 	submitFormHandler() { | ||||
| 		this.$saveServerButton.children[1].innerHTML = 'Adding...'; | ||||
| 		DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => { | ||||
| 			DomainUtil.addDomain(serverConf).then(() => { | ||||
| 				this.props.onChange(this.props.index); | ||||
| 			}); | ||||
| 		}, errorMessage => { | ||||
| 			this.$saveServerButton.children[1].innerHTML = 'Add'; | ||||
| 			alert(errorMessage); | ||||
| 		}); | ||||
| 	} | ||||
|   | ||||
| @@ -69,10 +69,27 @@ class PreferenceView extends BaseComponent { | ||||
| 		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() { | ||||
| 		ipcRenderer.on('switch-settings-nav', (event, 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); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ const BaseSection = require(__dirname + '/base-section.js'); | ||||
| const DomainUtil = require(__dirname + '/../../utils/domain-util.js'); | ||||
| const ServerInfoForm = require(__dirname + '/server-info-form.js'); | ||||
| const NewServerForm = require(__dirname + '/new-server-form.js'); | ||||
| const CreateOrganziation = require(__dirname + '/create-new-org.js'); | ||||
|  | ||||
| class ServersSection extends BaseSection { | ||||
| 	constructor(props) { | ||||
| @@ -14,10 +15,11 @@ class ServersSection extends BaseSection { | ||||
| 	template() { | ||||
| 		return ` | ||||
| 			<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 class="title" id="existing-servers"></div> | ||||
| 				<div id="server-info-container"></div> | ||||
| 				<div id="create-organization-container"></div> | ||||
| 			</div> | ||||
| 		`; | ||||
| 	} | ||||
| @@ -36,11 +38,14 @@ class ServersSection extends BaseSection { | ||||
| 		this.$newServerContainer = document.getElementById('new-server-container'); | ||||
| 		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 | ||||
| 		this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing Servers'; | ||||
| 		this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing organizations'; | ||||
| 		this.initNewServerForm(); | ||||
|  | ||||
| 		this.$createOrganizationContainer = document.getElementById('create-organization-container'); | ||||
| 		this.initCreateNewOrganization(); | ||||
|  | ||||
| 		for (let i = 0; i < servers.length; i++) { | ||||
| 			new ServerInfoForm({ | ||||
| 				$root: this.$serverInfoContainer, | ||||
| @@ -51,6 +56,12 @@ class ServersSection extends BaseSection { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	initCreateNewOrganization() { | ||||
| 		new CreateOrganziation({ | ||||
| 			$root: this.$createOrganizationContainer | ||||
| 		}).init(); | ||||
| 	} | ||||
|  | ||||
| 	initNewServerForm() { | ||||
| 		new NewServerForm({ | ||||
| 			$root: this.$newServerContainer, | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { ipcRenderer } = require('electron'); | ||||
| const { spellChecker } = require('./spellchecker'); | ||||
| const SetupSpellChecker = require('./spellchecker'); | ||||
|  | ||||
| const ConfigUtil = require(__dirname + '/utils/config-util.js'); | ||||
|  | ||||
| // eslint-disable-next-line import/no-unassigned-import | ||||
| require('./notification'); | ||||
|  | ||||
| @@ -32,8 +35,15 @@ process.once('loaded', () => { | ||||
|  | ||||
| // To prevent failing this script on linux we need to load it after the document loaded | ||||
| document.addEventListener('DOMContentLoaded', () => { | ||||
| 	// Get the default language of the server | ||||
| 	const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase | ||||
|  | ||||
| 	if (serverLanguage) { | ||||
| 		// Set spellcheker language | ||||
| 		ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage); | ||||
| 		// Init spellchecker | ||||
| 	spellChecker(); | ||||
| 		SetupSpellChecker.init(); | ||||
| 	} | ||||
|  | ||||
| 	// redirect users to network troubleshooting page | ||||
| 	const getRestartButton = document.querySelector('.restart_get_events_button'); | ||||
| @@ -43,3 +53,10 @@ document.addEventListener('DOMContentLoaded', () => { | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| // Clean up spellchecker events after you navigate away from this page; | ||||
| // otherwise, you may experience errors | ||||
| window.addEventListener('beforeunload', () => { | ||||
| 	SetupSpellChecker.unsubscribeSpellChecker(); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -2,28 +2,55 @@ | ||||
|  | ||||
| const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker'); | ||||
|  | ||||
| function spellChecker() { | ||||
| 	// Implement spellcheck using electron api | ||||
| 	window.spellCheckHandler = new SpellCheckHandler(); | ||||
| 	window.spellCheckHandler.attachToInput(); | ||||
| const ConfigUtil = require(__dirname + '/utils/config-util.js'); | ||||
|  | ||||
| 	// Start off as US English | ||||
| 	window.spellCheckHandler.switchLanguage('en-US'); | ||||
| class SetupSpellChecker { | ||||
| 	init() { | ||||
| 		if (ConfigUtil.getConfigItem('enableSpellchecker')) { | ||||
| 			this.enableSpellChecker(); | ||||
| 		} | ||||
| 		this.enableContextMenu(); | ||||
| 	} | ||||
|  | ||||
| 	const contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler); | ||||
| 	const contextMenuListener = new ContextMenuListener(info => { | ||||
| 	enableSpellChecker() { | ||||
| 		try { | ||||
| 			this.SpellCheckHandler = new SpellCheckHandler(); | ||||
| 		} catch (err) { | ||||
| 			console.log(err); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	enableContextMenu() { | ||||
| 		if (this.SpellCheckHandler) { | ||||
| 			this.SpellCheckHandler.attachToInput(); | ||||
|  | ||||
| 			const userLanguage = ConfigUtil.getConfigItem('spellcheckerLanguage'); | ||||
|  | ||||
| 			// eslint-disable-next-line no-unused-expressions | ||||
| 			process.platform === 'darwin' ? | ||||
| 				// On macOS, spellchecker fails to auto-detect the lanugage user is typing in | ||||
| 				// that's why we need to mention it explicitly | ||||
| 				this.SpellCheckHandler.switchLanguage(userLanguage) : | ||||
| 				// On Linux and Windows, spellchecker can automatically detects the language the user is typing in | ||||
| 				// and silently switches on the fly; thus we can start off as US English | ||||
| 				this.SpellCheckHandler.switchLanguage('en-US'); | ||||
| 		} | ||||
|  | ||||
| 		const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler); | ||||
| 		this.contextMenuListener = new ContextMenuListener(info => { | ||||
| 			contextMenuBuilder.showPopupMenu(info); | ||||
| 		}); | ||||
|  | ||||
| 	// Clean up events after you navigate away from this page; | ||||
| 	// otherwise, you may experience errors | ||||
| 	window.addEventListener('beforeunload', () => { | ||||
| 	// eslint-disable-next-line no-undef | ||||
| 		spellCheckHandler.unsubscribe(); | ||||
| 		contextMenuListener.unsubscribe(); | ||||
| 	}); | ||||
| 	} | ||||
|  | ||||
| module.exports = { | ||||
| 	spellChecker | ||||
| }; | ||||
| 	unsubscribeSpellChecker() { | ||||
| 		// eslint-disable-next-line no-undef | ||||
| 		if (this.SpellCheckHandler) { | ||||
| 			this.SpellCheckHandler.unsubscribe(); | ||||
| 		} | ||||
| 		if (this.contextMenuListener) { | ||||
| 			this.contextMenuListener.unsubscribe(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = new SetupSpellChecker(); | ||||
|   | ||||
| @@ -197,13 +197,16 @@ ipcRenderer.on('tray', (event, arg) => { | ||||
| }); | ||||
|  | ||||
| function toggleTray() { | ||||
| 	let state; | ||||
| 	if (window.tray) { | ||||
| 		state = false; | ||||
| 		window.tray.destroy(); | ||||
| 		if (window.tray.isDestroyed()) { | ||||
| 			window.tray = null; | ||||
| 		} | ||||
| 		ConfigUtil.setConfigItem('trayIcon', false); | ||||
| 	} else { | ||||
| 		state = true; | ||||
| 		createTray(); | ||||
| 		if (process.platform === 'linux' || process.platform === 'win32') { | ||||
| 			renderNativeImage(unread).then(image => { | ||||
| @@ -213,6 +216,10 @@ function toggleTray() { | ||||
| 		} | ||||
| 		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); | ||||
|   | ||||
| @@ -1,16 +1,29 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const fs = require('fs'); | ||||
| const path = require('path'); | ||||
| const process = require('process'); | ||||
| const JsonDB = require('node-json-db'); | ||||
| const Logger = require('./logger-util'); | ||||
|  | ||||
| const logger = new Logger({ | ||||
| 	file: 'config-util.log', | ||||
| 	timestamp: true | ||||
| }); | ||||
|  | ||||
| let instance = null; | ||||
| let dialog = null; | ||||
| let app = null; | ||||
|  | ||||
| /* To make the util runnable in both main and renderer process */ | ||||
| if (process.type === 'renderer') { | ||||
| 	app = require('electron').remote.app; | ||||
| 	const remote = require('electron').remote; | ||||
| 	dialog = remote.dialog; | ||||
| 	app = remote.app; | ||||
| } else { | ||||
| 	app = require('electron').app; | ||||
| 	const electron = require('electron'); | ||||
| 	dialog = electron.dialog; | ||||
| 	app = electron.app; | ||||
| } | ||||
|  | ||||
| class ConfigUtil { | ||||
| @@ -47,7 +60,22 @@ class ConfigUtil { | ||||
| 	} | ||||
|  | ||||
| 	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 JsonDB = require('node-json-db'); | ||||
| const request = require('request'); | ||||
| const Logger = require('./logger-util'); | ||||
|  | ||||
| const logger = new Logger({ | ||||
| 	file: `domain-util.log`, | ||||
| 	timestamp: true | ||||
| }); | ||||
|  | ||||
| let instance = null; | ||||
|  | ||||
| @@ -93,8 +99,7 @@ class DomainUtil { | ||||
| 	checkDomain(domain, silent = false) { | ||||
| 		if (!silent && this.duplicateDomain(domain)) { | ||||
| 			// Do not check duplicate in silent mode | ||||
| 			alert('This server has been added.'); | ||||
| 			return; | ||||
| 			return Promise.reject('This server has been added.'); | ||||
| 		} | ||||
|  | ||||
| 		domain = this.formatUrl(domain); | ||||
| @@ -110,17 +115,28 @@ class DomainUtil { | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			request(checkDomain, (error, response) => { | ||||
| 				const certsError = | ||||
| 					['Error: self signed certificate', | ||||
| 						'Error: unable to verify the first certificate' | ||||
| 					[ | ||||
| 						'Error: self signed certificate', | ||||
| 						'Error: unable to verify the first certificate', | ||||
| 						'Error: unable to get local issuer certificate' | ||||
| 					]; | ||||
| 				if (!error && response.statusCode !== 404) { | ||||
|  | ||||
| 				// 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 | ||||
| 					this.getServerSettings(domain).then(serverSettings => { | ||||
| 						resolve(serverSettings); | ||||
| 					}, () => { | ||||
| 						resolve(serverConf); | ||||
| 					}); | ||||
| 				} else if (certsError.indexOf(error.toString()) >= 0) { | ||||
| 				} else if (domain.indexOf(whitelistDomains) >= 0 || certsError.indexOf(error.toString()) >= 0) { | ||||
| 					if (silent) { | ||||
| 						this.getServerSettings(domain).then(serverSettings => { | ||||
| 							resolve(serverSettings); | ||||
| @@ -152,7 +168,9 @@ class DomainUtil { | ||||
| 						}); | ||||
| 					} | ||||
| 				} else { | ||||
| 					reject('Not a valid Zulip server'); | ||||
| 					const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \ | ||||
| 					\n(1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server`; | ||||
| 					reject(invalidZulipServerError); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| @@ -166,7 +184,9 @@ class DomainUtil { | ||||
| 					const data = JSON.parse(response.body); | ||||
| 					if (data.hasOwnProperty('realm_icon') && data.realm_icon) { | ||||
| 						resolve({ | ||||
| 							icon: data.realm_uri + data.realm_icon, | ||||
| 							// Some Zulip Servers use absolute URL for server icon whereas others use relative URL | ||||
| 							// Following check handles both the cases | ||||
| 							icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon, | ||||
| 							url: data.realm_uri, | ||||
| 							alias: data.realm_name | ||||
| 						}); | ||||
| @@ -215,7 +235,23 @@ class DomainUtil { | ||||
| 	} | ||||
|  | ||||
| 	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) { | ||||
|   | ||||
							
								
								
									
										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; | ||||
| @@ -13,13 +13,14 @@ | ||||
|     <div class="popup"> | ||||
|       <span class="popuptext hidden" id="fullscreen-popup"></span> | ||||
|   </div> | ||||
|   <div id="sidebar"> | ||||
|   <div id="sidebar" class="toggle-sidebar"> | ||||
|     <div id="view-controls-container"> | ||||
|       <div id="tabs-container"></div> | ||||
|       <div id="add-tab" class="tab functional-tab"> | ||||
|         <div class="server-tab" id="add-action"> | ||||
|           <i class="material-icons">add</i> | ||||
|         </div> | ||||
|         <span id="add-server-tooltip" style="display:none">Add organization</span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div id="actions-container"> | ||||
| @@ -40,4 +41,13 @@ | ||||
| </body> | ||||
| <script src="js/main.js"></script> | ||||
|  | ||||
| <!-- To trigger electron.reload on changes in renderer from gulp --> | ||||
| <script> | ||||
|   window.addEventListener('load', () => { | ||||
|     const isDev = require('electron-is-dev'); | ||||
|     if (isDev) { | ||||
|       require('electron-connect').client.create(); | ||||
|     } | ||||
|   }); | ||||
| </script> | ||||
| </html> | ||||
							
								
								
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@2x.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@3x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@3x.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@4x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/resources/tray/trayosx@4x.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.8 KiB | 
| @@ -20,4 +20,6 @@ install: | ||||
| build: off | ||||
|  | ||||
| test_script: | ||||
|   - node ./tools/gitlint --ci-mode | ||||
|   - npm run test | ||||
|   - npm run test-e2e | ||||
|   | ||||
							
								
								
									
										15
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								gulpfile.js
									
									
									
									
									
								
							| @@ -1,9 +1,10 @@ | ||||
| 'use strict'; | ||||
| const gulp = require('gulp'); | ||||
| const mocha = require('gulp-mocha'); | ||||
| const electron = require('electron-connect').server.create({ | ||||
| 	verbose: true | ||||
| }); | ||||
| const tape = require('gulp-tape'); | ||||
| const tapColorize = require('tap-colorize'); | ||||
|  | ||||
| gulp.task('dev', () => { | ||||
|   // Start browser process | ||||
| @@ -13,7 +14,7 @@ gulp.task('dev', () => { | ||||
|   // Reload renderer process | ||||
| 	gulp.watch('app/renderer/css/*.css', ['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 => { | ||||
| @@ -28,9 +29,11 @@ gulp.task('reload:renderer', done => { | ||||
| 	done(); | ||||
| }); | ||||
|  | ||||
| // Test app using mocha+spectron | ||||
| gulp.task('test', () => { | ||||
| 	return gulp.src('tests/index.js').pipe(mocha()); | ||||
| gulp.task('test-e2e', () => { | ||||
| 	return gulp.src('tests/*.js') | ||||
| 	.pipe(tape({ | ||||
| 		reporter: tapColorize() | ||||
| 	})); | ||||
| }); | ||||
|  | ||||
| gulp.task('default', ['dev', 'test']); | ||||
| gulp.task('default', ['dev', 'test-e2e']); | ||||
|   | ||||
							
								
								
									
										7557
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7557
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										42
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,15 +1,14 @@ | ||||
| { | ||||
|   "name": "zulip", | ||||
|   "productName": "Zulip", | ||||
|   "version": "1.4.0", | ||||
|   "version": "1.8.1", | ||||
|   "main": "./app/main", | ||||
|   "description": "Zulip Desktop App", | ||||
|   "license": "Apache-2.0", | ||||
|   "email": "<svnitakash@gmail.com>", | ||||
|   "copyright": "©2017 Kandra Labs, Inc.", | ||||
|   "copyright": "Kandra Labs, Inc.", | ||||
|   "author": { | ||||
|     "name": "Kandra Labs, Inc.", | ||||
|     "email": "svnitakash@gmail.com" | ||||
|     "email": "support@zulipchat.com" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
| @@ -20,12 +19,16 @@ | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "electron app --disable-http-cache", | ||||
|     "reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install", | ||||
|     "postinstall": "electron-builder install-app-deps", | ||||
|     "test": "xo", | ||||
|     "dev": "gulp dev", | ||||
|     "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", | ||||
|     "dist": "electron-builder", | ||||
|     "mas": "electron-builder --mac mas", | ||||
|     "setup-gitlint-hooks": "node ./tools/gitlint/setup-gitlint-hook", | ||||
|     "lint-commits": "node ./tools/gitlint --all-commits", | ||||
|     "travis": "cd ./scripts && ./travis-build-test.sh" | ||||
|   }, | ||||
|   "pre-commit": [ | ||||
| @@ -55,7 +58,9 @@ | ||||
|       "maintainer": "Akash Nimare <svnitakash@gmail.com>" | ||||
|     }, | ||||
|     "deb": { | ||||
|       "synopsis": "Zulip Desktop App" | ||||
|       "synopsis": "Zulip Desktop App", | ||||
|       "afterInstall": "./scripts/debian-add-repo.sh", | ||||
|       "afterRemove": "./scripts/debian-uninstaller.sh" | ||||
|     }, | ||||
|     "dmg": { | ||||
|       "background": "build/appdmg.png", | ||||
| @@ -104,17 +109,22 @@ | ||||
|   ], | ||||
|   "devDependencies": { | ||||
|     "assert": "1.4.1", | ||||
|     "chalk": "^2.3.0", | ||||
|     "cp-file": "^5.0.0", | ||||
|     "devtron": "1.4.0", | ||||
|     "electron-builder": "19.27.3", | ||||
|     "electron": "1.6.11", | ||||
|     "electron": "1.7.10", | ||||
|     "electron-builder": "19.53.6", | ||||
|     "electron-connect": "0.6.2", | ||||
|     "electron-debug": "1.4.0", | ||||
|     "gulp": "3.9.1", | ||||
|     "gulp-mocha": "4.3.1", | ||||
|     "chai-as-promised": "7.1.1", | ||||
|     "chai": "4.1.1", | ||||
|     "gulp-tape": "0.0.9", | ||||
|     "is-ci": "^1.0.10", | ||||
|     "nodemon": "^1.14.11", | ||||
|     "pre-commit": "1.2.2", | ||||
|     "spectron": "3.7.2", | ||||
|     "xo": "0.18.2", | ||||
|     "pre-commit": "1.2.2" | ||||
|     "tap-colorize": "^1.2.0", | ||||
|     "tape": "^4.8.0", | ||||
|     "xo": "0.18.2" | ||||
|   }, | ||||
|   "xo": { | ||||
|     "parserOptions": { | ||||
| @@ -144,6 +154,12 @@ | ||||
|           "import/no-extraneous-dependencies": 0, | ||||
|           "no-prototype-builtins": 0 | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|         "files": "scripts/gitlint/*.js", | ||||
|         "rules": { | ||||
|           "unicorn/no-process-exit": 1 | ||||
|         } | ||||
|       } | ||||
|     ], | ||||
|     "ignore": [ | ||||
|   | ||||
							
								
								
									
										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 | ||||
| @@ -5,6 +5,13 @@ if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then | ||||
|     export DISPLAY=:99.0 | ||||
|     sh -e /etc/init.d/xvfb start | ||||
|     sleep 3 | ||||
|  | ||||
|     echo 'Travis Screen Resolution:' | ||||
|     xdpyinfo | grep dimensions | ||||
| fi | ||||
|  | ||||
| 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 Application = require('spectron').Application | ||||
| const chai = require('chai') | ||||
| const { expect } = chai | ||||
| const chaiAsPromised = require('chai-as-promised') | ||||
| const test = require('tape') | ||||
| const setup = require('./setup') | ||||
|  | ||||
| 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'] | ||||
| 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')) | ||||
| }) | ||||
|     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)) | ||||
| 	}) | ||||
| }) | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 | ||||
							
								
								
									
										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 | ||||
							
								
								
									
										31
									
								
								tools/gitlint/ci.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tools/gitlint/ci.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| const {run} = require('./helpers'); | ||||
|  | ||||
| module.exports = () => { | ||||
| 	if (process.argv.TRAVIS !== undefined) { | ||||
| 		return travis(); | ||||
| 	} | ||||
|  | ||||
| 	return appveyor(); | ||||
| }; | ||||
|  | ||||
| function travis() { | ||||
| 	if (!process.env.TRAVIS_PULL_REQUEST) { | ||||
|     // Building against master last commit | ||||
| 		return run('git log -1 HEAD'); | ||||
| 	} | ||||
|  | ||||
| 	const cmd = `git log ${process.env.TRAVIS_COMMIT_RANGE}`; | ||||
| 	const commitRange = run(`git diff --name-only ${process.env.TRAVIS_COMMIT_RANGE}`); | ||||
| 	process.stdout.write(`COMMIT_RANGE: ${commitRange}`, 'utf8'); | ||||
| 	return run(cmd); | ||||
| } | ||||
|  | ||||
| function appveyor() { | ||||
| 	if (!process.env.APPVEYOR_PULL_REQUEST_NUMBER) { | ||||
| 		return run('git log -1 HEAD'); | ||||
| 	} | ||||
|  | ||||
| 	const cmd = | ||||
|     `git log origin/master...${process.env.APPVEYOR_PULL_REQUEST_HEAD_COMMIT}`; | ||||
| 	return run(cmd); | ||||
| } | ||||
							
								
								
									
										91
									
								
								tools/gitlint/helpers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								tools/gitlint/helpers.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| const {spawnSync} = require('child_process'); | ||||
| const chalk = require('chalk'); | ||||
|  | ||||
| const commitMsgRegex = /[A-Z]+.*\.$/; | ||||
| const isFullCommitRegex = /(\w|\W){1,}:\s{1}/; | ||||
| const fullCommitRegex = /(\w|\W){1,}:\s{1}[A-Z]+.*\.$/; | ||||
|  | ||||
| function run(script) { | ||||
| 	script = script.split(' '); | ||||
| 	const cmd = script.splice(0, 1)[0]; | ||||
| 	const args = script; | ||||
| 	const output = spawnSync(cmd, args, { | ||||
| 		cwd: process.cwd(), | ||||
| 		encoding: 'utf8', | ||||
| 		windowsHide: true | ||||
| 	}).stdout; | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| function garbageCollect(a) { | ||||
| 	a.forEach((content, index) => { | ||||
| 		if (content === '' || content === undefined) { | ||||
| 			a.splice(index, 1); | ||||
| 		} | ||||
| 	}); | ||||
| 	return a; | ||||
| } | ||||
|  | ||||
| function getAllCommits(output) { | ||||
| 	output = output.split('\ncommits'); | ||||
| 	if (!output.length > 1) { | ||||
| 		exports.error('There are no commits to lint.'); | ||||
| 		process.exit(1); | ||||
| 	} | ||||
|  | ||||
| 	output = garbageCollect(output); | ||||
| 	output.forEach((commit, index) => { | ||||
| 		output[index] = 'commit' + commit; | ||||
| 	}); | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| function parseCommit(output) { | ||||
| 	output = output.split('\n\n'); | ||||
|  | ||||
| 	let commit = output[0].replace('commit ', ''); | ||||
| 	commit = commit.replace(/\n.*/g, ''); | ||||
| 	let commitHash = commit.split(''); | ||||
| 	commitHash = commitHash.slice(commitHash.length - 7); | ||||
| 	commitHash = commitHash.join(''); | ||||
|  | ||||
| 	const fullCommit = output[1].split('\n'); | ||||
| 	const commitMsg = fullCommit[0]; | ||||
| 	let lintingStatus = commitMsgRegex.test(commitMsg); | ||||
| 	lintingStatus = (commitMsg.length <= 72); | ||||
|  | ||||
| 	if (lintingStatus && isFullCommitRegex(commitMsg)) { | ||||
| 		lintingStatus = fullCommitRegex.test(commitMsg); | ||||
| 	} | ||||
|  | ||||
| 	const result = { | ||||
| 		failed: !lintingStatus, | ||||
| 		commitHash | ||||
| 	}; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| function logSuccess() { | ||||
| 	console.log(chalk`{green commit linter:} commit linter passed.`); | ||||
| 	process.exit(0); | ||||
| } | ||||
|  | ||||
| function error(...args) { | ||||
| 	args.unshift(chalk.red('ERROR! ')); | ||||
| 	console.error.apply(this, args); | ||||
| } | ||||
|  | ||||
| function warn() { | ||||
| 	// console.error(chalk`{yellow ${msg}}`); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
| 	run, | ||||
| 	getAllCommits, | ||||
| 	parseCommit, | ||||
| 	logSuccess, | ||||
| 	error, | ||||
| 	warn | ||||
| }; | ||||
							
								
								
									
										44
									
								
								tools/gitlint/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								tools/gitlint/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| const helpers = require('./helpers'); | ||||
| const getCICmd = require('./ci'); | ||||
|  | ||||
| let checkAllCommits = false; | ||||
| let ciMode = false; | ||||
| if (process.argv[2]) { | ||||
| 	checkAllCommits = process.argv[2].includes('-a'); | ||||
| 	ciMode = process.argv[2] === '--ci-mode'; | ||||
| } | ||||
|  | ||||
| let cmd; | ||||
| if (ciMode) { | ||||
| 	cmd = getCICmd(); | ||||
| } else { | ||||
| 	cmd = | ||||
|     checkAllCommits ? 'git log upstream/master...HEAD' : 'git log -1 HEAD'; | ||||
| } | ||||
|  | ||||
| const commits = helpers.run(cmd); | ||||
| const commitsArray = helpers.getAllCommits(commits); | ||||
| let lintFailed = false; | ||||
| commitsArray.forEach(commit => { | ||||
| 	const res = helpers.parseCommit(commit); | ||||
| 	if (res.failed) { | ||||
| 		const {commitHash} = res; | ||||
| 		helpers.error(`commit ${commitHash} does not pass our commit style.`); | ||||
| 		lintFailed = true; | ||||
| 	} else { | ||||
| 		helpers.logSuccess('Commit[s] follow the zulip-electron commit rules.'); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| if (lintFailed) { | ||||
| 	helpers.warn(` | ||||
|   commit msg linting failed | ||||
|   ------------------------------- | ||||
|   Reasons it does not have: | ||||
|     a) capitial latter at start of message | ||||
| 		b) period at the end of commit or | ||||
| 		c) Commit msg length is more than 72 charaters | ||||
| 	`); | ||||
| 	helpers.warn('Run with --no-verify flag to skip the commit-linter'); | ||||
| 	process.exit(1); | ||||
| } | ||||
							
								
								
									
										73
									
								
								tools/gitlint/setup-gitlint-hook.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								tools/gitlint/setup-gitlint-hook.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // This script sets up the pre-push | ||||
| // git hook which will be used to lint | ||||
| // commits | ||||
|  | ||||
| const fs = require('fs'); | ||||
| const path = require('path'); | ||||
|  | ||||
| const gitHooks = '../../.git/hooks'; | ||||
| const postCommitPath = path.resolve(__dirname, `${gitHooks}/post-commit`); | ||||
| const prePushPath = path.resolve(__dirname, `${gitHooks}/pre-push`); | ||||
|  | ||||
| function scriptTemplate(cmds) { | ||||
| 	cmds = cmds.join(''); | ||||
| 	const script = [ | ||||
| 		'#!/bin/sh', | ||||
| 		'set -e', | ||||
| 		'echo running gitlint...', | ||||
| 		cmds, | ||||
| 		'exit $?' | ||||
| 	]; | ||||
|  | ||||
| 	return script.join('\n'); | ||||
| } | ||||
|  | ||||
| const postCommitFile = scriptTemplate`node scripts/gitlint`; | ||||
| const prePushFile = scriptTemplate`node scripts/gitlint --all-commits`; | ||||
|  | ||||
| function writeAndChmod(file, data) { | ||||
| 	fs.writeFile(file, data, err => { | ||||
| 		if (err) { | ||||
| 			throw err; | ||||
| 		} | ||||
|  | ||||
| 		fs.chmod(file, '777', err => { | ||||
| 			if (err) { | ||||
| 				const msg = | ||||
| 					'chmod post-commit, pre-push hooks, at .git/hooks 0777 so they work!'; | ||||
| 				console.error(msg); | ||||
| 			} | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| [postCommitPath, prePushPath].forEach((file, index) => { | ||||
| 	fs.open(file, 'w+', err => { | ||||
| 		if (err && err.code !== 'EEXIST') { | ||||
| 			throw err; | ||||
| 		} | ||||
|  | ||||
| 		const data = index === 0 ? postCommitFile : prePushFile; | ||||
| 		writeAndChmod(file, data); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| // Remove .sample files since | ||||
| // sometimes the hooks do not work | ||||
| const postCommitSampleFile = `${postCommitPath}.sample`; | ||||
| const prePushSampleFile = `${prePushPath}.sample`; | ||||
| function removeSampleFile(file) { | ||||
| 	fs.unlink(file, err => { | ||||
| 		if (err) { | ||||
| 			throw err; | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| [postCommitSampleFile, prePushSampleFile].forEach(file => { | ||||
| 	fs.exists(file, exists => { | ||||
| 		if (exists) { | ||||
| 			removeSampleFile(file); | ||||
| 		} | ||||
| 	}); | ||||
| }); | ||||
| @@ -5,7 +5,11 @@ | ||||
| * Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I` | ||||
|  | ||||
| ### 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 | ||||
| - 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