mirror of
				https://github.com/zulip/zulip-desktop.git
				synced 2025-10-31 12:03:39 +00:00 
			
		
		
		
	Compare commits
	
		
			19 Commits
		
	
	
		
			git-linter
			...
			setup-karm
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 292e1b4d15 | ||
|  | 64a12dca09 | ||
|  | d1d19327ba | ||
|  | dc6a1c3c0b | ||
|  | 63005d20ca | ||
|  | 600e8acdfa | ||
|  | 3f01774d0a | ||
|  | 808dffd2ed | ||
|  | 6b51f6d9a6 | ||
|  | 36342145da | ||
|  | 7b639129b3 | ||
|  | 89ae4585e6 | ||
|  | 469b827425 | ||
|  | 7feb0e4280 | ||
|  | 8cd8bac6a7 | ||
|  | ce1e902e89 | ||
|  | 3baa2c6ca5 | ||
|  | 2114ccfb40 | ||
|  | c6f1f99fe9 | 
							
								
								
									
										8
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,8 +0,0 @@ | ||||
| --- | ||||
| <!-- 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
									
									
								
							
							
						
						
									
										16
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,16 +0,0 @@ | ||||
| --- | ||||
| <!-- | ||||
| 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 | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -26,6 +26,7 @@ yarn-error.log* | ||||
| config.gypi | ||||
|  | ||||
| # Test generated files | ||||
| tests/package.json | ||||
| tests/e2e/package.json | ||||
| coverage | ||||
|  | ||||
| .python-version | ||||
|   | ||||
| @@ -28,13 +28,7 @@ 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: | ||||
|   | ||||
							
								
								
									
										7
									
								
								ISSUE_TEMPLATE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								ISSUE_TEMPLATE.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| Please include:  | ||||
| - `Operating System` | ||||
| - `Clear steps to reproduce the issue` | ||||
| - `Relevant error messages and/or screenshots` | ||||
|  | ||||
|  | ||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @@ -12,12 +12,19 @@ Please see [installation guide](https://zulipchat.com/help/desktop-app-install-g | ||||
|  | ||||
| # Features | ||||
| * Sign in to multiple teams | ||||
| * Desktop Notifications with inline reply support | ||||
| * Multilanguage SpellChecker | ||||
| * Native desktop Notifications | ||||
| * SpellChecker | ||||
| * OSX/Win/Linux installers | ||||
| * Automatic Updates (macOS/Windows/Linux) | ||||
| * Automatic Updates (macOS/Windows) | ||||
| * 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,4 +1,5 @@ | ||||
| 'use strict'; | ||||
| const fs = require('fs'); | ||||
| const { app, dialog } = require('electron'); | ||||
| const { autoUpdater } = require('electron-updater'); | ||||
| const isDev = require('electron-is-dev'); | ||||
| @@ -14,6 +15,10 @@ function appUpdater() { | ||||
| 	// Create Logs directory | ||||
| 	const LogsDir = `${app.getPath('userData')}/Logs`; | ||||
|  | ||||
| 	if (!fs.existsSync(LogsDir)) { | ||||
| 		fs.mkdirSync(LogsDir); | ||||
| 	} | ||||
|  | ||||
| 	// Log whats happening | ||||
| 	const log = require('electron-log'); | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,6 @@ 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 | ||||
| @@ -82,11 +81,7 @@ function createMainWindow() { | ||||
| 	}); | ||||
|  | ||||
| 	win.once('ready-to-show', () => { | ||||
| 		if (ConfigUtil.getConfigItem('startMinimized')) { | ||||
| 			win.minimize(); | ||||
| 		} else { | ||||
| 		win.show(); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	win.loadURL(mainURL); | ||||
| @@ -129,9 +124,6 @@ function createMainWindow() { | ||||
| 	return win; | ||||
| } | ||||
|  | ||||
| // Decrease load on GPU (experimental) | ||||
| app.disableHardwareAcceleration(); | ||||
|  | ||||
| // eslint-disable-next-line max-params | ||||
| app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { | ||||
| 	event.preventDefault(); | ||||
| @@ -153,11 +145,7 @@ app.on('ready', () => { | ||||
| 	const page = mainWindow.webContents; | ||||
|  | ||||
| 	page.on('dom-ready', () => { | ||||
| 		if (ConfigUtil.getConfigItem('startMinimized')) { | ||||
| 			mainWindow.minimize(); | ||||
| 		} else { | ||||
| 		mainWindow.show(); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	page.once('did-frame-finish-load', () => { | ||||
| @@ -167,8 +155,7 @@ app.on('ready', () => { | ||||
| 	}); | ||||
|  | ||||
| 	electron.powerMonitor.on('resume', () => { | ||||
| 		mainWindow.reload(); | ||||
| 		page.send('destroytray'); | ||||
| 		page.send('reload-viewer'); | ||||
| 	}); | ||||
|  | ||||
| 	ipcMain.on('focus-app', () => { | ||||
|   | ||||
| @@ -54,7 +54,7 @@ class AppMenu { | ||||
| 			role: 'togglefullscreen' | ||||
| 		}, { | ||||
| 			label: 'Zoom In', | ||||
| 			accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=', | ||||
| 			accelerator: 'CommandOrControl+=', | ||||
| 			click(item, focusedWindow) { | ||||
| 				if (focusedWindow) { | ||||
| 					AppMenu.sendAction('zoomIn'); | ||||
|   | ||||
							
								
								
									
										31
									
								
								app/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										31
									
								
								app/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -101,12 +101,6 @@ | ||||
|         "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", | ||||
| @@ -324,12 +318,6 @@ | ||||
|       "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", | ||||
| @@ -634,25 +622,6 @@ | ||||
|         "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", | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "zulip", | ||||
|   "productName": "Zulip", | ||||
|   "version": "1.8.1", | ||||
|   "version": "1.7.0", | ||||
|   "description": "Zulip Desktop App", | ||||
|   "license": "Apache-2.0", | ||||
|   "copyright": "Kandra Labs, Inc.", | ||||
| @@ -30,13 +30,10 @@ | ||||
|     "electron-is-dev": "0.3.0", | ||||
|     "electron-log": "2.2.7", | ||||
|     "electron-spellchecker": "1.1.2", | ||||
|     "electron-updater": "2.18.2", | ||||
|     "electron-window-state": "4.1.1", | ||||
|     "electron-updater": "2.16.2", | ||||
|     "node-json-db": "0.7.3", | ||||
|     "request": "2.81.0", | ||||
|     "wurl": "2.5.0" | ||||
|   }, | ||||
|   "optionalDependencies": { | ||||
|     "node-mac-notifier": "0.0.13" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -263,13 +263,7 @@ img.server-info-icon { | ||||
|     margin: 10px 0 20px 0; | ||||
|     background: #fff; | ||||
|     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 { | ||||
|   | ||||
| @@ -4,7 +4,7 @@ const Tab = require(__dirname + '/../components/tab.js'); | ||||
|  | ||||
| class FunctionalTab extends Tab { | ||||
| 	template() { | ||||
| 		return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}"> | ||||
| 		return `<div class="tab functional-tab"> | ||||
| 					<div class="server-tab-badge close-button"> | ||||
| 						<i class="material-icons">close</i> | ||||
| 					</div> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ const {ipcRenderer} = require('electron'); | ||||
|  | ||||
| class ServerTab extends Tab { | ||||
| 	template() { | ||||
| 		return `<div class="tab" data-tab-id="${this.props.tabIndex}"> | ||||
| 		return `<div class="tab"> | ||||
| 					<div class="server-tooltip" style="display:none"></div> | ||||
| 					<div class="server-tab-badge"></div> | ||||
| 					<div class="server-tab"> | ||||
|   | ||||
| @@ -4,14 +4,12 @@ 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, app } = require('electron').remote; | ||||
|  | ||||
| const BaseComponent = require(__dirname + '/../components/base.js'); | ||||
|  | ||||
| const shouldSilentWebview = ConfigUtil.getConfigItem('silent'); | ||||
| class WebView extends BaseComponent { | ||||
| 	constructor(props) { | ||||
| 		super(); | ||||
| @@ -26,7 +24,6 @@ class WebView extends BaseComponent { | ||||
| 	template() { | ||||
| 		return `<webview | ||||
| 					class="disabled" | ||||
| 					data-tab-id="${this.props.tabIndex}" | ||||
| 					src="${this.props.url}" | ||||
| 					${this.props.nodeIntegration ? 'nodeIntegration' : ''} | ||||
| 					disablewebsecurity | ||||
| @@ -57,12 +54,6 @@ 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); | ||||
|   | ||||
							
								
								
									
										69
									
								
								app/renderer/js/console.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/renderer/js/console.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| const NodeConsole = require('console').Console; | ||||
| const fs = require('fs'); | ||||
|  | ||||
| const { app } = require('electron').remote; | ||||
| const isDev = require('electron-is-dev'); | ||||
|  | ||||
| const browserConsole = console; | ||||
| const logDir = `${app.getPath('userData')}/Logs`; | ||||
| if (!fs.existsSync(logDir)) { | ||||
| 	fs.mkdirSync(logDir); | ||||
| } | ||||
|  | ||||
| function customConsole(opts, type, ...args) { | ||||
| 	const { nodeConsole, timestamp } = opts; | ||||
| 	if (timestamp) { | ||||
| 		args.unshift(timestamp()); | ||||
| 	} | ||||
|  | ||||
| 	if (!isDev) { | ||||
| 		const nodeConsoleLog = nodeConsole[type] || nodeConsole.log; | ||||
| 		nodeConsoleLog.apply(null, args); | ||||
| 	} | ||||
| 	browserConsole[type].apply(null, args); | ||||
| } | ||||
|  | ||||
| function getTimestamp() { | ||||
| 	const date = new Date(); | ||||
| 	const timestamp = | ||||
| 		`${date.getMonth()}/${date.getDate()} ` + | ||||
| 		`${date.getMinutes()}:${date.getSeconds()}`; | ||||
| 	return timestamp; | ||||
| } | ||||
|  | ||||
| function setConsoleProto(type) { | ||||
| 	Object.defineProperty(this, type, { | ||||
| 		value(...args) { | ||||
| 			const { timestamp, nodeConsole } = this; | ||||
| 			const opts = { | ||||
| 				timestamp, | ||||
| 				nodeConsole | ||||
| 			}; | ||||
| 			customConsole.apply(null, [].concat(opts, type, args)); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| class Console { | ||||
| 	constructor(opts = {}) { | ||||
| 		let { timestamp, file } = opts; | ||||
| 		file = `${logDir}/${file || 'console.log'}`; | ||||
| 		if (timestamp === true) { | ||||
| 			timestamp = getTimestamp; | ||||
| 		} | ||||
|  | ||||
| 		const fileStream = fs.createWriteStream(file); | ||||
| 		const nodeConsole = new NodeConsole(fileStream); | ||||
| 		this.nodeConsole = nodeConsole; | ||||
| 		this.timestamp = timestamp; | ||||
| 		this.setUpConsole(); | ||||
| 	} | ||||
|  | ||||
| 	setUpConsole() { | ||||
| 		for (const type in browserConsole) { | ||||
| 			setConsoleProto.call(this, type); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = Console; | ||||
| @@ -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'); | ||||
| @@ -35,7 +35,6 @@ class ServerManagerView { | ||||
| 		this.activeTabIndex = -1; | ||||
| 		this.tabs = []; | ||||
| 		this.functionalTabs = {}; | ||||
| 		this.tabIndex = 0; | ||||
| 	} | ||||
|  | ||||
| 	init() { | ||||
| @@ -78,7 +77,6 @@ class ServerManagerView { | ||||
| 			showSidebar: true, | ||||
| 			badgeOption: true, | ||||
| 			startAtLogin: false, | ||||
| 			startMinimized: false, | ||||
| 			enableSpellchecker: true, | ||||
| 			showNotification: true, | ||||
| 			betaUpdate: false, | ||||
| @@ -86,13 +84,6 @@ class ServerManagerView { | ||||
| 			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]); | ||||
| @@ -121,20 +112,17 @@ class ServerManagerView { | ||||
| 	} | ||||
|  | ||||
| 	initServer(server, index) { | ||||
| 		const tabIndex = this.getTabIndex(); | ||||
| 		this.tabs.push(new ServerTab({ | ||||
| 			role: 'server', | ||||
| 			icon: server.icon, | ||||
| 			$root: this.$tabsContainer, | ||||
| 			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: () => { | ||||
| @@ -159,24 +147,11 @@ 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'); | ||||
| @@ -203,19 +178,16 @@ 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: () => { | ||||
| @@ -283,27 +255,6 @@ 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) { | ||||
| @@ -436,18 +387,6 @@ 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) { | ||||
|   | ||||
							
								
								
									
										30
									
								
								app/renderer/js/notification.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/renderer/js/notification.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { remote } = require('electron'); | ||||
|  | ||||
| const ConfigUtil = require(__dirname + '/utils/config-util.js'); | ||||
|  | ||||
| const app = remote.app; | ||||
|  | ||||
| // From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid | ||||
| // On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work. | ||||
| app.setAppUserModelId('org.zulip.zulip-electron'); | ||||
|  | ||||
| const NativeNotification = window.Notification; | ||||
|  | ||||
| class baseNotification extends NativeNotification { | ||||
| 	constructor(title, opts) { | ||||
| 		opts.silent = ConfigUtil.getConfigItem('silent') || false; | ||||
| 		super(title, opts); | ||||
| 	} | ||||
| 	static requestPermission() { | ||||
| 		return; // eslint-disable-line no-useless-return | ||||
| 	} | ||||
| 	// Override default Notification permission | ||||
| 	static get permission() { | ||||
| 		return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied'; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| window.Notification = baseNotification; | ||||
|  | ||||
| @@ -1,100 +0,0 @@ | ||||
| '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; | ||||
| @@ -1,31 +0,0 @@ | ||||
| '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; | ||||
| @@ -1,102 +0,0 @@ | ||||
| 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 | ||||
| }; | ||||
| @@ -1,18 +0,0 @@ | ||||
| '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; | ||||
| } | ||||
| @@ -1,10 +1,11 @@ | ||||
| 'use strict'; | ||||
| const path = require('path'); | ||||
| const { ipcRenderer, remote } = require('electron'); | ||||
|  | ||||
| const { ipcRenderer } = require('electron'); | ||||
| const { app, dialog } = require('electron').remote; | ||||
|  | ||||
| const fs = require('fs-extra'); | ||||
|  | ||||
| const { app, dialog } = remote; | ||||
| const currentBrowserWindow = remote.getCurrentWindow(); | ||||
| const BaseSection = require(__dirname + '/base-section.js'); | ||||
| const ConfigUtil = require(__dirname + '/../../utils/config-util.js'); | ||||
|  | ||||
| @@ -60,10 +61,6 @@ 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> | ||||
| @@ -92,7 +89,6 @@ class GeneralSection extends BaseSection { | ||||
| 		this.updateResetDataOption(); | ||||
| 		this.showDesktopNotification(); | ||||
| 		this.enableSpellchecker(); | ||||
| 		this.minimizeOnStart(); | ||||
|  | ||||
| 		// Platform specific settings | ||||
| 		// Flashing taskbar on Windows | ||||
| @@ -159,7 +155,6 @@ class GeneralSection extends BaseSection { | ||||
| 				const newValue = !ConfigUtil.getConfigItem('silent', true); | ||||
| 				ConfigUtil.setConfigItem('silent', newValue); | ||||
| 				this.updateSilentOption(); | ||||
| 				currentBrowserWindow.send('toogle-silent', newValue); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| @@ -239,18 +234,6 @@ 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; | ||||
|   | ||||
| @@ -13,9 +13,9 @@ class NewServerForm extends BaseComponent { | ||||
| 		return ` | ||||
| 			<div class="settings-card"> | ||||
| 				<div class="server-info-right"> | ||||
| 					<div class="title">URL of Zulip organization</div> | ||||
| 					<div class="title">URL of your Zulip organization</div> | ||||
| 					<div class="server-info-row"> | ||||
| 						<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or chat.your-organization.com"/> | ||||
| 						<input class="setting-input-value" autofocus placeholder="acme.zulipchat.com"/> | ||||
| 					</div> | ||||
| 					<div class="server-info-row"> | ||||
| 						<div class="action blue server-save-action"> | ||||
| @@ -43,13 +43,11 @@ 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,27 +69,10 @@ 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); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -197,16 +197,13 @@ 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 => { | ||||
| @@ -216,10 +213,6 @@ 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,29 +1,16 @@ | ||||
| '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') { | ||||
| 	const remote = require('electron').remote; | ||||
| 	dialog = remote.dialog; | ||||
| 	app = remote.app; | ||||
| 	app = require('electron').remote.app; | ||||
| } else { | ||||
| 	const electron = require('electron'); | ||||
| 	dialog = electron.dialog; | ||||
| 	app = electron.app; | ||||
| 	app = require('electron').app; | ||||
| } | ||||
|  | ||||
| class ConfigUtil { | ||||
| @@ -60,22 +47,7 @@ class ConfigUtil { | ||||
| 	} | ||||
|  | ||||
| 	reloadDB() { | ||||
| 		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); | ||||
| 		this.db = new JsonDB(app.getPath('userData') + '/settings.json', true, true); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,31 +0,0 @@ | ||||
| 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,12 +5,6 @@ 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; | ||||
|  | ||||
| @@ -99,7 +93,8 @@ class DomainUtil { | ||||
| 	checkDomain(domain, silent = false) { | ||||
| 		if (!silent && this.duplicateDomain(domain)) { | ||||
| 			// Do not check duplicate in silent mode | ||||
| 			return Promise.reject('This server has been added.'); | ||||
| 			alert('This server has been added.'); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		domain = this.formatUrl(domain); | ||||
| @@ -120,16 +115,11 @@ class DomainUtil { | ||||
| 						'Error: unable to verify the first certificate', | ||||
| 						'Error: unable to get local issuer certificate' | ||||
| 					]; | ||||
|  | ||||
| 				// 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) { | ||||
| 				if (!error && response.statusCode !== 404) { | ||||
| 					// Correct | ||||
| 					this.getServerSettings(domain).then(serverSettings => { | ||||
| 						resolve(serverSettings); | ||||
| @@ -169,7 +159,7 @@ class DomainUtil { | ||||
| 					} | ||||
| 				} else { | ||||
| 					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`; | ||||
| 					\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`; | ||||
| 					reject(invalidZulipServerError); | ||||
| 				} | ||||
| 			}); | ||||
| @@ -235,23 +225,7 @@ class DomainUtil { | ||||
| 	} | ||||
|  | ||||
| 	reloadDB() { | ||||
| 		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); | ||||
| 		this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); | ||||
| 	} | ||||
|  | ||||
| 	generateFilePath(url) { | ||||
|   | ||||
| @@ -1,87 +0,0 @@ | ||||
| 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; | ||||
| @@ -41,13 +41,4 @@ | ||||
| </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> | ||||
| @@ -20,6 +20,4 @@ install: | ||||
| build: off | ||||
|  | ||||
| test_script: | ||||
|   - node ./tools/gitlint --ci-mode | ||||
|   - npm run test | ||||
|   - npm run test-e2e | ||||
|   - npm run test-all | ||||
|   | ||||
| @@ -14,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 => { | ||||
| @@ -30,7 +30,7 @@ gulp.task('reload:renderer', done => { | ||||
| }); | ||||
|  | ||||
| gulp.task('test-e2e', () => { | ||||
| 	return gulp.src('tests/*.js') | ||||
| 	return gulp.src('tests/e2e/*.js') | ||||
| 	.pipe(tape({ | ||||
| 		reporter: tapColorize() | ||||
| 	})); | ||||
|   | ||||
							
								
								
									
										21
									
								
								karma.conf.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								karma.conf.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| module.exports = function (config) { | ||||
| 	config.set({ | ||||
| 		basePath: '', | ||||
| 		frameworks: ['jasmine'], | ||||
| 		browsers: ['Electron'], | ||||
| 		preprocessors: { | ||||
| 			'**/*.js': ['electron'] | ||||
| 		}, | ||||
| 		files: [ | ||||
| 			{pattern: './karma.shim.js', watched: true, included: true, served: true}, | ||||
| 			{pattern: './tests/unit/*.js', watched: true, included: true, served: true}, | ||||
| 			{pattern: './app/renderer/**/*.js', watched: true, included: false, served: true} | ||||
| 		], | ||||
| 		reporters: ['mocha'], | ||||
| 		client: { | ||||
| 			captureConsole: true, | ||||
| 			useIframe: false | ||||
| 		}, | ||||
| 		singleRun: true | ||||
| 	}); | ||||
| }; | ||||
							
								
								
									
										5
									
								
								karma.shim.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								karma.shim.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| window.require = window.parent.require; | ||||
| window.process = window.parent.process; | ||||
| window.__dirname = window.parent.__dirname; | ||||
| require('module').globalPaths.push('./node_modules'); | ||||
| require('module').globalPaths.push('./app/renderer'); | ||||
							
								
								
									
										39
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										39
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1561,9 +1561,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "electron": { | ||||
|       "version": "1.7.9", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-1.7.9.tgz", | ||||
|       "integrity": "sha1-rdVOn4+D7QL2UZ7BATX2mLGTNs8=", | ||||
|       "version": "1.6.15", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-1.6.15.tgz", | ||||
|       "integrity": "sha1-w07FRIa39Jpm21jG8koJKEFPrqc=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@types/node": "7.0.48", | ||||
| @@ -4762,34 +4762,6 @@ | ||||
|         "inherits": "2.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "nodemon": { | ||||
|       "version": "1.14.11", | ||||
|       "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.14.11.tgz", | ||||
|       "integrity": "sha512-323uPopdzYcyDR2Ze1UOLF9zocwoQEyGPiKaLm/Y8Mbfjylt/YueAJUVHqox+vgG8TqZqZApcHv5lmUvrn/KQw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "chokidar": "2.0.0", | ||||
|         "debug": "3.1.0", | ||||
|         "ignore-by-default": "1.0.1", | ||||
|         "minimatch": "3.0.4", | ||||
|         "pstree.remy": "1.1.0", | ||||
|         "semver": "5.4.1", | ||||
|         "touch": "3.1.0", | ||||
|         "undefsafe": "2.0.1", | ||||
|         "update-notifier": "2.3.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "3.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", | ||||
|           "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node-abi": { | ||||
|       "version": "2.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.2.tgz", | ||||
| @@ -5829,6 +5801,11 @@ | ||||
|         "resolve-from": "1.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "resemblejs": { | ||||
|       "version": "2.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/resemblejs/-/resemblejs-2.5.0.tgz", | ||||
|       "integrity": "sha512-j2p4H8jpxch3bG9SOoap4tWbNZ4hNGrl54y7WJSwoKBQM3ZBhVyaFEYqzlv06dY1I4Pns/M8Ten6R6+/jTjycA==" | ||||
|     }, | ||||
|     "resolve": { | ||||
|       "version": "1.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", | ||||
|   | ||||
							
								
								
									
										34
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "zulip", | ||||
|   "productName": "Zulip", | ||||
|   "version": "1.8.1", | ||||
|   "version": "1.7.0", | ||||
|   "main": "./app/main", | ||||
|   "description": "Zulip Desktop App", | ||||
|   "license": "Apache-2.0", | ||||
| @@ -21,18 +21,18 @@ | ||||
|     "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", | ||||
|     "lint": "xo", | ||||
|     "test-e2e": "gulp test-e2e", | ||||
|     "dev": "gulp dev & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js", | ||||
|     "test-unit": "karma start karma.conf.js", | ||||
|     "test-all": "xo && npm run test-unit && npm run test-e2e", | ||||
|     "dev": "gulp dev", | ||||
|     "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": [ | ||||
|     "test" | ||||
|     "lint" | ||||
|   ], | ||||
|   "build": { | ||||
|     "appId": "org.zulip.zulip-electron", | ||||
| @@ -109,21 +109,26 @@ | ||||
|   ], | ||||
|   "devDependencies": { | ||||
|     "assert": "1.4.1", | ||||
|     "chalk": "^2.3.0", | ||||
|     "cp-file": "^5.0.0", | ||||
|     "devtron": "1.4.0", | ||||
|     "electron": "1.7.10", | ||||
|     "electron-builder": "19.53.6", | ||||
|     "electron-builder": "19.46.4", | ||||
|     "electron": "1.7.9", | ||||
|     "electron-connect": "0.6.2", | ||||
|     "electron-debug": "1.4.0", | ||||
|     "gulp": "3.9.1", | ||||
|     "gulp-tape": "0.0.9", | ||||
|     "is-ci": "^1.0.10", | ||||
|     "nodemon": "^1.14.11", | ||||
|     "jasmine": "^2.8.0", | ||||
|     "karma": "^1.7.1", | ||||
|     "karma-electron": "^5.2.2", | ||||
|     "karma-electron-launcher": "^0.2.0", | ||||
|     "karma-jasmine": "^1.1.0", | ||||
|     "karma-mocha-reporter": "^2.2.5", | ||||
|     "pre-commit": "1.2.2", | ||||
|     "spectron": "3.7.2", | ||||
|     "tap-colorize": "^1.2.0", | ||||
|     "tape": "^4.8.0", | ||||
|     "watchify": "^3.9.0", | ||||
|     "xo": "0.18.2" | ||||
|   }, | ||||
|   "xo": { | ||||
| @@ -154,16 +159,11 @@ | ||||
|           "import/no-extraneous-dependencies": 0, | ||||
|           "no-prototype-builtins": 0 | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|         "files": "scripts/gitlint/*.js", | ||||
|         "rules": { | ||||
|           "unicorn/no-process-exit": 1 | ||||
|         } | ||||
|       } | ||||
|     ], | ||||
|     "ignore": [ | ||||
|       "tests/*.js" | ||||
|       "tests/e2e/*.js", | ||||
|       "tests/unit/*" | ||||
|     ], | ||||
|     "envs": [ | ||||
|       "node", | ||||
|   | ||||
| @@ -10,8 +10,14 @@ if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then | ||||
|     xdpyinfo | grep dimensions | ||||
| fi | ||||
|  | ||||
| npm run test | ||||
|  | ||||
| # macOS | ||||
| # Run all the tests | ||||
| if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then | ||||
|     npm run test-e2e | ||||
|     npm run test-all | ||||
| fi | ||||
|  | ||||
| # Linux | ||||
| # Only run linting test on Linux | ||||
| if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then | ||||
|     npm run lint | ||||
| fi | ||||
|   | ||||
| @@ -22,7 +22,7 @@ module.exports = { | ||||
| function createApp (t) { | ||||
|   generateTestAppPackageJson() | ||||
|   return new Application({ | ||||
|     path: path.join(__dirname, '..', 'node_modules', '.bin', | ||||
|     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'}, | ||||
| @@ -34,9 +34,9 @@ function createApp (t) { | ||||
| // 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')) | ||||
|   let packageJson = require(path.join(__dirname, '..', '..', 'package.json')) | ||||
|   packageJson.productName = config.TEST_APP_PRODUCT_NAME | ||||
|   packageJson.main = '../app/main' | ||||
|   packageJson.main = '../../app/main' | ||||
| 
 | ||||
|   const testPackageJsonPath = path.join(__dirname, 'package.json') | ||||
|   fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8') | ||||
							
								
								
									
										20
									
								
								tests/unit/test-spellchecker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/unit/test-spellchecker.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| const ConfigUtil = require('js/utils/config-util.js'); | ||||
| const SetupSpellChecker = require('js/spellchecker') | ||||
|  | ||||
| describe('test spell checker', function () { | ||||
|   // enable spellchecker settings | ||||
|   ConfigUtil.setConfigItem('enableSpellchecker', true); | ||||
|   ConfigUtil.setConfigItem('spellcheckerLanguage', 'en'); | ||||
|  | ||||
|   SetupSpellChecker.init()  // re-initialize after setting update | ||||
|  | ||||
|   const spellCheckHandler = SetupSpellChecker.SpellCheckHandler | ||||
|  | ||||
|   it('mark misspelled word', function () { | ||||
|     expect(spellCheckHandler.isMisspelled('helpe')).toBe(true) | ||||
|   }) | ||||
|  | ||||
|   it('verify properly spelled word', function () { | ||||
|     expect(spellCheckHandler.isMisspelled('help')).toBe(false) | ||||
|   }) | ||||
| }) | ||||
| @@ -1,16 +0,0 @@ | ||||
| #!/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 | ||||
| @@ -1,17 +0,0 @@ | ||||
| #!/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 | ||||
| @@ -1,31 +0,0 @@ | ||||
| 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); | ||||
| } | ||||
| @@ -1,91 +0,0 @@ | ||||
| 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 | ||||
| }; | ||||
| @@ -1,44 +0,0 @@ | ||||
| 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); | ||||
| } | ||||
| @@ -1,73 +0,0 @@ | ||||
| // 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); | ||||
| 		} | ||||
| 	}); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user