diff --git a/.editorconfig b/.editorconfig index e7066cfc7a..f2f826b09d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,9 +8,12 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.{js,coffee,html,less,json}] +[*.{js,coffee,html,less,css,json}] +indent_style = tab + +[*.i18n.json] indent_style = space -indent_size = 4 +indent_size = 2 [*.md] trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore index af602648ba..1d6cd2eeb9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,5 @@ -node_modules -app -e2e -src/public/lib/bugsnag.js -src/public/vendor/* -scripts/istanbul-reporter.js +/node_modules +/app +/src/public/lib/bugsnag.js +/src/public/vendor/* +/scripts/istanbul-reporter.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..17ad7212a5 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,9 @@ +{ + "extends": [ + "@rocket.chat/eslint-config" + ], + "globals": { + "_": false, + "Bugsnag": false + } +} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 03fb12fdd6..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "parserOptions": { - "sourceType": "module" - }, - "env": { - "browser": true, - "commonjs": true, - "es6": true, - "node": true, - "jquery": true - }, - "rules": { - "no-var": 2, - "prefer-const": 2, - "no-multi-spaces": 2, - "no-eval": 2, - "no-extend-native": 2, - "no-multi-str": 2, - "no-use-before-define": 2, - "no-const-assign": 2, - "no-cond-assign": 2, - "no-constant-condition": 2, - "no-control-regex": 2, - "no-debugger": 2, - "no-delete-var": 2, - "no-dupe-keys": 2, - "no-dupe-args": 2, - "no-duplicate-case": 2, - "no-empty": 2, - "no-empty-character-class": 2, - "no-ex-assign": 2, - "no-extra-boolean-cast": 2, - "no-extra-semi": 2, - "no-fallthrough": 2, - "no-func-assign": 2, - "no-inner-declarations": [2, "functions"], - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-mixed-spaces-and-tabs": 2, - "no-sparse-arrays": 2, - "no-negated-in-lhs": 2, - "no-obj-calls": 2, - "no-octal": 2, - "no-redeclare": 2, - "no-regex-spaces": 2, - "no-undef": 2, - "no-unreachable": 2, - "no-unused-vars": [2, { - "vars": "all", - "args": "after-used" - }], - "no-lonely-if": 2, - "no-trailing-spaces": 2, - "complexity": [1, 31], - "space-in-parens": [2, "never"], - "space-before-function-paren": [2, "always"], - "space-before-blocks": [2, "always"], - "indent": [2, 4, {"SwitchCase": 1}], - "keyword-spacing": 2, - "block-spacing": 2, - "brace-style": [2, "1tbs", { "allowSingleLine": true }], - "computed-property-spacing": 2, - "comma-spacing": 2, - "comma-style": 2, - "guard-for-in": 2, - "wrap-iife": 2, - "block-scoped-var": 2, - "curly": [2, "all"], - "eqeqeq": [2, "allow-null"], - "new-cap": [2, { - "capIsNewExceptions": ["Match.Optional", "Match.Maybe", "Match.ObjectIncluding"] - }], - "use-isnan": 2, - "valid-typeof": 2, - "linebreak-style": [2, "unix"], - "semi": [2, "always"] - }, - "globals": { - "_" : false, - "Bugsnag" : false - } -} diff --git a/README.md b/README.md index 678749fd64..6fac334a0b 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,8 @@ Using [electron-mocha](https://github.com/jprichardson/electron-mocha) test runn yarn e2e ``` -Using [mocha](https://mochajs.org/) test runner and [spectron](http://electron.atom.io/spectron/). This task searches for all files in `e2e` directory which respect pattern `*.e2e.js`. +Using [mocha](https://mochajs.org/) test runner and [spectron](http://electron.atom.io/spectron/). +This task searches for all files in `src/e2e` directory which respect pattern `*.e2e.js`. ### Code coverage diff --git a/e2e/app.e2e.js b/e2e/app.e2e.js deleted file mode 100644 index fcacec9847..0000000000 --- a/e2e/app.e2e.js +++ /dev/null @@ -1,14 +0,0 @@ -import { expect } from 'chai'; -import { app, startApp, stopApp } from './utils'; - -import packageJson from '../package.json'; - -describe('application', function () { - before(startApp); - after(stopApp); - - it('shows the main window', async function () { - expect(await app.browserWindow.isVisible()).to.be.true; - expect(await app.browserWindow.getTitle()).to.be.equal(packageJson.productName); - }); -}); diff --git a/e2e/utils.js b/e2e/utils.js deleted file mode 100644 index 5a7bed54cb..0000000000 --- a/e2e/utils.js +++ /dev/null @@ -1,87 +0,0 @@ -import path from 'path'; -import electron from 'electron'; -import { Application } from 'spectron'; - -export let app = null; -let logFetchInterval = null; - -export async function startApp () { - this.timeout(10000); - - app = new Application({ - path: electron, - cwd: process.cwd(), - args: [path.join(__dirname, '..')], - quitTimeout: 5000, - startTimeout: 5000, - waitTimeout: 5000, - }); - - await app.start(); - await app.client.waitUntilWindowLoaded(); - - logFetchInterval = setInterval(fetchLogs, 100); -}; - -export async function stopApp () { - this.timeout(10000); - - if (app && app.isRunning()) { - clearInterval(logFetchInterval); - fetchLogs(); - await app.stop(); - app = null; - } -}; - -const fetchLogs = async () => { - const logs = await app.client.getMainProcessLogs(); - logs.forEach(log => console.log(log)); -}; - -export const menuItem = (menuId, cb) => ({ - get exists() { - return app.client.execute((menuId) => { - const { Menu } = require('electron').remote; - const appMenu = Menu.getApplicationMenu(); - const menuItem = appMenu.getMenuItemById(menuId); - return !!menuItem; - }, menuId).then(({ value }) => value); - }, - - get enabled() { - return app.client.execute((menuId) => { - const { Menu } = require('electron').remote; - const appMenu = Menu.getApplicationMenu(); - const menuItem = appMenu.getMenuItemById(menuId); - return menuItem.enabled; - }, menuId).then(({ value }) => value); - }, - - get visible() { - return app.client.execute((menuId) => { - const { Menu } = require('electron').remote; - const appMenu = Menu.getApplicationMenu(); - const menuItem = appMenu.getMenuItemById(menuId); - return menuItem.visible; - }, menuId).then(({ value }) => value); - }, - - get label() { - return app.client.execute((menuId) => { - const { Menu } = require('electron').remote; - const appMenu = Menu.getApplicationMenu(); - const menuItem = appMenu.getMenuItemById(menuId); - return menuItem.label; - }, menuId).then(({ value }) => value); - }, - - click() { - return app.client.execute((menuId) => { - const { Menu } = require('electron').remote; - const appMenu = Menu.getApplicationMenu(); - const menuItem = appMenu.getMenuItemById(menuId); - menuItem.click(); - }, menuId); - } -}); diff --git a/package.json b/package.json index f8c915dbac..bb842bab55 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "release": "gulp release --env=production", "release-dev": "gulp release --env=development", "release-mas-dev": "build --publish never --mac mas-dev --c.mac.provisioningProfile=Development.provisionprofile", - "lint": "eslint .", + "lint": "eslint src", "pretest": "gulp build-unit-tests --env=test", "test": "electron-mocha app/specs.js.autogenerated --renderer --require source-map-support/register", "coverage": "npm test -- -R scripts/istanbul-reporter", @@ -38,6 +38,7 @@ "node-mac-notifier": "^1.1.0" }, "devDependencies": { + "@rocket.chat/eslint-config": "^0.1.2", "chai": "^4.1.2", "conventional-changelog-cli": "^2.0.5", "electron": "^2.0.9", diff --git a/src/app.js b/src/app.js index c165252b62..118ae5dbbe 100644 --- a/src/app.js +++ b/src/app.js @@ -1,15 +1,15 @@ import './branding/branding.js'; import { start } from './scripts/start'; import { remote } from 'electron'; -const app = remote.app; +const { app } = remote; Bugsnag.metaData = { - // platformId: app.process.platform, - // platformArch: app.process.arch, - // electronVersion: app.process.versions.electron, - version: app.getVersion() - // platformVersion: cordova.platformVersion - // build: appInfo.build + // platformId: app.process.platform, + // platformArch: app.process.arch, + // electronVersion: app.process.versions.electron, + version: app.getVersion(), + // platformVersion: cordova.platformVersion + // build: appInfo.build }; Bugsnag.appVersion = app.getVersion(); diff --git a/src/background.js b/src/background.js index 30db8bf544..3bbc5beecb 100644 --- a/src/background.js +++ b/src/background.js @@ -21,90 +21,90 @@ process.env.GOOGLE_API_KEY = 'AIzaSyADqUh_c1Qhji3Cp1NE43YrcpuPkmhXD-c'; const isMacOS = process.platform === 'darwin'; const unsetDefaultApplicationMenu = () => { - if (!isMacOS) { - Menu.setApplicationMenu(null); - return; - } - - const emptyMenuTemplate = [{ - submenu: [ - { - label: i18n.__('Quit_App', app.getName()), - accelerator: 'CommandOrControl+Q', - click () { - app.quit(); - } - } - ] - }]; - Menu.setApplicationMenu(Menu.buildFromTemplate(emptyMenuTemplate)); + if (!isMacOS) { + Menu.setApplicationMenu(null); + return; + } + + const emptyMenuTemplate = [{ + submenu: [ + { + label: i18n.__('Quit_App', app.getName()), + accelerator: 'CommandOrControl+Q', + click() { + app.quit(); + }, + }, + ], + }]; + Menu.setApplicationMenu(Menu.buildFromTemplate(emptyMenuTemplate)); }; const setUserDataPath = () => { - const appName = app.getName(); - const dirName = env.name === 'production' ? appName : `${ appName } (${ env.name })`; + const appName = app.getName(); + const dirName = env.name === 'production' ? appName : `${ appName } (${ env.name })`; - app.setPath('userData', path.join(app.getPath('appData'), dirName)); + app.setPath('userData', path.join(app.getPath('appData'), dirName)); }; const migrateOlderVersionUserData = () => { - const olderAppName = 'Rocket.Chat+'; - const dirName = env.name === 'production' ? olderAppName : `${ olderAppName } (${ env.name })`; - const olderUserDataPath = path.join(app.getPath('appData'), dirName); - - try { - jetpack.copy(olderUserDataPath, app.getPath('userData'), { overwrite: true }); - jetpack.remove(olderUserDataPath); - } catch (e) { - return; - } + const olderAppName = 'Rocket.Chat+'; + const dirName = env.name === 'production' ? olderAppName : `${ olderAppName } (${ env.name })`; + const olderUserDataPath = path.join(app.getPath('appData'), dirName); + + try { + jetpack.copy(olderUserDataPath, app.getPath('userData'), { overwrite: true }); + jetpack.remove(olderUserDataPath); + } catch (e) { + return; + } }; const parseProtocolUrls = (args) => - args.filter(arg => /^rocketchat:\/\/./.test(arg)) - .map(uri => url.parse(uri)) - .map(({ hostname, pathname, query }) => { - const { insecure } = querystring.parse(query); - return `${ insecure === 'true' ? 'http' : 'https' }://${ hostname }${ pathname || '' }`; - }); + args.filter((arg) => /^rocketchat:\/\/./.test(arg)) + .map((uri) => url.parse(uri)) + .map(({ hostname, pathname, query }) => { + const { insecure } = querystring.parse(query); + return `${ insecure === 'true' ? 'http' : 'https' }://${ hostname }${ pathname || '' }`; + }); const addServers = (protocolUrls) => parseProtocolUrls(protocolUrls) - .forEach(serverUrl => addServer(serverUrl)); + .forEach((serverUrl) => addServer(serverUrl)); const isSecondInstance = app.makeSingleInstance((argv) => { - addServers(argv.slice(2)); + addServers(argv.slice(2)); }); if (isSecondInstance && !process.mas) { - app.quit(); + app.quit(); } // macOS only app.on('open-url', (event, url) => { - event.preventDefault(); - addServers([ url ]); + event.preventDefault(); + addServers([url]); }); app.on('ready', () => { - unsetDefaultApplicationMenu(); - setUserDataPath(); - migrateOlderVersionUserData(); + unsetDefaultApplicationMenu(); + setUserDataPath(); + migrateOlderVersionUserData(); - if (!app.isDefaultProtocolClient('rocketchat')) { - app.setAsDefaultProtocolClient('rocketchat'); - } + if (!app.isDefaultProtocolClient('rocketchat')) { + app.setAsDefaultProtocolClient('rocketchat'); + } - createMainWindow(); + createMainWindow(); - getMainWindow().then(mainWindow => certificate.initWindow(mainWindow)); + getMainWindow().then((mainWindow) => certificate.initWindow(mainWindow)); - autoUpdate(); + autoUpdate(); }); app.on('window-all-closed', () => { - app.quit(); + app.quit(); }); ipcMain.on('getSystemIdleTime', (event) => { - event.returnValue = idle.getIdleTime(); + event.returnValue = idle.getIdleTime(); }); diff --git a/src/background/autoUpdate.js b/src/background/autoUpdate.js index 63accbfcf5..f2e0ef9f16 100644 --- a/src/background/autoUpdate.js +++ b/src/background/autoUpdate.js @@ -8,127 +8,127 @@ const userDataDir = jetpack.cwd(app.getPath('userData')); const updateSettingsFileName = 'update.json'; const loadUpdateSettings = (dir) => { - try { - return dir.read(updateSettingsFileName, 'json') || {}; - } catch (error) { - console.error(error); - return {}; - } + try { + return dir.read(updateSettingsFileName, 'json') || {}; + } catch (error) { + console.error(error); + return {}; + } }; const appUpdateSettings = loadUpdateSettings(appDir); const userUpdateSettings = loadUpdateSettings(userDataDir); const updateSettings = (() => { - const defaultUpdateSettings = { autoUpdate: true }; + const defaultUpdateSettings = { autoUpdate: true }; - if (appUpdateSettings.forced) { - return Object.assign({}, defaultUpdateSettings, appUpdateSettings); - } else { - return Object.assign({}, defaultUpdateSettings, appUpdateSettings, userUpdateSettings); - } + if (appUpdateSettings.forced) { + return Object.assign({}, defaultUpdateSettings, appUpdateSettings); + } else { + return Object.assign({}, defaultUpdateSettings, appUpdateSettings, userUpdateSettings); + } })(); delete updateSettings.forced; const saveUpdateSettings = () => { - if (appUpdateSettings.forced) { - return; - } + if (appUpdateSettings.forced) { + return; + } - userDataDir.write(updateSettingsFileName, userUpdateSettings, { atomic: true }); + userDataDir.write(updateSettingsFileName, userUpdateSettings, { atomic: true }); }; let checkForUpdatesEvent; -function updateDownloaded () { - dialog.showMessageBox({ - title: i18n.__('Update_ready'), - message: i18n.__('Update_ready_message'), - buttons: [ - i18n.__('Update_Install_Later'), - i18n.__('Update_Install_Now') - ], - defaultId: 1 - }, (response) => { - if (response === 0) { - dialog.showMessageBox({ - title: i18n.__('Update_installing_later'), - message: i18n.__('Update_installing_later_message') - }); - } else { - autoUpdater.quitAndInstall(); - setTimeout(() => app.quit(), 1000); - } - }); +function updateDownloaded() { + dialog.showMessageBox({ + title: i18n.__('Update_ready'), + message: i18n.__('Update_ready_message'), + buttons: [ + i18n.__('Update_Install_Later'), + i18n.__('Update_Install_Now'), + ], + defaultId: 1, + }, (response) => { + if (response === 0) { + dialog.showMessageBox({ + title: i18n.__('Update_installing_later'), + message: i18n.__('Update_installing_later_message'), + }); + } else { + autoUpdater.quitAndInstall(); + setTimeout(() => app.quit(), 1000); + } + }); } -function updateNotAvailable () { - if (checkForUpdatesEvent) { - checkForUpdatesEvent.sender.send('update-result', false); - checkForUpdatesEvent = null; - } +function updateNotAvailable() { + if (checkForUpdatesEvent) { + checkForUpdatesEvent.sender.send('update-result', false); + checkForUpdatesEvent = null; + } } -function updateAvailable ({version}) { - if (checkForUpdatesEvent) { - checkForUpdatesEvent.sender.send('update-result', true); - checkForUpdatesEvent = null; - } else if (updateSettings.skip === version) { - return; - } - - let window = new BrowserWindow({ - title: i18n.__('Update_Available'), - width: 600, - height: 330, - show : false, - center: true, - resizable: false, - maximizable: false, - minimizable: false - }); - - window.loadURL(`file://${__dirname}/public/update.html`); - window.setMenuBarVisibility(false); - - window.webContents.on('did-finish-load', () => { - window.webContents.send('new-version', version); - window.show(); - }); - - ipcMain.once('update-response', (e, type) => { - switch (type) { - case 'skip': - userUpdateSettings.skip = version; - saveUpdateSettings(); - dialog.showMessageBox({ - title: i18n.__('Update_skip'), - message: i18n.__('Update_skip_message') - }, () => window.close()); - break; - case 'remind': - dialog.showMessageBox({ - title: i18n.__('Update_remind'), - message: i18n.__('Update_remind_message') - }, () => window.close()); - break; - case 'update': - dialog.showMessageBox({ - title: i18n.__('Update_downloading'), - message: i18n.__('Update_downloading_message') - }, () => window.close()); - autoUpdater.downloadUpdate(); - break; - } - }); - - window.on('closed', () => { - window = null; - ipcMain.removeAllListeners('update-response'); - }); +function updateAvailable({ version }) { + if (checkForUpdatesEvent) { + checkForUpdatesEvent.sender.send('update-result', true); + checkForUpdatesEvent = null; + } else if (updateSettings.skip === version) { + return; + } + + let window = new BrowserWindow({ + title: i18n.__('Update_Available'), + width: 600, + height: 330, + show : false, + center: true, + resizable: false, + maximizable: false, + minimizable: false, + }); + + window.loadURL(`file://${ __dirname }/public/update.html`); + window.setMenuBarVisibility(false); + + window.webContents.on('did-finish-load', () => { + window.webContents.send('new-version', version); + window.show(); + }); + + ipcMain.once('update-response', (e, type) => { + switch (type) { + case 'skip': + userUpdateSettings.skip = version; + saveUpdateSettings(); + dialog.showMessageBox({ + title: i18n.__('Update_skip'), + message: i18n.__('Update_skip_message'), + }, () => window.close()); + break; + case 'remind': + dialog.showMessageBox({ + title: i18n.__('Update_remind'), + message: i18n.__('Update_remind_message'), + }, () => window.close()); + break; + case 'update': + dialog.showMessageBox({ + title: i18n.__('Update_downloading'), + message: i18n.__('Update_downloading_message'), + }, () => window.close()); + autoUpdater.downloadUpdate(); + break; + } + }); + + window.on('closed', () => { + window = null; + ipcMain.removeAllListeners('update-response'); + }); } export const canUpdate = () => - (process.platform === 'linux' && Boolean(process.env.APPIMAGE)) || + (process.platform === 'linux' && Boolean(process.env.APPIMAGE)) || (process.platform === 'win32' && !process.windowsStore) || (process.platform === 'darwin' && !process.mas); @@ -137,28 +137,28 @@ export const canAutoUpdate = () => updateSettings.autoUpdate !== false; export const canSetAutoUpdate = () => !appUpdateSettings.forced || appUpdateSettings.autoUpdate !== false; export const setAutoUpdate = (canAutoUpdate) => { - if (!canSetAutoUpdate()) { - return; - } + if (!canSetAutoUpdate()) { + return; + } - updateSettings.autoUpdate = userUpdateSettings.autoUpdate = Boolean(canAutoUpdate); - saveUpdateSettings(); + updateSettings.autoUpdate = userUpdateSettings.autoUpdate = Boolean(canAutoUpdate); + saveUpdateSettings(); }; ipcMain.on('can-update', (event) => { - event.returnValue = canUpdate(); + event.returnValue = canUpdate(); }); ipcMain.on('can-auto-update', (event) => { - event.returnValue = canAutoUpdate(); + event.returnValue = canAutoUpdate(); }); ipcMain.on('can-set-auto-update', (event) => { - event.returnValue = canSetAutoUpdate(); + event.returnValue = canSetAutoUpdate(); }); ipcMain.on('set-auto-update', (event, canAutoUpdate) => { - setAutoUpdate(canAutoUpdate); + setAutoUpdate(canAutoUpdate); }); autoUpdater.autoDownload = false; @@ -167,14 +167,14 @@ autoUpdater.on('update-not-available', updateNotAvailable); autoUpdater.on('update-downloaded', updateDownloaded); ipcMain.on('check-for-updates', (event) => { - if (canAutoUpdate() && canUpdate()) { - checkForUpdatesEvent = event; - autoUpdater.checkForUpdates(); - } + if (canAutoUpdate() && canUpdate()) { + checkForUpdatesEvent = event; + autoUpdater.checkForUpdates(); + } }); export default () => { - if (canAutoUpdate() && canUpdate()) { - autoUpdater.checkForUpdates(); - } + if (canAutoUpdate() && canUpdate()) { + autoUpdater.checkForUpdates(); + } }; diff --git a/src/background/certificate.js b/src/background/certificate.js index 3380ab5f87..85afe67d4e 100644 --- a/src/background/certificate.js +++ b/src/background/certificate.js @@ -4,108 +4,108 @@ import url from 'url'; import i18n from '../i18n/index.js'; class CertificateStore { - initWindow (win) { - this.storeFileName = 'certificate.json'; - this.userDataDir = jetpack.cwd(app.getPath('userData')); - - this.load(); - - // Don't ask twice for same cert if loading multiple urls - this.queued = {}; - - this.window = win; - app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { - event.preventDefault(); - if (this.isTrusted(url, certificate)) { - callback(true); - return; - } - - if (this.queued[certificate.fingerprint]) { - this.queued[certificate.fingerprint].push(callback); - // Call the callback after approved/rejected - return; - } else { - this.queued[certificate.fingerprint] = [callback]; - } - - let detail = `URL: ${url}\nError: ${error}`; - if (this.isExisting(url)) { - detail = i18n.__('Certificate_error_different', detail); - } - - dialog.showMessageBox(this.window, { - title: i18n.__('Certificate_error'), - message: i18n.__('Certificate_error_message', certificate.issuerName), - detail: detail, - type: 'warning', - buttons: [ - i18n.__('Yes'), - i18n.__('No') - ], - cancelId: 1 - }, (response) => { - if (response === 0) { - this.add(url, certificate); - this.save(); - if (webContents.getURL().indexOf('file://') === 0) { - webContents.send('certificate-reload', url); - } - } - //Call all queued callbacks with result - this.queued[certificate.fingerprint].forEach(cb => cb(response === 0)); - delete this.queued[certificate.fingerprint]; - }); - }); - } - - load () { - try { - this.data = this.userDataDir.read(this.storeFileName, 'json'); - } catch (e) { - console.error(e); - this.data = {}; - } - - if (this.data === undefined) { - this.clear(); - } - } - - clear () { - this.data = {}; - this.save(); - } - - save () { - this.userDataDir.write(this.storeFileName, this.data, { atomic: true }); - } - - parseCertificate (certificate) { - return certificate.issuerName + '\n' + certificate.data.toString(); - } - - getHost (certUrl) { - return url.parse(certUrl).host; - } - - add (certUrl, certificate) { - const host = this.getHost(certUrl); - this.data[host] = this.parseCertificate(certificate); - } - - isExisting (certUrl) { - const host = this.getHost(certUrl); - return this.data.hasOwnProperty(host); - } - - isTrusted (certUrl, certificate) { - const host = this.getHost(certUrl); - if (!this.isExisting(certUrl)) { - return false; - } - return this.data[host] === this.parseCertificate(certificate); - } + initWindow(win) { + this.storeFileName = 'certificate.json'; + this.userDataDir = jetpack.cwd(app.getPath('userData')); + + this.load(); + + // Don't ask twice for same cert if loading multiple urls + this.queued = {}; + + this.window = win; + app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { + event.preventDefault(); + if (this.isTrusted(url, certificate)) { + callback(true); + return; + } + + if (this.queued[certificate.fingerprint]) { + this.queued[certificate.fingerprint].push(callback); + // Call the callback after approved/rejected + return; + } else { + this.queued[certificate.fingerprint] = [callback]; + } + + let detail = `URL: ${ url }\nError: ${ error }`; + if (this.isExisting(url)) { + detail = i18n.__('Certificate_error_different', detail); + } + + dialog.showMessageBox(this.window, { + title: i18n.__('Certificate_error'), + message: i18n.__('Certificate_error_message', certificate.issuerName), + detail, + type: 'warning', + buttons: [ + i18n.__('Yes'), + i18n.__('No'), + ], + cancelId: 1, + }, (response) => { + if (response === 0) { + this.add(url, certificate); + this.save(); + if (webContents.getURL().indexOf('file://') === 0) { + webContents.send('certificate-reload', url); + } + } + // Call all queued callbacks with result + this.queued[certificate.fingerprint].forEach((cb) => cb(response === 0)); + delete this.queued[certificate.fingerprint]; + }); + }); + } + + load() { + try { + this.data = this.userDataDir.read(this.storeFileName, 'json'); + } catch (e) { + console.error(e); + this.data = {}; + } + + if (this.data === undefined) { + this.clear(); + } + } + + clear() { + this.data = {}; + this.save(); + } + + save() { + this.userDataDir.write(this.storeFileName, this.data, { atomic: true }); + } + + parseCertificate(certificate) { + return `${ certificate.issuerName }\n${ certificate.data.toString() }`; + } + + getHost(certUrl) { + return url.parse(certUrl).host; + } + + add(certUrl, certificate) { + const host = this.getHost(certUrl); + this.data[host] = this.parseCertificate(certificate); + } + + isExisting(certUrl) { + const host = this.getHost(certUrl); + return this.data.hasOwnProperty(host); + } + + isTrusted(certUrl, certificate) { + const host = this.getHost(certUrl); + if (!this.isExisting(certUrl)) { + return false; + } + return this.data[host] === this.parseCertificate(certificate); + } } const certificateStore = new CertificateStore(); diff --git a/src/background/mainWindow.js b/src/background/mainWindow.js index 145f490eb2..0412e5ad41 100644 --- a/src/background/mainWindow.js +++ b/src/background/mainWindow.js @@ -13,115 +13,115 @@ import env from '../env'; let mainWindow = null; const mainWindowOptions = { - width: 1000, - height: 600, - minWidth: 600, - minHeight: 400, - titleBarStyle: 'hidden', - show: false + width: 1000, + height: 600, + minWidth: 600, + minHeight: 400, + titleBarStyle: 'hidden', + show: false, }; const attachWindowStateHandling = (mainWindow) => { - const mainWindowState = windowStateKeeper('main', mainWindowOptions); - - mainWindow.once('ready-to-show', () => mainWindowState.loadState(mainWindow)); - - // macOS only - app.on('activate', () => { - mainWindow.show(); - }); - - app.on('before-quit', () => { - mainWindowState.saveState(mainWindow); - mainWindowState.saveState.flush(); - mainWindow = null; - }); - - mainWindow.on('show', () => { - mainWindowState.saveState(mainWindow); - }); - - mainWindow.on('close', function (event) { - if (!mainWindow) { - return; - } - - event.preventDefault(); - if (mainWindow.isFullScreen()) { - mainWindow.once('leave-full-screen', () => { - mainWindow.hide(); - }); - mainWindow.setFullScreen(false); - } else { - mainWindow.hide(); - } - mainWindowState.saveState(mainWindow); - }); - - mainWindow.on('resize', () => { - mainWindowState.saveState(mainWindow); - }); - - mainWindow.on('move', () => { - mainWindowState.saveState(mainWindow); - }); + const mainWindowState = windowStateKeeper('main', mainWindowOptions); + + mainWindow.once('ready-to-show', () => mainWindowState.loadState(mainWindow)); + + // macOS only + app.on('activate', () => { + mainWindow.show(); + }); + + app.on('before-quit', () => { + mainWindowState.saveState(mainWindow); + mainWindowState.saveState.flush(); + mainWindow = null; + }); + + mainWindow.on('show', () => { + mainWindowState.saveState(mainWindow); + }); + + mainWindow.on('close', function(event) { + if (!mainWindow) { + return; + } + + event.preventDefault(); + if (mainWindow.isFullScreen()) { + mainWindow.once('leave-full-screen', () => { + mainWindow.hide(); + }); + mainWindow.setFullScreen(false); + } else { + mainWindow.hide(); + } + mainWindowState.saveState(mainWindow); + }); + + mainWindow.on('resize', () => { + mainWindowState.saveState(mainWindow); + }); + + mainWindow.on('move', () => { + mainWindowState.saveState(mainWindow); + }); }; const attachIpcMessageHandling = (mainWindow) => { - ipcMain.on('focus', () => { - mainWindow.show(); - }); - - ipcMain.on('update-taskbar-icon', (event, data, text) => { - const img = nativeImage.createFromDataURL(data); - mainWindow.setOverlayIcon(img, text); - }); + ipcMain.on('focus', () => { + mainWindow.show(); + }); + + ipcMain.on('update-taskbar-icon', (event, data, text) => { + const img = nativeImage.createFromDataURL(data); + mainWindow.setOverlayIcon(img, text); + }); }; export const createMainWindow = (cb) => { - if (mainWindow) { - cb && cb(mainWindow); - return; - } + if (mainWindow) { + cb && cb(mainWindow); + return; + } - mainWindow = new BrowserWindow(mainWindowOptions); - attachWindowStateHandling(mainWindow); - attachIpcMessageHandling(mainWindow); + mainWindow = new BrowserWindow(mainWindowOptions); + attachWindowStateHandling(mainWindow); + attachIpcMessageHandling(mainWindow); - mainWindow.webContents.on('will-navigate', (event) => { - event.preventDefault(); - }); + mainWindow.webContents.on('will-navigate', (event) => { + event.preventDefault(); + }); - const appUrl = url.format({ - pathname: path.join(__dirname, 'public', 'app.html'), - protocol: 'file:', - slashes: true - }); + const appUrl = url.format({ + pathname: path.join(__dirname, 'public', 'app.html'), + protocol: 'file:', + slashes: true, + }); - mainWindow.loadURL(appUrl); + mainWindow.loadURL(appUrl); - if (env.name === 'development') { - mainWindow.openDevTools(); - } + if (env.name === 'development') { + mainWindow.openDevTools(); + } - cb && cb(mainWindow); + cb && cb(mainWindow); }; export const getMainWindow = () => new Promise((resolve) => { - if (app.isReady()) { - createMainWindow(resolve); - return; - } + if (app.isReady()) { + createMainWindow(resolve); + return; + } - app.on('ready', () => createMainWindow(resolve)); + app.on('ready', () => createMainWindow(resolve)); }); export const addServer = (serverUrl) => getMainWindow().then((mainWindow) => { - mainWindow.send('add-host', serverUrl); + mainWindow.send('add-host', serverUrl); - mainWindow.show(); + mainWindow.show(); - if (mainWindow.isMinimized()) { - mainWindow.restore(); - } + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } }); diff --git a/src/background/screenshare.js b/src/background/screenshare.js index db99d4d856..db73809c7d 100644 --- a/src/background/screenshare.js +++ b/src/background/screenshare.js @@ -2,34 +2,34 @@ import { BrowserWindow, ipcMain } from 'electron'; let screenshareEvent; ipcMain.on('screenshare', (event, sources) => { - screenshareEvent = event; - let mainWindow = new BrowserWindow({ - width: 776, - height: 600, - show : false, - skipTaskbar: false - }); + screenshareEvent = event; + let mainWindow = new BrowserWindow({ + width: 776, + height: 600, + show : false, + skipTaskbar: false, + }); - mainWindow.loadURL('file://'+__dirname+'/../public/screenshare.html'); + mainWindow.loadURL(`file://${ __dirname }/../public/screenshare.html`); - //window.openDevTools(); - mainWindow.webContents.on('did-finish-load', () => { - mainWindow.webContents.send('sources', sources); - mainWindow.show(); - }); + // window.openDevTools(); + mainWindow.webContents.on('did-finish-load', () => { + mainWindow.webContents.send('sources', sources); + mainWindow.show(); + }); - mainWindow.on('closed', () => { - mainWindow = null; - if (screenshareEvent) { - screenshareEvent.sender.send('screenshare-result', 'PermissionDeniedError'); - screenshareEvent = null; - } - }); + mainWindow.on('closed', () => { + mainWindow = null; + if (screenshareEvent) { + screenshareEvent.sender.send('screenshare-result', 'PermissionDeniedError'); + screenshareEvent = null; + } + }); }); ipcMain.on('source-result', (e, sourceId) => { - if (screenshareEvent) { - screenshareEvent.sender.send('screenshare-result', sourceId); - screenshareEvent = null; - } + if (screenshareEvent) { + screenshareEvent.sender.send('screenshare-result', sourceId); + screenshareEvent = null; + } }); diff --git a/src/background/servers.js b/src/background/servers.js index 66675a7b4c..3aeb07152d 100644 --- a/src/background/servers.js +++ b/src/background/servers.js @@ -3,21 +3,21 @@ import { app } from 'electron'; let servers = {}; export default { - loadServers (s) { - servers = s; - }, + loadServers(s) { + servers = s; + }, - getServers () { - return servers; - } + getServers() { + return servers; + }, }; -app.on('login', function (event, webContents, request, authInfo, callback) { - for (const url of Object.keys(servers)) { - const server = servers[url]; - if (request.url.indexOf(url) === 0 && server.username) { - callback(server.username, server.password); - break; - } - } +app.on('login', function(event, webContents, request, authInfo, callback) { + for (const url of Object.keys(servers)) { + const server = servers[url]; + if (request.url.indexOf(url) === 0 && server.username) { + callback(server.username, server.password); + break; + } + } }); diff --git a/src/background/windowState.js b/src/background/windowState.js index a1e77d1f38..0e9c974b82 100644 --- a/src/background/windowState.js +++ b/src/background/windowState.js @@ -6,63 +6,63 @@ import { app } from 'electron'; import jetpack from 'fs-jetpack'; import { debounce } from 'lodash'; -export default function (name, defaults) { +export default function(name, defaults) { - let state = { - width: defaults.width, - height: defaults.height - }; + let state = { + width: defaults.width, + height: defaults.height, + }; - const userDataDir = jetpack.cwd(app.getPath('userData')); - const stateStoreFile = `window-state-${name}.json`; + const userDataDir = jetpack.cwd(app.getPath('userData')); + const stateStoreFile = `window-state-${ name }.json`; - try { - state = userDataDir.read(stateStoreFile, 'json') || state; - } catch (err) { - console.error(`Failed to load "${ name }" window state`); - console.error(err); - } + try { + state = userDataDir.read(stateStoreFile, 'json') || state; + } catch (err) { + console.error(`Failed to load "${ name }" window state`); + console.error(err); + } - const saveState = function (window) { - if (window.isDestroyed()) { - return; - } + const saveState = function(window) { + if (window.isDestroyed()) { + return; + } - state.isMaximized = window.isMaximized(); - state.isMinimized = window.isMinimized(); - state.isHidden = !window.isMinimized() && !window.isVisible(); + state.isMaximized = window.isMaximized(); + state.isMinimized = window.isMinimized(); + state.isHidden = !window.isMinimized() && !window.isVisible(); - if (!state.isMaximized && !state.isHidden) { - [ state.x, state.y ] = window.getPosition(); - [ state.width, state.height ] = window.getSize(); - } + if (!state.isMaximized && !state.isHidden) { + [state.x, state.y] = window.getPosition(); + [state.width, state.height] = window.getSize(); + } - userDataDir.write(stateStoreFile, state, { atomic: true }); - }; + userDataDir.write(stateStoreFile, state, { atomic: true }); + }; - const loadState = function (window) { - if (this.x !== undefined && this.y !== undefined) { - window.setPosition(this.x, this.y, false); - } + const loadState = function(window) { + if (this.x !== undefined && this.y !== undefined) { + window.setPosition(this.x, this.y, false); + } - if (this.width !== undefined && this.height !== undefined) { - window.setSize(this.width, this.height, false); - } + if (this.width !== undefined && this.height !== undefined) { + window.setSize(this.width, this.height, false); + } - this.isMaximized ? window.maximize() : window.unmaximize(); - this.isMinimized ? window.minimize() : window.restore(); - this.isHidden ? window.hide() : window.show(); - }; + this.isMaximized ? window.maximize() : window.unmaximize(); + this.isMinimized ? window.minimize() : window.restore(); + this.isHidden ? window.hide() : window.show(); + }; - return { - get x () { return state.x && Math.floor(state.x); }, - get y () { return state.y && Math.floor(state.y); }, - get width () { return state.width && Math.floor(state.width); }, - get height () { return state.height && Math.floor(state.height); }, - get isMaximized () { return state.isMaximized; }, - get isMinimized () { return state.isMinimized; }, - get isHidden () { return state.isHidden; }, - saveState: debounce(saveState, 1000), // see https://github.com/RocketChat/Rocket.Chat.Electron/issues/181 - loadState - }; + return { + get x() { return state.x && Math.floor(state.x); }, + get y() { return state.y && Math.floor(state.y); }, + get width() { return state.width && Math.floor(state.width); }, + get height() { return state.height && Math.floor(state.height); }, + get isMaximized() { return state.isMaximized; }, + get isMinimized() { return state.isMinimized; }, + get isHidden() { return state.isHidden; }, + saveState: debounce(saveState, 1000), // see https://github.com/RocketChat/Rocket.Chat.Electron/issues/181 + loadState, + }; } diff --git a/src/e2e/.eslintrc b/src/e2e/.eslintrc new file mode 100644 index 0000000000..22cafb1ac6 --- /dev/null +++ b/src/e2e/.eslintrc @@ -0,0 +1,8 @@ +{ + "extends": [ + "@rocket.chat/eslint-config" + ], + "env": { + "mocha": true + } +} diff --git a/src/e2e/app.e2e.js b/src/e2e/app.e2e.js new file mode 100644 index 0000000000..dd7abc8d15 --- /dev/null +++ b/src/e2e/app.e2e.js @@ -0,0 +1,14 @@ +import { expect } from 'chai'; +import { app, startApp, stopApp } from './utils'; + +import appManifest from '../../package.json'; + +describe('application', function() { + before(startApp); + after(stopApp); + + it('shows the main window', async function() { + expect(await app.browserWindow.isVisible()).to.be.true; + expect(await app.browserWindow.getTitle()).to.be.equal(appManifest.productName); + }); +}); diff --git a/src/e2e/utils.js b/src/e2e/utils.js new file mode 100644 index 0000000000..60b40d410f --- /dev/null +++ b/src/e2e/utils.js @@ -0,0 +1,87 @@ +import path from 'path'; +import electron from 'electron'; +import { Application } from 'spectron'; + +export let app = null; +let logFetchInterval = null; + +const fetchLogs = async() => { + const logs = await app.client.getMainProcessLogs(); + logs.forEach((log) => console.log(log)); +}; + +export async function startApp() { + this.timeout(10000); + + app = new Application({ + path: electron, + cwd: process.cwd(), + args: [path.join(__dirname, '..')], + quitTimeout: 5000, + startTimeout: 5000, + waitTimeout: 5000, + }); + + await app.start(); + await app.client.waitUntilWindowLoaded(); + + logFetchInterval = setInterval(fetchLogs, 100); +} + +export async function stopApp() { + this.timeout(10000); + + if (app && app.isRunning()) { + clearInterval(logFetchInterval); + fetchLogs(); + await app.stop(); + app = null; + } +} + +export const menuItem = (menuId) => ({ + get exists() { + return app.client.execute((menuId) => { + const { Menu } = require('electron').remote; + const appMenu = Menu.getApplicationMenu(); + const menuItem = appMenu.getMenuItemById(menuId); + return !!menuItem; + }, menuId).then(({ value }) => value); + }, + + get enabled() { + return app.client.execute((menuId) => { + const { Menu } = require('electron').remote; + const appMenu = Menu.getApplicationMenu(); + const menuItem = appMenu.getMenuItemById(menuId); + return menuItem.enabled; + }, menuId).then(({ value }) => value); + }, + + get visible() { + return app.client.execute((menuId) => { + const { Menu } = require('electron').remote; + const appMenu = Menu.getApplicationMenu(); + const menuItem = appMenu.getMenuItemById(menuId); + return menuItem.visible; + }, menuId).then(({ value }) => value); + }, + + get label() { + return app.client.execute((menuId) => { + const { Menu } = require('electron').remote; + const appMenu = Menu.getApplicationMenu(); + const menuItem = appMenu.getMenuItemById(menuId); + return menuItem.label; + }, menuId).then(({ value }) => value); + }, + + click() { + return app.client.execute((menuId) => { + const { Menu } = require('electron').remote; + const appMenu = Menu.getApplicationMenu(); + const menuItem = appMenu.getMenuItemById(menuId); + menuItem.click(); + }, menuId); + }, +}); diff --git a/src/helpers/window.js b/src/helpers/window.js index c9d242e59a..cb2c581dce 100644 --- a/src/helpers/window.js +++ b/src/helpers/window.js @@ -6,75 +6,75 @@ import { app, BrowserWindow, screen } from 'electron'; import jetpack from 'fs-jetpack'; -export default function (name, options) { - const userDataDir = jetpack.cwd(app.getPath('userData')); - const stateStoreFile = 'window-state-' + name +'.json'; - const defaultSize = { - width: options.width, - height: options.height - }; - let state = {}; - const win = new BrowserWindow(Object.assign({}, options, state)); +export default function(name, options) { + const userDataDir = jetpack.cwd(app.getPath('userData')); + const stateStoreFile = `window-state-${ name }.json`; + const defaultSize = { + width: options.width, + height: options.height, + }; + let state = {}; + const win = new BrowserWindow(Object.assign({}, options, state)); - const restore = function () { - let restoredState = {}; - try { - restoredState = userDataDir.read(stateStoreFile, 'json'); - } catch (err) { - // For some reason json can't be read (might be corrupted). - // No worries, we have defaults. - } - return Object.assign({}, defaultSize, restoredState); - }; + const restore = function() { + let restoredState = {}; + try { + restoredState = userDataDir.read(stateStoreFile, 'json'); + } catch (err) { + // For some reason json can't be read (might be corrupted). + // No worries, we have defaults. + } + return Object.assign({}, defaultSize, restoredState); + }; - const getCurrentPosition = function () { - const position = win.getPosition(); - const size = win.getSize(); - return { - x: Math.floor(position[0]), - y: Math.floor(position[1]), - width: Math.floor(size[0]), - height: Math.floor(size[1]) - }; - }; + const getCurrentPosition = function() { + const position = win.getPosition(); + const size = win.getSize(); + return { + x: Math.floor(position[0]), + y: Math.floor(position[1]), + width: Math.floor(size[0]), + height: Math.floor(size[1]), + }; + }; - const windowWithinBounds = function (windowState, bounds) { - return windowState.x >= bounds.x && + const windowWithinBounds = function(windowState, bounds) { + return windowState.x >= bounds.x && windowState.y >= bounds.y && windowState.x + windowState.width <= bounds.x + bounds.width && windowState.y + windowState.height <= bounds.y + bounds.height; - }; + }; - const resetToDefaults = function (/*windowState*/) { - const bounds = screen.getPrimaryDisplay().bounds; - return Object.assign({}, defaultSize, { - x: (bounds.width - defaultSize.width) / 2, - y: (bounds.height - defaultSize.height) / 2 - }); - }; + const resetToDefaults = function(/* windowState*/) { + const { bounds } = screen.getPrimaryDisplay(); + return Object.assign({}, defaultSize, { + x: (bounds.width - defaultSize.width) / 2, + y: (bounds.height - defaultSize.height) / 2, + }); + }; - const ensureVisibleOnSomeDisplay = function (windowState) { - const visible = screen.getAllDisplays().some(function (display) { - return windowWithinBounds(windowState, display.bounds); - }); - if (!visible) { - // Window is partially or fully not visible now. - // Reset it to safe defaults. - return resetToDefaults(windowState); - } - return windowState; - }; + const ensureVisibleOnSomeDisplay = function(windowState) { + const visible = screen.getAllDisplays().some(function(display) { + return windowWithinBounds(windowState, display.bounds); + }); + if (!visible) { + // Window is partially or fully not visible now. + // Reset it to safe defaults. + return resetToDefaults(windowState); + } + return windowState; + }; - const saveState = function () { - if (!win.isMinimized() && !win.isMaximized()) { - Object.assign(state, getCurrentPosition()); - } - userDataDir.write(stateStoreFile, state, { atomic: true }); - }; + const saveState = function() { + if (!win.isMinimized() && !win.isMaximized()) { + Object.assign(state, getCurrentPosition()); + } + userDataDir.write(stateStoreFile, state, { atomic: true }); + }; - state = ensureVisibleOnSomeDisplay(restore()); + state = ensureVisibleOnSomeDisplay(restore()); - win.on('close', saveState); + win.on('close', saveState); - return win; + return win; } diff --git a/src/i18n/index.js b/src/i18n/index.js index d0800010ca..853b653434 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -13,61 +13,61 @@ let loadedLanguage = []; * @param {number} chount Count to check for singular / plural (0-1,2-n) * @returns {string} Translation in user language */ -function loadTranslation (phrase = '', count) { - const loadedLanguageTranslation = loadedLanguage[phrase]; - let translation = loadedLanguageTranslation; - if (loadedLanguageTranslation === undefined) { - translation = phrase; - } else if (loadedLanguageTranslation instanceof Object) { - translation = loadedLanguageTranslation['one']; - if (count > 1) { - translation = loadedLanguageTranslation['multi']; - } - } - return translation; +function loadTranslation(phrase = '', count) { + const loadedLanguageTranslation = loadedLanguage[phrase]; + let translation = loadedLanguageTranslation; + if (loadedLanguageTranslation === undefined) { + translation = phrase; + } else if (loadedLanguageTranslation instanceof Object) { + translation = loadedLanguageTranslation.one; + if (count > 1) { + translation = loadedLanguageTranslation.multi; + } + } + return translation; } class I18n { - /** + /** * Load users language if available, and fallback to english for any missing strings * @constructor */ - constructor () { - let dir = path.join(__dirname, '../i18n/lang'); - if (!fs.existsSync(dir)) { - dir = path.join(__dirname, 'i18n/lang'); - } - const defaultLocale = path.join(dir, 'en.i18n.json'); - loadedLanguage = JSON.parse(fs.readFileSync(defaultLocale, 'utf8')); - const locale = path.join(dir, `${eApp.getLocale()}.i18n.json`); - if (fs.existsSync(locale)) { - const lang = JSON.parse(fs.readFileSync(locale, 'utf8')); - loadedLanguage = Object.assign(loadedLanguage, lang); - } - } + constructor() { + let dir = path.join(__dirname, '../i18n/lang'); + if (!fs.existsSync(dir)) { + dir = path.join(__dirname, 'i18n/lang'); + } + const defaultLocale = path.join(dir, 'en.i18n.json'); + loadedLanguage = JSON.parse(fs.readFileSync(defaultLocale, 'utf8')); + const locale = path.join(dir, `${ eApp.getLocale() }.i18n.json`); + if (fs.existsSync(locale)) { + const lang = JSON.parse(fs.readFileSync(locale, 'utf8')); + loadedLanguage = Object.assign(loadedLanguage, lang); + } + } - /** + /** * Get translation string * @param {string} phrase The key for the translation string * @param {...string|number} replacements List of replacements in template strings * @return {string} Translation in users language */ - __ (phrase, ...replacements) { - const translation = loadTranslation(phrase, 0); - return util.format(translation, ...replacements); - } + __(phrase, ...replacements) { + const translation = loadTranslation(phrase, 0); + return util.format(translation, ...replacements); + } - /** + /** * Get translation string * @param {string} phrase The key for the translation string * @param {number} count Count to check for singular / plural (0-1,2-n) * @param {...string|number} replacements List of replacements in template strings * @return {string} Translation in users language */ - pluralize (phrase, count, ...replacements) { - const translation = loadTranslation(phrase, count); - return util.format(translation, ...replacements); - } + pluralize(phrase, count, ...replacements) { + const translation = loadTranslation(phrase, count); + return util.format(translation, ...replacements); + } } export default new I18n(); diff --git a/src/public/helpers/context_menu.js b/src/public/helpers/context_menu.js index 02e2f8208b..8d025e03f0 100644 --- a/src/public/helpers/context_menu.js +++ b/src/public/helpers/context_menu.js @@ -2,59 +2,58 @@ // in all input fields and textareas across your app. const i18n = require('../../i18n'); -(function () { - 'use strict'; - - const remote = require('electron').remote; - const Menu = remote.Menu; - const MenuItem = remote.MenuItem; - - const isAnyTextSelected = function () { - return window.getSelection().toString() !== ''; - }; - - const cut = new MenuItem({ - label: i18n.__('Cut'), - click: function () { - document.execCommand("cut"); - } - }); - - const copy = new MenuItem({ - label: i18n.__('Copy'), - click: function () { - document.execCommand("copy"); - } - }); - - const paste = new MenuItem({ - label: i18n.__('Paste'), - click: function () { - document.execCommand("paste"); - } - }); - - const normalMenu = new Menu(); - normalMenu.append(copy); - - const textEditingMenu = new Menu(); - textEditingMenu.append(cut); - textEditingMenu.append(copy); - textEditingMenu.append(paste); - - document.addEventListener('contextmenu', function (e) { - switch (e.target.nodeName) { - case 'TEXTAREA': - case 'INPUT': - e.preventDefault(); - textEditingMenu.popup(remote.getCurrentWindow()); - break; - default: - if (isAnyTextSelected()) { - e.preventDefault(); - normalMenu.popup(remote.getCurrentWindow()); - } - } - }, false); +(function() { + 'use strict'; + + const { remote } = require('electron'); + const { Menu, MenuItem } = remote; + + const isAnyTextSelected = function() { + return window.getSelection().toString() !== ''; + }; + + const cut = new MenuItem({ + label: i18n.__('Cut'), + click() { + document.execCommand('cut'); + }, + }); + + const copy = new MenuItem({ + label: i18n.__('Copy'), + click() { + document.execCommand('copy'); + }, + }); + + const paste = new MenuItem({ + label: i18n.__('Paste'), + click() { + document.execCommand('paste'); + }, + }); + + const normalMenu = new Menu(); + normalMenu.append(copy); + + const textEditingMenu = new Menu(); + textEditingMenu.append(cut); + textEditingMenu.append(copy); + textEditingMenu.append(paste); + + document.addEventListener('contextmenu', function(e) { + switch (e.target.nodeName) { + case 'TEXTAREA': + case 'INPUT': + e.preventDefault(); + textEditingMenu.popup(remote.getCurrentWindow()); + break; + default: + if (isAnyTextSelected()) { + e.preventDefault(); + normalMenu.popup(remote.getCurrentWindow()); + } + } + }, false); }()); diff --git a/src/public/jitsi-preload.js b/src/public/jitsi-preload.js index 6a2a31e45e..22a2d5d67d 100644 --- a/src/public/jitsi-preload.js +++ b/src/public/jitsi-preload.js @@ -5,8 +5,8 @@ const { remote } = require('electron'); const selfBrowserWindow = remote.getCurrentWindow(); selfBrowserWindow.webContents.once('dom-ready', () => { - window.JitsiMeetElectron = { - /** + window.JitsiMeetElectron = { + /** * Get sources available for screensharing. The callback is invoked * with an array of DesktopCapturerSources. * @@ -21,16 +21,16 @@ selfBrowserWindow.webContents.once('dom-ready', () => { * default electron will return images with height and width of * 150px. */ - obtainDesktopStreams (callback, errorCallback, options = {}) { - electron.desktopCapturer.getSources(options, - (error, sources) => { - if (error) { - errorCallback(error); - return; - } + obtainDesktopStreams(callback, errorCallback, options = {}) { + electron.desktopCapturer.getSources(options, + (error, sources) => { + if (error) { + errorCallback(error); + return; + } - callback(sources); - }); - } - }; + callback(sources); + }); + }, + }; }); diff --git a/src/public/lib/Notification.js b/src/public/lib/Notification.js index 797c6dea5e..81fba05945 100644 --- a/src/public/lib/Notification.js +++ b/src/public/lib/Notification.js @@ -1,39 +1,39 @@ const { ipcRenderer, remote } = require('electron'); if (process.platform === 'darwin') { - const NodeNotification = require('node-mac-notifier'); - window.Notification = class Notification extends NodeNotification { - constructor (title, options) { - options.bundleId = `chat.rocket`; - super(title, options); - this.addEventListener('click', (/*notification*/) => this.onclick()); - } + const NodeNotification = require('node-mac-notifier'); + window.Notification = class Notification extends NodeNotification { + constructor(title, options) { + options.bundleId = 'chat.rocket'; + super(title, options); + this.addEventListener('click', (/* notification*/) => this.onclick()); + } - static requestPermission () { - return; - } + static requestPermission() { + return; + } - static get permission () { - return 'granted'; - } - }; + static get permission() { + return 'granted'; + } + }; } class Notification extends window.Notification { - constructor (title, options) { - super(title, options); - ipcRenderer.send('notification-shim', title, options); + constructor(title, options) { + super(title, options); + ipcRenderer.send('notification-shim', title, options); - // Handle correct notification using unique tag - ipcRenderer.once(`clicked-${options.tag}`, () => this.onclick()); - } + // Handle correct notification using unique tag + ipcRenderer.once(`clicked-${ options.tag }`, () => this.onclick()); + } - get onclick () { - return super.onclick; - } + get onclick() { + return super.onclick; + } - /* + /* set onclick (fn) { const result = super.onclick = () => { ipcRenderer.send('focus'); @@ -44,28 +44,28 @@ class Notification extends window.Notification { } */ - set onclick (fn) { - const result = super.onclick = () => { - const currentWindow = remote.getCurrentWindow(); - if (process.platform === 'win32') { - if (currentWindow.isVisible()) { - currentWindow.focus(); - } else if (currentWindow.isMinimized()) { - currentWindow.restore(); - } else { - currentWindow.show(); - } - } else if (currentWindow.isMinimized()) { - currentWindow.restore(); - } else { - currentWindow.show(); - } + set onclick(fn) { + const result = super.onclick = (...args) => { + const currentWindow = remote.getCurrentWindow(); + if (process.platform === 'win32') { + if (currentWindow.isVisible()) { + currentWindow.focus(); + } else if (currentWindow.isMinimized()) { + currentWindow.restore(); + } else { + currentWindow.show(); + } + } else if (currentWindow.isMinimized()) { + currentWindow.restore(); + } else { + currentWindow.show(); + } - ipcRenderer.sendToHost('focus'); - fn.apply(this, arguments); - }; - return result; - } + ipcRenderer.sendToHost('focus'); + fn.apply(this, args); + }; + return result; + } } module.exports = Notification; diff --git a/src/public/lib/SpellCheck.js b/src/public/lib/SpellCheck.js index e8e7d707fe..0e79708b98 100644 --- a/src/public/lib/SpellCheck.js +++ b/src/public/lib/SpellCheck.js @@ -13,371 +13,375 @@ const isWindows = ['win32', 'win64'].indexOf(os.platform()) !== -1; class SpellCheck { - get userLanguage () { - const lang = localStorage.getItem('userLanguage'); - if (lang) { - return lang.replace('-', '_'); - } - } - - get dictionaries () { - const dictionaries = localStorage.getItem('spellcheckerDictionaries'); - if (dictionaries) { - const result = JSON.parse(dictionaries); - if (Array.isArray(result)) { - return result; - } - } - } - - constructor () { - this.enabledDictionaries = []; - this.contractions = this.getContractions(); - this.loadAvailableDictionaries(); - this.setEnabledDictionaries(); - - this.languagesMenu = { - label: i18n.__('Spelling_languages'), - submenu: this.availableDictionaries.map((dictionary) => { - const menu = { - label: dictionary, - type: 'checkbox', - checked: this.enabledDictionaries.includes(dictionary), - click: (menuItem) => { - menu.checked = menuItem.checked; - // If not using os dictionary then limit to only 1 language - if (!this.multiLanguage && this.languagesMenu.submenu) { - this.languagesMenu.submenu.forEach((m) => { - if (m.label !== menuItem.label) { - m.checked = false; - } - }); - } - if (menuItem.checked) { - this.setEnabled(dictionary); - } else { - this.disable(dictionary); - } - this.saveEnabledDictionaries(); - } - }; - return menu; - }) - }; - - this.browseForLanguageMenu = new MenuItem({ - label: i18n.__('Browse_for_language'), - click: () => { - dialog.showOpenDialog({ - title: i18n.__('Open_Language_Dictionary'), - defaultPath: this.dictionariesPath, - filters: {name: 'Dictionaries', extensions: ['aff', 'dic']}, - properties: ['openFile', 'multiSelections'] - }, - (filePaths) => { this.installDictionariesFromPaths(filePaths); } - ); - } - }); - } - - /** + get userLanguage() { + const lang = localStorage.getItem('userLanguage'); + if (lang) { + return lang.replace('-', '_'); + } + + return undefined; + } + + get dictionaries() { + const dictionaries = localStorage.getItem('spellcheckerDictionaries'); + if (dictionaries) { + const result = JSON.parse(dictionaries); + if (Array.isArray(result)) { + return result; + } + } + + return undefined; + } + + constructor() { + this.enabledDictionaries = []; + this.contractions = this.getContractions(); + this.loadAvailableDictionaries(); + this.setEnabledDictionaries(); + + this.languagesMenu = { + label: i18n.__('Spelling_languages'), + submenu: this.availableDictionaries.map((dictionary) => { + const menu = { + label: dictionary, + type: 'checkbox', + checked: this.enabledDictionaries.includes(dictionary), + click: (menuItem) => { + menu.checked = menuItem.checked; + // If not using os dictionary then limit to only 1 language + if (!this.multiLanguage && this.languagesMenu.submenu) { + this.languagesMenu.submenu.forEach((m) => { + if (m.label !== menuItem.label) { + m.checked = false; + } + }); + } + if (menuItem.checked) { + this.setEnabled(dictionary); + } else { + this.disable(dictionary); + } + this.saveEnabledDictionaries(); + }, + }; + return menu; + }), + }; + + this.browseForLanguageMenu = new MenuItem({ + label: i18n.__('Browse_for_language'), + click: () => { + dialog.showOpenDialog({ + title: i18n.__('Open_Language_Dictionary'), + defaultPath: this.dictionariesPath, + filters: { name: 'Dictionaries', extensions: ['aff', 'dic'] }, + properties: ['openFile', 'multiSelections'], + }, + (filePaths) => { this.installDictionariesFromPaths(filePaths); } + ); + }, + }); + } + + /** * Set enabled dictionaries on load * Either sets enabled dictionaries to saved preferences, or enables the first * dictionary that is valid based on system (defaults to en_US) */ - setEnabledDictionaries () { - const dictionaries = this.dictionaries; - if (dictionaries) { - // Dictionary disabled - if (dictionaries.length === 0) { - return; - } - if (this.setEnabled(dictionaries)) { - return; - } - } - - if (this.userLanguage) { - if (this.setEnabled(this.userLanguage)) { - return; - } - if (this.userLanguage.includes('_') && this.setEnabled(this.userLanguage.split('_')[0])) { - return; - } - } - - const navigatorLanguage = navigator.language.replace('-', '_'); - if (this.setEnabled(navigatorLanguage)) { - return; - } - - if (navigatorLanguage.includes('_') && this.setEnabled(navigatorLanguage.split('_')[0])) { - return; - } - - if (this.setEnabled('en_US')) { - return; - } - - if (!this.setEnabled('en')) { - console.info('Unable to set a language for the spell checker - Spell checker is disabled'); - } - - } - - loadAvailableDictionaries () { - this.availableDictionaries = checker.getAvailableDictionaries().sort(); - if (this.availableDictionaries.length === 0) { - this.multiLanguage = false; - // Dictionaries path is correct for build - this.dictionariesPath = path.join(remote.app.getAppPath(), '../dictionaries'); - this.getDictionariesFromInstallDirectory(); - } else { - this.multiLanguage = !isWindows; - this.availableDictionaries = this.availableDictionaries.map((dict) => dict.replace('-', '_')); - } - } - - /** + setEnabledDictionaries() { + const { dictionaries } = this; + if (dictionaries) { + // Dictionary disabled + if (dictionaries.length === 0) { + return; + } + if (this.setEnabled(dictionaries)) { + return; + } + } + + if (this.userLanguage) { + if (this.setEnabled(this.userLanguage)) { + return; + } + if (this.userLanguage.includes('_') && this.setEnabled(this.userLanguage.split('_')[0])) { + return; + } + } + + const navigatorLanguage = navigator.language.replace('-', '_'); + if (this.setEnabled(navigatorLanguage)) { + return; + } + + if (navigatorLanguage.includes('_') && this.setEnabled(navigatorLanguage.split('_')[0])) { + return; + } + + if (this.setEnabled('en_US')) { + return; + } + + if (!this.setEnabled('en')) { + console.info('Unable to set a language for the spell checker - Spell checker is disabled'); + } + + } + + loadAvailableDictionaries() { + this.availableDictionaries = checker.getAvailableDictionaries().sort(); + if (this.availableDictionaries.length === 0) { + this.multiLanguage = false; + // Dictionaries path is correct for build + this.dictionariesPath = path.join(remote.app.getAppPath(), '../dictionaries'); + this.getDictionariesFromInstallDirectory(); + } else { + this.multiLanguage = !isWindows; + this.availableDictionaries = this.availableDictionaries.map((dict) => dict.replace('-', '_')); + } + } + + /** * Installs all of the dictionaries specified in filePaths * Copies dicts into our dictionary path and adds them to availableDictionaries */ - installDictionariesFromPaths (dictionaryPaths) { - for (const dictionaryPath of dictionaryPaths) { - const dictionaryFileName = dictionaryPath.split(path.sep).pop(); - const dictionaryName = dictionaryFileName.slice(0, -4); - const newDictionaryPath = path.join(this.dictionariesPath, dictionaryFileName); - - this.copyDictionaryToInstallDirectory(dictionaryName, dictionaryPath, newDictionaryPath); - } - } - - copyDictionaryToInstallDirectory (dictionaryName, oldPath, newPath) { - fs.createReadStream(oldPath).pipe(fs.createWriteStream(newPath) - .on('error', (errorMessage) => { - dialog.showErrorBox(i18n.__('Error'), i18n.__('Error copying dictionary file') + `: ${dictionaryName}`); - console.error(errorMessage); - }) - .on('finish', () => { - if (!this.availableDictionaries.includes(dictionaryName)) { - this.availableDictionaries.push(dictionaryName); - } - })); - } - - getDictionariesFromInstallDirectory () { - if (this.dictionariesPath) { - const fileNames = fs.readdirSync(this.dictionariesPath); - for (const fileName of fileNames) { - const dictionaryExtension = fileName.slice(-3); - const dictionaryName = fileName.slice(0, -4); - if (!this.availableDictionaries.includes(dictionaryName) + installDictionariesFromPaths(dictionaryPaths) { + for (const dictionaryPath of dictionaryPaths) { + const dictionaryFileName = dictionaryPath.split(path.sep).pop(); + const dictionaryName = dictionaryFileName.slice(0, -4); + const newDictionaryPath = path.join(this.dictionariesPath, dictionaryFileName); + + this.copyDictionaryToInstallDirectory(dictionaryName, dictionaryPath, newDictionaryPath); + } + } + + copyDictionaryToInstallDirectory(dictionaryName, oldPath, newPath) { + fs.createReadStream(oldPath).pipe(fs.createWriteStream(newPath) + .on('error', (errorMessage) => { + dialog.showErrorBox(i18n.__('Error'), `${ i18n.__('Error copying dictionary file') }: ${ dictionaryName }`); + console.error(errorMessage); + }) + .on('finish', () => { + if (!this.availableDictionaries.includes(dictionaryName)) { + this.availableDictionaries.push(dictionaryName); + } + })); + } + + getDictionariesFromInstallDirectory() { + if (this.dictionariesPath) { + const fileNames = fs.readdirSync(this.dictionariesPath); + for (const fileName of fileNames) { + const dictionaryExtension = fileName.slice(-3); + const dictionaryName = fileName.slice(0, -4); + if (!this.availableDictionaries.includes(dictionaryName) && (dictionaryExtension === 'aff' || dictionaryExtension === 'dic')) { - this.availableDictionaries.push(dictionaryName); - } - } - } - } - - setEnabled (dictionaries) { - dictionaries = [].concat(dictionaries); - let result = false; - for (let i = 0; i < dictionaries.length; i++) { - if (this.availableDictionaries.includes(dictionaries[i])) { - result = true; - this.enabledDictionaries.push(dictionaries[i]); - // If using Hunspell or Windows then only allow 1 language for performance reasons - if (!this.multiLanguage) { - this.enabledDictionaries = [dictionaries[i]]; - checker.setDictionary(dictionaries[i], this.dictionariesPath); - return true; - } - } - } - return result; - } - - disable (dictionary) { - const pos = this.enabledDictionaries.indexOf(dictionary); - if (pos !== -1) { - this.enabledDictionaries.splice(pos, 1); - } - } - - getContractions () { - const contractions = [ - "ain't", "aren't", "can't", "could've", "couldn't", "couldn't've", "didn't", "doesn't", "don't", "hadn't", - "hadn't've", "hasn't", "haven't", "he'd", "he'd've", "he'll", "he's", "how'd", "how'll", "how's", "I'd", - "I'd've", "I'll", "I'm", "I've", "isn't", "it'd", "it'd've", "it'll", "it's", "let's", "ma'am", "mightn't", - "mightn't've", "might've", "mustn't", "must've", "needn't", "not've", "o'clock", "shan't", "she'd", "she'd've", - "she'll", "she's", "should've", "shouldn't", "shouldn't've", "that'll", "that's", "there'd", "there'd've", - "there're", "there's", "they'd", "they'd've", "they'll", "they're", "they've", "wasn't", "we'd", "we'd've", - "we'll", "we're", "we've", "weren't", "what'll", "what're", "what's", "what've", "when's", "where'd", - "where's", "where've", "who'd", "who'll", "who're", "who's", "who've", "why'll", "why're", "why's", "won't", - "would've", "wouldn't", "wouldn't've", "y'all", "y'all'd've", "you'd", "you'd've", "you'll", "you're", "you've" - ]; - - const contractionMap = contractions.reduce((acc, word) => { - acc[word.replace(/'.*/, '')] = true; - return acc; - }, {}); - - return contractionMap; - } - - enable () { - webFrame.setSpellCheckProvider('', false, { - spellCheck: (text) => this.isCorrect(text) - }); - - this.setupContextMenuListener(); - } - - getMenu () { - return [ - { - label: i18n.__('Undo'), - role: 'undo' - }, - { - label: i18n.__('Redo'), - role: 'redo' - }, - { - type: 'separator' - }, - { - label: i18n.__('Cut'), - role: 'cut', - accelerator: 'CommandOrControl+X', - }, - { - label: i18n.__('Copy'), - role: 'copy', - accelerator: 'CommandOrControl+C', - }, - { - label: i18n.__('Paste'), - role: 'paste', - accelerator: 'CommandOrControl+V', - }, - { - label: i18n.__('Select_All'), - role: 'selectall', - accelerator: 'CommandOrControl+A', - } - ]; - } - - saveEnabledDictionaries () { - localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries)); - } - - isCorrect (text) { - if (!this.enabledDictionaries.length || this.contractions[text.toLocaleLowerCase()]) { - return true; - } - - if (this.multiLanguage) { - for (let i = 0; i < this.enabledDictionaries.length; i++) { - checker.setDictionary(this.enabledDictionaries[i]); - if (!checker.isMisspelled(text)) { - return true; - } - } - } else { - return !checker.isMisspelled(text); - } - return false; - } - - getCorrections (text) { - if (!this.multiLanguage) { - return checker.getCorrectionsForMisspelling(text); - } - - const allCorrections = this.enabledDictionaries.map((dictionary) => { - checker.setDictionary(dictionary); - return checker.getCorrectionsForMisspelling(text); - }).filter((c) => c.length > 0); - - const length = Math.max(...allCorrections.map((a) => a.length)); - - // Get the best suggestions of each language first - const corrections = []; - for (let i = 0; i < length; i++) { - corrections.push(...allCorrections.map((c) => c[i]).filter((c) => c)); - } - - // Remove duplicates - return [...new Set(corrections)]; - } - - setupContextMenuListener () { - window.addEventListener('contextmenu', (event) => { - event.preventDefault(); - - const template = this.getMenu(); - - if (this.languagesMenu && this.browseForLanguageMenu) { - template.unshift({ type: 'separator' }); - if (this.dictionariesPath) { - template.unshift(this.browseForLanguageMenu); - } - template.unshift(this.languagesMenu); - } - - setTimeout(() => { - if (event.target.nodeName === 'A') { - const targetLink = event.target.href; - - template.unshift({ - label: i18n.__('Open_Link'), - click: () => { - shell.openExternal(targetLink); - } - }); - } - - if (['TEXTAREA', 'INPUT'].indexOf(event.target.nodeName) > -1) { - const text = window.getSelection().toString().trim(); - if (text !== '' && !this.isCorrect(text)) { - const options = this.getCorrections(text); - const maxItems = Math.min(options.length, 6); - - if (maxItems > 0) { - const suggestions = []; - const onClick = function (menuItem) { - webContents.replaceMisspelling(menuItem.label); - }; - - for (let i = 0; i < options.length; i++) { - const item = options[i]; - suggestions.push({ label: item, click: onClick }); - } - - template.unshift({ type: 'separator' }); - - if (suggestions.length > maxItems) { - const moreSuggestions = { - label: i18n.__('More_spelling_suggestions'), - submenu: suggestions.slice(maxItems) - }; - template.unshift(moreSuggestions); - } - - template.unshift.apply(template, suggestions.slice(0, maxItems)); - } else { - template.unshift({ label: i18n.__('No_suggestions'), enabled: false }); - } - } - } - - menu = remote.Menu.buildFromTemplate(template); - menu.popup(remote.getCurrentWindow(), undefined, undefined, 5); - }, 0); - }, false); - } + this.availableDictionaries.push(dictionaryName); + } + } + } + } + + setEnabled(dictionaries) { + dictionaries = [].concat(dictionaries); + let result = false; + for (let i = 0; i < dictionaries.length; i++) { + if (this.availableDictionaries.includes(dictionaries[i])) { + result = true; + this.enabledDictionaries.push(dictionaries[i]); + // If using Hunspell or Windows then only allow 1 language for performance reasons + if (!this.multiLanguage) { + this.enabledDictionaries = [dictionaries[i]]; + checker.setDictionary(dictionaries[i], this.dictionariesPath); + return true; + } + } + } + return result; + } + + disable(dictionary) { + const pos = this.enabledDictionaries.indexOf(dictionary); + if (pos !== -1) { + this.enabledDictionaries.splice(pos, 1); + } + } + + getContractions() { + const contractions = [ + "ain't", "aren't", "can't", "could've", "couldn't", "couldn't've", "didn't", "doesn't", "don't", "hadn't", + "hadn't've", "hasn't", "haven't", "he'd", "he'd've", "he'll", "he's", "how'd", "how'll", "how's", "I'd", + "I'd've", "I'll", "I'm", "I've", "isn't", "it'd", "it'd've", "it'll", "it's", "let's", "ma'am", "mightn't", + "mightn't've", "might've", "mustn't", "must've", "needn't", "not've", "o'clock", "shan't", "she'd", "she'd've", + "she'll", "she's", "should've", "shouldn't", "shouldn't've", "that'll", "that's", "there'd", "there'd've", + "there're", "there's", "they'd", "they'd've", "they'll", "they're", "they've", "wasn't", "we'd", "we'd've", + "we'll", "we're", "we've", "weren't", "what'll", "what're", "what's", "what've", "when's", "where'd", + "where's", "where've", "who'd", "who'll", "who're", "who's", "who've", "why'll", "why're", "why's", "won't", + "would've", "wouldn't", "wouldn't've", "y'all", "y'all'd've", "you'd", "you'd've", "you'll", "you're", "you've", + ]; + + const contractionMap = contractions.reduce((acc, word) => { + acc[word.replace(/'.*/, '')] = true; + return acc; + }, {}); + + return contractionMap; + } + + enable() { + webFrame.setSpellCheckProvider('', false, { + spellCheck: (text) => this.isCorrect(text), + }); + + this.setupContextMenuListener(); + } + + getMenu() { + return [ + { + label: i18n.__('Undo'), + role: 'undo', + }, + { + label: i18n.__('Redo'), + role: 'redo', + }, + { + type: 'separator', + }, + { + label: i18n.__('Cut'), + role: 'cut', + accelerator: 'CommandOrControl+X', + }, + { + label: i18n.__('Copy'), + role: 'copy', + accelerator: 'CommandOrControl+C', + }, + { + label: i18n.__('Paste'), + role: 'paste', + accelerator: 'CommandOrControl+V', + }, + { + label: i18n.__('Select_All'), + role: 'selectall', + accelerator: 'CommandOrControl+A', + }, + ]; + } + + saveEnabledDictionaries() { + localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries)); + } + + isCorrect(text) { + if (!this.enabledDictionaries.length || this.contractions[text.toLocaleLowerCase()]) { + return true; + } + + if (this.multiLanguage) { + for (let i = 0; i < this.enabledDictionaries.length; i++) { + checker.setDictionary(this.enabledDictionaries[i]); + if (!checker.isMisspelled(text)) { + return true; + } + } + } else { + return !checker.isMisspelled(text); + } + return false; + } + + getCorrections(text) { + if (!this.multiLanguage) { + return checker.getCorrectionsForMisspelling(text); + } + + const allCorrections = this.enabledDictionaries.map((dictionary) => { + checker.setDictionary(dictionary); + return checker.getCorrectionsForMisspelling(text); + }).filter((c) => c.length > 0); + + const length = Math.max(...allCorrections.map((a) => a.length)); + + // Get the best suggestions of each language first + const corrections = []; + for (let i = 0; i < length; i++) { + corrections.push(...allCorrections.map((c) => c[i]).filter((c) => c)); + } + + // Remove duplicates + return [...new Set(corrections)]; + } + + setupContextMenuListener() { + window.addEventListener('contextmenu', (event) => { + event.preventDefault(); + + const template = this.getMenu(); + + if (this.languagesMenu && this.browseForLanguageMenu) { + template.unshift({ type: 'separator' }); + if (this.dictionariesPath) { + template.unshift(this.browseForLanguageMenu); + } + template.unshift(this.languagesMenu); + } + + setTimeout(() => { + if (event.target.nodeName === 'A') { + const targetLink = event.target.href; + + template.unshift({ + label: i18n.__('Open_Link'), + click: () => { + shell.openExternal(targetLink); + }, + }); + } + + if (['TEXTAREA', 'INPUT'].indexOf(event.target.nodeName) > -1) { + const text = window.getSelection().toString().trim(); + if (text !== '' && !this.isCorrect(text)) { + const options = this.getCorrections(text); + const maxItems = Math.min(options.length, 6); + + if (maxItems > 0) { + const suggestions = []; + const onClick = function(menuItem) { + webContents.replaceMisspelling(menuItem.label); + }; + + for (let i = 0; i < options.length; i++) { + const item = options[i]; + suggestions.push({ label: item, click: onClick }); + } + + template.unshift({ type: 'separator' }); + + if (suggestions.length > maxItems) { + const moreSuggestions = { + label: i18n.__('More_spelling_suggestions'), + submenu: suggestions.slice(maxItems), + }; + template.unshift(moreSuggestions); + } + + template.unshift.apply(template, suggestions.slice(0, maxItems)); + } else { + template.unshift({ label: i18n.__('No_suggestions'), enabled: false }); + } + } + } + + menu = remote.Menu.buildFromTemplate(template); + menu.popup(remote.getCurrentWindow(), undefined, undefined, 5); + }, 0); + }, false); + } } module.exports = SpellCheck; diff --git a/src/public/preload.js b/src/public/preload.js index 546fffd48b..98e4a065cf 100644 --- a/src/public/preload.js +++ b/src/public/preload.js @@ -12,119 +12,119 @@ window.i18n = i18n; const defaultWindowOpen = window.open; -function customWindowOpen (url, frameName, features) { - const jitsiDomain = RocketChat.settings.get('Jitsi_Domain'); - if (jitsiDomain && url.indexOf(jitsiDomain) !== -1) { - features = ((features) ? (features + ",") : "") + - "nodeIntegration=true,preload=" + path.join(__dirname, 'jitsi-preload.js'); - return defaultWindowOpen(url, frameName, features); - } else { - return defaultWindowOpen(url, frameName, features); - } +function customWindowOpen(url, frameName, features) { + const jitsiDomain = RocketChat.settings.get('Jitsi_Domain'); + if (jitsiDomain && url.indexOf(jitsiDomain) !== -1) { + features = `${ (features) ? (`${ features },`) : '' + }nodeIntegration=true,preload=${ path.join(__dirname, 'jitsi-preload.js') }`; + return defaultWindowOpen(url, frameName, features); + } else { + return defaultWindowOpen(url, frameName, features); + } } window.open = customWindowOpen; const events = ['unread-changed', 'get-sourceId', 'user-status-manually-set']; -events.forEach(function (e) { - window.addEventListener(e, function (event) { - ipcRenderer.sendToHost(e, event.detail); - }); +events.forEach(function(e) { + window.addEventListener(e, function(event) { + ipcRenderer.sendToHost(e, event.detail); + }); }); const userPresenceControl = () => { - const INTERVAL = 10000; // 10s - setInterval(() => { - try { - const idleTime = ipcRenderer.sendSync('getSystemIdleTime'); - if (idleTime < INTERVAL) { - UserPresence.setOnline(); - } - } catch (e) { - console.error(`Error getting system idle time: ${e}`); - } - }, INTERVAL); + const INTERVAL = 10000; // 10s + setInterval(() => { + try { + const idleTime = ipcRenderer.sendSync('getSystemIdleTime'); + if (idleTime < INTERVAL) { + UserPresence.setOnline(); + } + } catch (e) { + console.error(`Error getting system idle time: ${ e }`); + } + }, INTERVAL); }; const changeSidebarColor = () => { - const sidebar = document.querySelector('.sidebar'); - const fullpage = document.querySelector('.full-page'); - if (sidebar) { - const sidebarItem = sidebar.querySelector('.sidebar-item'); - let itemColor; - if (sidebarItem) { - itemColor = window.getComputedStyle(sidebarItem); - } - const { color, background } = window.getComputedStyle(sidebar); - ipcRenderer.sendToHost('sidebar-background', {color: itemColor || color, background: background}); - } else if (fullpage) { - const { color, background } = window.getComputedStyle(fullpage); - ipcRenderer.sendToHost('sidebar-background', {color: color, background: background}); - } else { - window.requestAnimationFrame(changeSidebarColor); - - } + const sidebar = document.querySelector('.sidebar'); + const fullpage = document.querySelector('.full-page'); + if (sidebar) { + const sidebarItem = sidebar.querySelector('.sidebar-item'); + let itemColor; + if (sidebarItem) { + itemColor = window.getComputedStyle(sidebarItem); + } + const { color, background } = window.getComputedStyle(sidebar); + ipcRenderer.sendToHost('sidebar-background', { color: itemColor || color, background }); + } else if (fullpage) { + const { color, background } = window.getComputedStyle(fullpage); + ipcRenderer.sendToHost('sidebar-background', { color, background }); + } else { + window.requestAnimationFrame(changeSidebarColor); + + } }; ipcRenderer.on('request-sidebar-color', () => { - changeSidebarColor(); + changeSidebarColor(); }); -window.addEventListener('load', function () { - Meteor.startup(function () { - Tracker.autorun(function () { - const siteName = RocketChat.settings.get('Site_Name'); - if (siteName) { - ipcRenderer.sendToHost('title-changed', siteName); - } - }); - }); - userPresenceControl(); +window.addEventListener('load', function() { + Meteor.startup(function() { + Tracker.autorun(function() { + const siteName = RocketChat.settings.get('Site_Name'); + if (siteName) { + ipcRenderer.sendToHost('title-changed', siteName); + } + }); + }); + userPresenceControl(); }); -window.onload = function () { - const $ = require('./vendor/jquery-3.1.1'); - function checkExternalUrl (e) { - const href = $(this).attr('href'); - // Check href matching current domain - if (RegExp(`^${location.protocol}\/\/${location.host}`).test(href)) { - return; - } - - // Check if is file upload link - if (/^\/file-upload\//.test(href) && !this.hasAttribute('download')) { - this.setAttribute('download', ''); - this.click(); - } - - // Check href matching relative URL - if (!/^([a-z]+:)?\/\//.test(href)) { - return; - } - - if (/^file:\/\/.+/.test(href)) { - const item = href.slice(6); - shell.showItemInFolder(item); - e.preventDefault(); - } else { - shell.openExternal(href); - e.preventDefault(); - } - } - - $(document).on('click', 'a', checkExternalUrl); - - $('#reload').click(function () { - ipcRenderer.sendToHost('reload-server'); - $(this).hide(); - $(this).parent().find('.loading-animation').show(); - }); +window.onload = function() { + const $ = require('./vendor/jquery-3.1.1'); + function checkExternalUrl(e) { + const href = $(this).attr('href'); + // Check href matching current domain + if (RegExp(`^${ location.protocol }\/\/${ location.host }`).test(href)) { + return; + } + + // Check if is file upload link + if (/^\/file-upload\//.test(href) && !this.hasAttribute('download')) { + this.setAttribute('download', ''); + this.click(); + } + + // Check href matching relative URL + if (!/^([a-z]+:)?\/\//.test(href)) { + return; + } + + if (/^file:\/\/.+/.test(href)) { + const item = href.slice(6); + shell.showItemInFolder(item); + e.preventDefault(); + } else { + shell.openExternal(href); + e.preventDefault(); + } + } + + $(document).on('click', 'a', checkExternalUrl); + + $('#reload').click(function() { + ipcRenderer.sendToHost('reload-server'); + $(this).hide(); + $(this).parent().find('.loading-animation').show(); + }); }; // Prevent redirect to url when dragging in -document.addEventListener('dragover', e => e.preventDefault()); -document.addEventListener('drop', e => e.preventDefault()); +document.addEventListener('dragover', (e) => e.preventDefault()); +document.addEventListener('drop', (e) => e.preventDefault()); const spellChecker = new SpellCheck(); spellChecker.enable(); diff --git a/src/scripts/bugsnag_apikey.js b/src/scripts/bugsnag_apikey.js index 296b8084a2..bf52f6a144 100644 --- a/src/scripts/bugsnag_apikey.js +++ b/src/scripts/bugsnag_apikey.js @@ -1 +1 @@ -Bugsnag.apiKey = "2fa5f5a7b728b84ae273015995b42a6e"; +Bugsnag.apiKey = '2fa5f5a7b728b84ae273015995b42a6e'; diff --git a/src/scripts/menus.js b/src/scripts/menus.js index d0092c1b81..e6a7762386 100644 --- a/src/scripts/menus.js +++ b/src/scripts/menus.js @@ -10,85 +10,85 @@ import historyMenu from './menus/history'; import windowMenu from './menus/window'; import helpMenu from './menus/help'; -const Menu = remote.Menu; +const { Menu } = remote; const APP_NAME = remote.app.getName(); const isMac = process.platform === 'darwin'; document.title = APP_NAME; const menuTemplate = [ - { - label: '&' + APP_NAME, - submenu: appMenu - }, - { - label: '&' + i18n.__('Edit'), - submenu: editMenu - }, - { - label: '&' + i18n.__('View'), - submenu: viewMenu - }, - { - label: '&' + i18n.__('History'), - submenu: historyMenu - }, - { - label: '&' + i18n.__('Window'), - id: 'window', - role: 'window', - submenu: windowMenu - }, - { - label: '&' + i18n.__('Help'), - role: 'help', - submenu: helpMenu - } + { + label: `&${ APP_NAME }`, + submenu: appMenu, + }, + { + label: `&${ i18n.__('Edit') }`, + submenu: editMenu, + }, + { + label: `&${ i18n.__('View') }`, + submenu: viewMenu, + }, + { + label: `&${ i18n.__('History') }`, + submenu: historyMenu, + }, + { + label: `&${ i18n.__('Window') }`, + id: 'window', + role: 'window', + submenu: windowMenu, + }, + { + label: `&${ i18n.__('Help') }`, + role: 'help', + submenu: helpMenu, + }, ]; -function createMenu () { - const menu = Menu.buildFromTemplate(menuTemplate); - Menu.setApplicationMenu(menu); +function createMenu() { + const menu = Menu.buildFromTemplate(menuTemplate); + Menu.setApplicationMenu(menu); } -function addServer (host, position) { - const index = windowMenu.findIndex((i) => i.id === 'server-list-separator'); - windowMenu[index].visible = true; +function addServer(host, position) { + const index = windowMenu.findIndex((i) => i.id === 'server-list-separator'); + windowMenu[index].visible = true; - const menuItem = { - label: '&' + host.title, - accelerator: `CmdOrCtrl+ ${position}`, - position: 'before=server-list-separator', - id: host.url, - click: () => { - const mainWindow = remote.getCurrentWindow(); - mainWindow.show(); - servers.setActive(host.url); - } - }; + const menuItem = { + label: `&${ host.title }`, + accelerator: `CmdOrCtrl+ ${ position }`, + position: 'before=server-list-separator', + id: host.url, + click: () => { + const mainWindow = remote.getCurrentWindow(); + mainWindow.show(); + servers.setActive(host.url); + }, + }; - windowMenu.push(menuItem); + windowMenu.push(menuItem); - createMenu(); + createMenu(); } -function removeServer (server) { - const index = windowMenu.findIndex((i) => i.id === server); - windowMenu.splice(index, 1); - createMenu(); +function removeServer(server) { + const index = windowMenu.findIndex((i) => i.id === server); + windowMenu.splice(index, 1); + createMenu(); } -function autoHideMenu () { - remote.getCurrentWindow().setAutoHideMenuBar(true); +function autoHideMenu() { + remote.getCurrentWindow().setAutoHideMenuBar(true); } if (!isMac && localStorage.getItem('autohideMenu') === 'true') { - autoHideMenu(); + autoHideMenu(); } createMenu(); export { - addServer, - removeServer + addServer, + removeServer, }; diff --git a/src/scripts/menus/app.js b/src/scripts/menus/app.js index 263cc6641d..f48163914f 100644 --- a/src/scripts/menus/app.js +++ b/src/scripts/menus/app.js @@ -5,63 +5,63 @@ const APP_NAME = remote.app.getName(); const isMac = process.platform === 'darwin'; const appTemplate = [ - { - label: i18n.__('About', APP_NAME), - click: function () { - const win = new remote.BrowserWindow({ - width: 310, - height: 240, - resizable: false, - show: false, - center: true, - maximizable: false, - minimizable: false, - title: 'About Rocket.Chat' - }); - win.loadURL('file://' + __dirname + '/about.html'); - win.setMenuBarVisibility(false); - win.show(); - } - }, - { - type: 'separator', - id: 'about-sep' - }, - { - label: i18n.__('Quit_App', APP_NAME), - accelerator: 'CommandOrControl+Q', - click: function () { - remote.app.quit(); - } - } + { + label: i18n.__('About', APP_NAME), + click() { + const win = new remote.BrowserWindow({ + width: 310, + height: 240, + resizable: false, + show: false, + center: true, + maximizable: false, + minimizable: false, + title: 'About Rocket.Chat', + }); + win.loadURL(`file://${ __dirname }/about.html`); + win.setMenuBarVisibility(false); + win.show(); + }, + }, + { + type: 'separator', + id: 'about-sep', + }, + { + label: i18n.__('Quit_App', APP_NAME), + accelerator: 'CommandOrControl+Q', + click() { + remote.app.quit(); + }, + }, ]; if (isMac) { - const macAppExtraTemplate = [ - { - role: 'services', - submenu: [], - position: 'after=about-sep' - }, - { - type: 'separator' - }, - { - accelerator: 'Command+H', - role: 'hide' - }, - { - accelerator: 'Command+Alt+H', - role: 'hideothers' - }, - { - role: 'unhide' - }, - { - type: 'separator' - } - ]; - appTemplate.push(...macAppExtraTemplate); + const macAppExtraTemplate = [ + { + role: 'services', + submenu: [], + position: 'after=about-sep', + }, + { + type: 'separator', + }, + { + accelerator: 'Command+H', + role: 'hide', + }, + { + accelerator: 'Command+Alt+H', + role: 'hideothers', + }, + { + role: 'unhide', + }, + { + type: 'separator', + }, + ]; + appTemplate.push(...macAppExtraTemplate); } export default appTemplate; diff --git a/src/scripts/menus/edit.js b/src/scripts/menus/edit.js index b9b7588d73..6f650e9686 100644 --- a/src/scripts/menus/edit.js +++ b/src/scripts/menus/edit.js @@ -1,39 +1,39 @@ import i18n from '../../i18n/index.js'; const editTemplate = [ - { - label: i18n.__('Undo'), - accelerator: 'CommandOrControl+Z', - role: 'undo' - }, - { - label: i18n.__('Redo'), - accelerator: 'CommandOrControl+Shift+Z', - role: 'redo' - }, - { - type: 'separator' - }, - { - label: i18n.__('Cut'), - accelerator: 'CommandOrControl+X', - role: 'cut' - }, - { - label: i18n.__('Copy'), - accelerator: 'CommandOrControl+C', - role: 'copy' - }, - { - label: i18n.__('Paste'), - accelerator: 'CommandOrControl+V', - role: 'paste' - }, - { - label: i18n.__('Select_All'), - accelerator: 'CommandOrControl+A', - role: 'selectall' - } + { + label: i18n.__('Undo'), + accelerator: 'CommandOrControl+Z', + role: 'undo', + }, + { + label: i18n.__('Redo'), + accelerator: 'CommandOrControl+Shift+Z', + role: 'redo', + }, + { + type: 'separator', + }, + { + label: i18n.__('Cut'), + accelerator: 'CommandOrControl+X', + role: 'cut', + }, + { + label: i18n.__('Copy'), + accelerator: 'CommandOrControl+C', + role: 'copy', + }, + { + label: i18n.__('Paste'), + accelerator: 'CommandOrControl+V', + role: 'paste', + }, + { + label: i18n.__('Select_All'), + accelerator: 'CommandOrControl+A', + role: 'selectall', + }, ]; export default editTemplate; diff --git a/src/scripts/menus/help.js b/src/scripts/menus/help.js index 676c338162..9b860f6c7f 100644 --- a/src/scripts/menus/help.js +++ b/src/scripts/menus/help.js @@ -4,28 +4,28 @@ import i18n from '../../i18n/index.js'; const APP_NAME = remote.app.getName(); const helpTemplate = [ - { - label: i18n.__('Help_Name', APP_NAME), - click: () => remote.shell.openExternal('https://rocket.chat/docs') - }, - { - type: 'separator' - }, - { - label: i18n.__('Report_Issue'), - click: () => remote.shell.openExternal('https://github.com/RocketChat/Rocket.Chat/issues') - }, - { - label: i18n.__('Reset_App_Data'), - click: () => servers.resetAppData() - }, - { - type: 'separator' - }, - { - label: i18n.__('Learn_More'), - click: () => remote.shell.openExternal('https://rocket.chat') - } + { + label: i18n.__('Help_Name', APP_NAME), + click: () => remote.shell.openExternal('https://rocket.chat/docs'), + }, + { + type: 'separator', + }, + { + label: i18n.__('Report_Issue'), + click: () => remote.shell.openExternal('https://github.com/RocketChat/Rocket.Chat/issues'), + }, + { + label: i18n.__('Reset_App_Data'), + click: () => servers.resetAppData(), + }, + { + type: 'separator', + }, + { + label: i18n.__('Learn_More'), + click: () => remote.shell.openExternal('https://rocket.chat'), + }, ]; export default helpTemplate; diff --git a/src/scripts/menus/history.js b/src/scripts/menus/history.js index ce47a238df..7f3ce1e6f9 100644 --- a/src/scripts/menus/history.js +++ b/src/scripts/menus/history.js @@ -3,29 +3,29 @@ import i18n from '../../i18n/index.js'; const isMac = process.platform === 'darwin'; const macWindowTemplate = [ - { - label: i18n.__('Back'), - accelerator: 'Command+left', - click: () => { webview.goBack(); } - }, - { - label: i18n.__('Forward'), - accelerator: 'Command+right', - click: () => { webview.goForward(); } - } + { + label: i18n.__('Back'), + accelerator: 'Command+left', + click: () => { webview.goBack(); }, + }, + { + label: i18n.__('Forward'), + accelerator: 'Command+right', + click: () => { webview.goForward(); }, + }, ]; const windowTemplate = [ - { - label: i18n.__('Back'), - accelerator: 'Alt+Left', - click: () => { webview.goBack(); } - }, - { - label: i18n.__('Forward'), - accelerator: 'Alt+Right', - click: () => { webview.goForward(); } - }, + { + label: i18n.__('Back'), + accelerator: 'Alt+Left', + click: () => { webview.goBack(); }, + }, + { + label: i18n.__('Forward'), + accelerator: 'Alt+Right', + click: () => { webview.goForward(); }, + }, ]; export default isMac ? macWindowTemplate : windowTemplate; diff --git a/src/scripts/menus/view.js b/src/scripts/menus/view.js index c149d0ffb9..c924292b62 100644 --- a/src/scripts/menus/view.js +++ b/src/scripts/menus/view.js @@ -5,119 +5,119 @@ import sidebar from '../sidebar'; import tray from '../tray'; const isMac = process.platform === 'darwin'; -const certificate = remote.require('./background').certificate; +const { certificate } = remote.require('./background'); const viewTemplate = [ - { - label: i18n.__('Original_Zoom'), - accelerator: 'CommandOrControl+0', - role: 'resetzoom' - }, - { - label: i18n.__('Zoom_In'), - accelerator: 'CommandOrControl+Plus', - role: 'zoomin' - }, - { - label: i18n.__('Zoom_Out'), - accelerator: 'CommandOrControl+-', - role: 'zoomout' - }, - { - type: 'separator' - }, - { - label: i18n.__('Current_Server_Reload'), - accelerator: 'CommandOrControl+R', - click: function () { - const activeWebview = webview.getActive(); - if (activeWebview) { - activeWebview.reload(); - } - } - }, - { - label: i18n.__('Current_Server_Toggle_DevTools'), - accelerator: isMac ? 'Command+Alt+I' : 'Ctrl+Shift+I', - click: function () { - const activeWebview = webview.getActive(); - if (activeWebview) { - activeWebview.openDevTools(); - } - } - }, - { - type: 'separator' - }, - { - label: i18n.__('Application_Reload'), - accelerator: 'CommandOrControl+Shift+R', - click: function () { - const mainWindow = remote.getCurrentWindow(); - if (mainWindow.destroyTray) { - mainWindow.destroyTray(); - } - mainWindow.reload(); - } - }, - { - label: i18n.__('Application_Toggle_DevTools'), - click: function () { - remote.getCurrentWindow().toggleDevTools(); - } - }, - { - type: 'separator', - id: 'toggle' - }, - { - label: i18n.__('Toggle_Server_List'), - click: function () { - sidebar.toggle(); - } - }, - { - type: 'separator' - }, - { - label: i18n.__('Clear'), - submenu: [ - { - label: i18n.__('Clear_Trusted_Certificates'), - click: function () { - certificate.clear(); - } - } - ] - } + { + label: i18n.__('Original_Zoom'), + accelerator: 'CommandOrControl+0', + role: 'resetzoom', + }, + { + label: i18n.__('Zoom_In'), + accelerator: 'CommandOrControl+Plus', + role: 'zoomin', + }, + { + label: i18n.__('Zoom_Out'), + accelerator: 'CommandOrControl+-', + role: 'zoomout', + }, + { + type: 'separator', + }, + { + label: i18n.__('Current_Server_Reload'), + accelerator: 'CommandOrControl+R', + click() { + const activeWebview = webview.getActive(); + if (activeWebview) { + activeWebview.reload(); + } + }, + }, + { + label: i18n.__('Current_Server_Toggle_DevTools'), + accelerator: isMac ? 'Command+Alt+I' : 'Ctrl+Shift+I', + click() { + const activeWebview = webview.getActive(); + if (activeWebview) { + activeWebview.openDevTools(); + } + }, + }, + { + type: 'separator', + }, + { + label: i18n.__('Application_Reload'), + accelerator: 'CommandOrControl+Shift+R', + click() { + const mainWindow = remote.getCurrentWindow(); + if (mainWindow.destroyTray) { + mainWindow.destroyTray(); + } + mainWindow.reload(); + }, + }, + { + label: i18n.__('Application_Toggle_DevTools'), + click() { + remote.getCurrentWindow().toggleDevTools(); + }, + }, + { + type: 'separator', + id: 'toggle', + }, + { + label: i18n.__('Toggle_Server_List'), + click() { + sidebar.toggle(); + }, + }, + { + type: 'separator', + }, + { + label: i18n.__('Clear'), + submenu: [ + { + label: i18n.__('Clear_Trusted_Certificates'), + click() { + certificate.clear(); + }, + }, + ], + }, ]; if (isMac) { - viewTemplate.push({ - label: i18n.__('Toggle_Tray_Icon'), - click: function () { - tray.toggle(); - }, - position: 'after=toggle' - }, { - label: i18n.__('Toggle_Full_Screen'), - accelerator: 'Control+Command+F', - click: function () { - const mainWindow = remote.getCurrentWindow(); - mainWindow.setFullScreen(!mainWindow.isFullScreen()); - }, - position: 'after=toggle' - }); + viewTemplate.push({ + label: i18n.__('Toggle_Tray_Icon'), + click() { + tray.toggle(); + }, + position: 'after=toggle', + }, { + label: i18n.__('Toggle_Full_Screen'), + accelerator: 'Control+Command+F', + click() { + const mainWindow = remote.getCurrentWindow(); + mainWindow.setFullScreen(!mainWindow.isFullScreen()); + }, + position: 'after=toggle', + }); } else { - viewTemplate.push({ - label: i18n.__('Toggle_Menu_Bar'), - click: function () { - const current = localStorage.getItem('autohideMenu') === 'true'; - remote.getCurrentWindow().setAutoHideMenuBar(!current); - localStorage.setItem('autohideMenu', JSON.stringify(!current)); - }, - position: 'after=toggle' - }); + viewTemplate.push({ + label: i18n.__('Toggle_Menu_Bar'), + click() { + const current = localStorage.getItem('autohideMenu') === 'true'; + remote.getCurrentWindow().setAutoHideMenuBar(!current); + localStorage.setItem('autohideMenu', JSON.stringify(!current)); + }, + position: 'after=toggle', + }); } export default viewTemplate; diff --git a/src/scripts/menus/window.js b/src/scripts/menus/window.js index 9c9f239474..2f5b42ae52 100644 --- a/src/scripts/menus/window.js +++ b/src/scripts/menus/window.js @@ -5,70 +5,70 @@ import servers from '../servers'; const isMac = process.platform === 'darwin'; const macWindowTemplate = [ - { - label: i18n.__('Minimize'), - accelerator: 'Command+M', - role: 'minimize' - }, - { - label: i18n.__('Close'), - accelerator: 'Command+W', - role: 'close' - }, - { - type: 'separator' - }, - { - type: 'separator', - id: 'server-list-separator', - visible: false - }, - { - label: i18n.__('Add_new_server'), - accelerator: 'Command+N', - click: function () { - const mainWindow = remote.getCurrentWindow(); - mainWindow.show(); - servers.clearActive(); - webview.showLanding(); - } - }, - { - type: 'separator' - }, - { - label: i18n.__('Bring_All_to_Front'), - click: function () { - const mainWindow = remote.getCurrentWindow(); - mainWindow.show(); - } - } + { + label: i18n.__('Minimize'), + accelerator: 'Command+M', + role: 'minimize', + }, + { + label: i18n.__('Close'), + accelerator: 'Command+W', + role: 'close', + }, + { + type: 'separator', + }, + { + type: 'separator', + id: 'server-list-separator', + visible: false, + }, + { + label: i18n.__('Add_new_server'), + accelerator: 'Command+N', + click() { + const mainWindow = remote.getCurrentWindow(); + mainWindow.show(); + servers.clearActive(); + webview.showLanding(); + }, + }, + { + type: 'separator', + }, + { + label: i18n.__('Bring_All_to_Front'), + click() { + const mainWindow = remote.getCurrentWindow(); + mainWindow.show(); + }, + }, ]; const windowTemplate = [ - { - type: 'separator', - id: 'server-list-separator', - visible: false - }, - { - label: i18n.__('Add_new_server'), - accelerator: 'Ctrl+N', - click: function () { - servers.clearActive(); - webview.showLanding(); - } - }, - { - type: 'separator' - }, - { - label: i18n.__('Close'), - accelerator: 'Ctrl+W', - click: function () { - remote.getCurrentWindow().close(); - } - } + { + type: 'separator', + id: 'server-list-separator', + visible: false, + }, + { + label: i18n.__('Add_new_server'), + accelerator: 'Ctrl+N', + click() { + servers.clearActive(); + webview.showLanding(); + }, + }, + { + type: 'separator', + }, + { + label: i18n.__('Close'), + accelerator: 'Ctrl+W', + click() { + remote.getCurrentWindow().close(); + }, + }, ]; export default isMac ? macWindowTemplate : windowTemplate; diff --git a/src/scripts/servers.js b/src/scripts/servers.js index fa8beb3bf0..ca1c2b3803 100644 --- a/src/scripts/servers.js +++ b/src/scripts/servers.js @@ -4,310 +4,310 @@ import jetpack from 'fs-jetpack'; import { EventEmitter } from 'events'; import { remote, ipcRenderer } from 'electron'; import i18n from '../i18n/index.js'; -const remoteServers = remote.require('./background').remoteServers; +const { remoteServers } = remote.require('./background'); class Servers extends EventEmitter { - constructor () { - super(); - this.load(); - const processProtocol = this.getProtocolUrlFromProcess(remote.process.argv); - if (processProtocol) { - this.showHostConfirmation(processProtocol); - } - ipcRenderer.on('add-host', (e, host) => { - if (this.hostExists(host)) { - this.setActive(host); - } else { - this.showHostConfirmation(host); - } - }); - } - - get hosts () { - return this._hosts; - } - - set hosts (hosts) { - this._hosts = hosts; - this.save(); - return true; - } - - get hostsKey () { - return 'rocket.chat.hosts'; - } - - get activeKey () { - return 'rocket.chat.currentHost'; - } - - load () { - let hosts = localStorage.getItem(this.hostsKey); - - try { - hosts = JSON.parse(hosts); - } catch (e) { - if (typeof hosts === 'string' && hosts.match(/^https?:\/\//)) { - hosts = {}; - hosts[hosts] = { - title: hosts, - url: hosts - }; - } - - localStorage.setItem(this.hostsKey, JSON.stringify(hosts)); - } - - if (hosts === null) { - hosts = {}; - } - - if (Array.isArray(hosts)) { - const oldHosts = hosts; - hosts = {}; - oldHosts.forEach(function (item) { - item = item.replace(/\/$/, ''); - hosts[item] = { - title: item, - url: item - }; - }); - localStorage.setItem(this.hostsKey, JSON.stringify(hosts)); - } - - // Load server info from server config file - if (Object.keys(hosts).length === 0) { - const path = jetpack.find(remote.app.getPath('userData'), { matching: 'servers.json'})[0] || - jetpack.find(jetpack.path(remote.app.getAppPath(), '..'), { matching: 'servers.json'})[0]; - - if (path) { - const pathToServerJson = jetpack.path(path); - - try { - const result = jetpack.read(pathToServerJson, 'json'); - if (result) { - hosts = {}; - Object.keys(result).forEach((title) => { - const url = result[title]; - hosts[url] = { title, url }; - }); - localStorage.setItem(this.hostsKey, JSON.stringify(hosts)); - // Assume user doesn't want sidebar if they only have one server - if (Object.keys(hosts).length === 1) { - localStorage.setItem('sidebar-closed', 'true'); - } - } - - } catch (e) { - console.error('Server file invalid'); - } - } - } - - this._hosts = hosts; - remoteServers.loadServers(this._hosts); - this.emit('loaded'); - } - - save () { - localStorage.setItem(this.hostsKey, JSON.stringify(this._hosts)); - this.emit('saved'); - } - - get (hostUrl) { - return this.hosts[hostUrl]; - } - - forEach (cb) { - for (const host in this.hosts) { - if (this.hosts.hasOwnProperty(host)) { - cb(this.hosts[host]); - } - } - } - - validateHost (hostUrl, timeout) { - timeout = timeout || 5000; - return new Promise(function (resolve, reject) { - let resolved = false; - $.getJSON(`${hostUrl}/api/info`).then(function () { - if (resolved) { - return; - } - resolved = true; - resolve(); - }, function (request) { - if (request.status === 401) { - const authHeader = request.getResponseHeader('www-authenticate'); - if (authHeader && authHeader.toLowerCase().indexOf('basic ') === 0) { - resolved = true; - reject('basic-auth'); - } - } - if (resolved) { - return; - } - resolved = true; - reject('invalid'); - }); - if (timeout) { - setTimeout(function () { - if (resolved) { - return; - } - resolved = true; - reject('timeout'); - }, timeout); - } - }); - } - - hostExists (hostUrl) { - const hosts = this.hosts; - - return !!hosts[hostUrl]; - } - - addHost (hostUrl) { - const hosts = this.hosts; - - const match = hostUrl.match(/^(https?:\/\/)([^:]+):([^@]+)@(.+)$/); - let username; - let password; - let authUrl; - if (match) { - authUrl = hostUrl; - hostUrl = match[1] + match[4]; - username = match[2]; - password = match[3]; - } - - if (this.hostExists(hostUrl) === true) { - this.setActive(hostUrl); - return false; - } - - hosts[hostUrl] = { - title: hostUrl, - url: hostUrl, - authUrl: authUrl, - username: username, - password: password - }; - this.hosts = hosts; - - remoteServers.loadServers(this.hosts); - - this.emit('host-added', hostUrl); - - return hostUrl; - } - - removeHost (hostUrl) { - const hosts = this.hosts; - if (hosts[hostUrl]) { - delete hosts[hostUrl]; - this.hosts = hosts; - - remoteServers.loadServers(this.hosts); - - if (this.active === hostUrl) { - this.clearActive(); - } - this.emit('host-removed', hostUrl); - } - } - - get active () { - return localStorage.getItem(this.activeKey); - } - - setActive (hostUrl) { - let url; - if (this.hostExists(hostUrl)) { - url = hostUrl; - } else if (Object.keys(this._hosts).length > 0) { - url = Object.keys(this._hosts)[0]; - } - - if (url) { - localStorage.setItem(this.activeKey, hostUrl); - this.emit('active-setted', url); - return true; - } - this.emit('loaded'); - return false; - } - - restoreActive () { - this.setActive(this.active); - } - - clearActive () { - localStorage.removeItem(this.activeKey); - this.emit('active-cleared'); - return true; - } - - setHostTitle (hostUrl, title) { - if (title === 'Rocket.Chat' && /https?:\/\/open\.rocket\.chat/.test(hostUrl) === false) { - title += ' - ' + hostUrl; - } - const hosts = this.hosts; - hosts[hostUrl].title = title; - this.hosts = hosts; - this.emit('title-setted', hostUrl, title); - } - getProtocolUrlFromProcess (args) { - let site = null; - if (args.length > 1) { - const protocolURI = args.find(arg => arg.startsWith('rocketchat://')); - if (protocolURI) { - site = protocolURI.split(/\/|\?/)[2]; - if (site) { - let scheme = 'https://'; - if (protocolURI.includes('insecure=true')) { - scheme = 'http://'; - } - site = scheme + site; - } - } - } - return site; - } - showHostConfirmation (host) { - return remote.dialog.showMessageBox({ - type: 'question', - buttons: [i18n.__('Add'), i18n.__('Cancel')], - defaultId: 0, - title: i18n.__('Add_Server'), - message: i18n.__('Add_host_to_servers', host) - }, (response) => { - if (response === 0) { - this.validateHost(host) - .then(() => this.addHost(host)) - .then(() => this.setActive(host)) - .catch(() => remote.dialog.showErrorBox(i18n.__('Invalid_Host'), i18n.__('Host_not_validated', host))); - } - }); - } - - resetAppData () { - return remote.dialog.showMessageBox({ - type: 'question', - buttons: ['Yes', 'Cancel'], - defaultId: 1, - title: 'Reset App Data', - message: 'This will sign you out from all your teams and reset the app back to its original settings. This cannot be undone.' - }, (response) => { - if (response === 0) { - const dataDir = remote.app.getPath('userData'); - jetpack.remove(dataDir); - remote.app.relaunch(); - remote.app.quit(); - } - }); - } + constructor() { + super(); + this.load(); + const processProtocol = this.getProtocolUrlFromProcess(remote.process.argv); + if (processProtocol) { + this.showHostConfirmation(processProtocol); + } + ipcRenderer.on('add-host', (e, host) => { + if (this.hostExists(host)) { + this.setActive(host); + } else { + this.showHostConfirmation(host); + } + }); + } + + get hosts() { + return this._hosts; + } + + set hosts(hosts) { + this._hosts = hosts; + this.save(); + return true; + } + + get hostsKey() { + return 'rocket.chat.hosts'; + } + + get activeKey() { + return 'rocket.chat.currentHost'; + } + + load() { + let hosts = localStorage.getItem(this.hostsKey); + + try { + hosts = JSON.parse(hosts); + } catch (e) { + if (typeof hosts === 'string' && hosts.match(/^https?:\/\//)) { + hosts = {}; + hosts[hosts] = { + title: hosts, + url: hosts, + }; + } + + localStorage.setItem(this.hostsKey, JSON.stringify(hosts)); + } + + if (hosts === null) { + hosts = {}; + } + + if (Array.isArray(hosts)) { + const oldHosts = hosts; + hosts = {}; + oldHosts.forEach(function(item) { + item = item.replace(/\/$/, ''); + hosts[item] = { + title: item, + url: item, + }; + }); + localStorage.setItem(this.hostsKey, JSON.stringify(hosts)); + } + + // Load server info from server config file + if (Object.keys(hosts).length === 0) { + const path = jetpack.find(remote.app.getPath('userData'), { matching: 'servers.json' })[0] || + jetpack.find(jetpack.path(remote.app.getAppPath(), '..'), { matching: 'servers.json' })[0]; + + if (path) { + const pathToServerJson = jetpack.path(path); + + try { + const result = jetpack.read(pathToServerJson, 'json'); + if (result) { + hosts = {}; + Object.keys(result).forEach((title) => { + const url = result[title]; + hosts[url] = { title, url }; + }); + localStorage.setItem(this.hostsKey, JSON.stringify(hosts)); + // Assume user doesn't want sidebar if they only have one server + if (Object.keys(hosts).length === 1) { + localStorage.setItem('sidebar-closed', 'true'); + } + } + + } catch (e) { + console.error('Server file invalid'); + } + } + } + + this._hosts = hosts; + remoteServers.loadServers(this._hosts); + this.emit('loaded'); + } + + save() { + localStorage.setItem(this.hostsKey, JSON.stringify(this._hosts)); + this.emit('saved'); + } + + get(hostUrl) { + return this.hosts[hostUrl]; + } + + forEach(cb) { + for (const host in this.hosts) { + if (this.hosts.hasOwnProperty(host)) { + cb(this.hosts[host]); + } + } + } + + validateHost(hostUrl, timeout) { + timeout = timeout || 5000; + return new Promise(function(resolve, reject) { + let resolved = false; + $.getJSON(`${ hostUrl }/api/info`).then(function() { + if (resolved) { + return; + } + resolved = true; + resolve(); + }, function(request) { + if (request.status === 401) { + const authHeader = request.getResponseHeader('www-authenticate'); + if (authHeader && authHeader.toLowerCase().indexOf('basic ') === 0) { + resolved = true; + reject('basic-auth'); + } + } + if (resolved) { + return; + } + resolved = true; + reject('invalid'); + }); + if (timeout) { + setTimeout(function() { + if (resolved) { + return; + } + resolved = true; + reject('timeout'); + }, timeout); + } + }); + } + + hostExists(hostUrl) { + const { hosts } = this; + + return !!hosts[hostUrl]; + } + + addHost(hostUrl) { + const { hosts } = this; + + const match = hostUrl.match(/^(https?:\/\/)([^:]+):([^@]+)@(.+)$/); + let username; + let password; + let authUrl; + if (match) { + authUrl = hostUrl; + hostUrl = match[1] + match[4]; + username = match[2]; + password = match[3]; + } + + if (this.hostExists(hostUrl) === true) { + this.setActive(hostUrl); + return false; + } + + hosts[hostUrl] = { + title: hostUrl, + url: hostUrl, + authUrl, + username, + password, + }; + this.hosts = hosts; + + remoteServers.loadServers(this.hosts); + + this.emit('host-added', hostUrl); + + return hostUrl; + } + + removeHost(hostUrl) { + const { hosts } = this; + if (hosts[hostUrl]) { + delete hosts[hostUrl]; + this.hosts = hosts; + + remoteServers.loadServers(this.hosts); + + if (this.active === hostUrl) { + this.clearActive(); + } + this.emit('host-removed', hostUrl); + } + } + + get active() { + return localStorage.getItem(this.activeKey); + } + + setActive(hostUrl) { + let url; + if (this.hostExists(hostUrl)) { + url = hostUrl; + } else if (Object.keys(this._hosts).length > 0) { + url = Object.keys(this._hosts)[0]; + } + + if (url) { + localStorage.setItem(this.activeKey, hostUrl); + this.emit('active-setted', url); + return true; + } + this.emit('loaded'); + return false; + } + + restoreActive() { + this.setActive(this.active); + } + + clearActive() { + localStorage.removeItem(this.activeKey); + this.emit('active-cleared'); + return true; + } + + setHostTitle(hostUrl, title) { + if (title === 'Rocket.Chat' && /https?:\/\/open\.rocket\.chat/.test(hostUrl) === false) { + title += ` - ${ hostUrl }`; + } + const { hosts } = this; + hosts[hostUrl].title = title; + this.hosts = hosts; + this.emit('title-setted', hostUrl, title); + } + getProtocolUrlFromProcess(args) { + let site = null; + if (args.length > 1) { + const protocolURI = args.find((arg) => arg.startsWith('rocketchat://')); + if (protocolURI) { + site = protocolURI.split(/\/|\?/)[2]; + if (site) { + let scheme = 'https://'; + if (protocolURI.includes('insecure=true')) { + scheme = 'http://'; + } + site = scheme + site; + } + } + } + return site; + } + showHostConfirmation(host) { + return remote.dialog.showMessageBox({ + type: 'question', + buttons: [i18n.__('Add'), i18n.__('Cancel')], + defaultId: 0, + title: i18n.__('Add_Server'), + message: i18n.__('Add_host_to_servers', host), + }, (response) => { + if (response === 0) { + this.validateHost(host) + .then(() => this.addHost(host)) + .then(() => this.setActive(host)) + .catch(() => remote.dialog.showErrorBox(i18n.__('Invalid_Host'), i18n.__('Host_not_validated', host))); + } + }); + } + + resetAppData() { + return remote.dialog.showMessageBox({ + type: 'question', + buttons: ['Yes', 'Cancel'], + defaultId: 1, + title: 'Reset App Data', + message: 'This will sign you out from all your teams and reset the app back to its original settings. This cannot be undone.', + }, (response) => { + if (response === 0) { + const dataDir = remote.app.getPath('userData'); + jetpack.remove(dataDir); + remote.app.relaunch(); + remote.app.quit(); + } + }); + } } diff --git a/src/scripts/sidebar.js b/src/scripts/sidebar.js index 5d084122cf..3cf7fac97e 100644 --- a/src/scripts/sidebar.js +++ b/src/scripts/sidebar.js @@ -6,309 +6,309 @@ import webview from './webview'; import * as menus from './menus'; class SideBar extends EventEmitter { - constructor () { - super(); - - this.sortOrder = JSON.parse(localStorage.getItem(this.sortOrderKey)) || []; - localStorage.setItem(this.sortOrderKey, JSON.stringify(this.sortOrder)); - - this.listElement = document.getElementById('serverList'); - - Object.values(servers.hosts) - .sort((a, b) => this.sortOrder.indexOf(a.url) - this.sortOrder.indexOf(b.url)) - .forEach((host) => { - this.add(host); - }); - - servers.on('host-added', (hostUrl) => { - this.add(servers.get(hostUrl)); - }); - - servers.on('host-removed', (hostUrl) => { - this.remove(hostUrl); - }); - - servers.on('active-setted', (hostUrl) => { - this.setActive(hostUrl); - }); - - servers.on('active-cleared', (hostUrl) => { - this.deactiveAll(hostUrl); - }); - - servers.on('title-setted', (hostUrl, title) => { - this.setLabel(hostUrl, title); - }); - - webview.on('dom-ready', (hostUrl) => { - this.setActive(localStorage.getItem(servers.activeKey)); - webview.getActive().send('request-sidebar-color'); - this.setImage(hostUrl); - if (this.isHidden()) { - this.hide(); - } else { - this.show(); - } - }); - - } - - get sortOrderKey () { - return 'rocket.chat.sortOrder'; - } - - add (host) { - let name = host.title.replace(/^https?:\/\/(?:www\.)?([^\/]+)(.*)/, '$1'); - name = name.split('.'); - name = name[0][0] + (name[1] ? name[1][0] : ''); - name = name.toUpperCase(); - - const initials = document.createElement('span'); - initials.innerHTML = name; - - const tooltip = document.createElement('div'); - tooltip.classList.add('tooltip'); - tooltip.innerHTML = host.title; - - const badge = document.createElement('div'); - badge.classList.add('badge'); - - const img = document.createElement('img'); - img.onload = function () { - img.style.display = 'initial'; - initials.style.display = 'none'; - }; - - let hostOrder = 0; - if (this.sortOrder.includes(host.url)) { - hostOrder = this.sortOrder.indexOf(host.url) + 1; - } else { - hostOrder = this.sortOrder.length + 1; - this.sortOrder.push(host.url); - } - - const hotkey = document.createElement('div'); - hotkey.classList.add('name'); - if (process.platform === 'darwin') { - hotkey.innerHTML = `⌘${hostOrder}`; - } else { - hotkey.innerHTML = `^${hostOrder}`; - } - - const item = document.createElement('li'); - item.appendChild(initials); - item.appendChild(tooltip); - item.appendChild(badge); - item.appendChild(img); - item.appendChild(hotkey); - - item.dataset.host = host.url; - item.dataset.sortOrder = hostOrder; - item.setAttribute('server', host.url); - item.classList.add('instance'); - - item.setAttribute('draggable', true); - - item.ondragstart = (event) => { - window.dragged = event.target.nodeName !== 'LI' ? event.target.closest('li') : event.target; - event.dataTransfer.effectAllowed = 'move'; - event.dataTransfer.dropEffect = 'move'; - event.target.style.opacity = .5; - }; - - item.ondragover = (event) => { - event.preventDefault(); - }; - - item.ondragenter = (event) => { - if (this.isBefore(window.dragged, event.target)) { - event.currentTarget.parentNode.insertBefore(window.dragged, event.currentTarget); - } else if (event.currentTarget !== event.currentTarget.parentNode.lastChild) { - event.currentTarget.parentNode.insertBefore(window.dragged, event.currentTarget.nextSibling); - } else { - event.currentTarget.parentNode.appendChild(window.dragged); - } - }; - - item.ondragend = (event) => { - event.target.style.opacity = ''; - }; - - item.ondrop = (event) => { - event.preventDefault(); - - const newSortOrder = []; - Array.from(event.currentTarget.parentNode.children) - .map((sideBarElement) => { - const url = sideBarElement.dataset.host; - newSortOrder.push(url); - this.remove(url); - - return sideBarElement; - }) - .map((sideBarElement) => { - this.sortOrder = newSortOrder; - localStorage.setItem(this.sortOrderKey, JSON.stringify(this.sortOrder)); - - const url = sideBarElement.dataset.host; - const host = { url, title: sideBarElement.querySelector('div.tooltip').innerHTML }; - this.add(host); - this.setImage(url); - }); - - this.setActive(window.dragged.dataset.host); - }; - - item.onclick = () => { - servers.setActive(host.url); - }; - - this.listElement.appendChild(item); - menus.addServer(host, hostOrder); - } - - setImage (hostUrl) { - const img = this.getByUrl(hostUrl).querySelector('img'); - img.src = `${hostUrl}/assets/favicon.svg?v=${Math.round(Math.random()*10000)}`; - } - - remove (hostUrl) { - const el = this.getByUrl(hostUrl); - if (el) { - el.remove(); - menus.removeServer(hostUrl); - } - } - - getByUrl (hostUrl) { - return this.listElement.querySelector(`.instance[server="${hostUrl}"]`); - } - - getActive () { - return this.listElement.querySelector('.instance.active'); - } - - isActive (hostUrl) { - return !!this.listElement.querySelector(`.instance.active[server="${hostUrl}"]`); - } - - changeSidebarColor ({color, background}) { - const sidebar = document.querySelector('.server-list'); - if (sidebar) { - sidebar.style.background = background; - sidebar.style.color = color; - } - } - - setActive (hostUrl) { - if (this.isActive(hostUrl)) { - return; - } - - this.deactiveAll(); - const item = this.getByUrl(hostUrl); - if (item) { - item.classList.add('active'); - } - webview.getActive().send && webview.getActive().send('request-sidebar-color'); - } - - deactiveAll () { - let item; - while (!(item = this.getActive()) === false) { - item.classList.remove('active'); - } - } - - setLabel (hostUrl, label) { - this.listElement.querySelector(`.instance[server="${hostUrl}"] .tooltip`).innerHTML = label; - } - - setBadge (hostUrl, badge) { - const item = this.getByUrl(hostUrl); - const badgeEl = item.querySelector('.badge'); - - if (badge !== null && badge !== undefined && badge !== '') { - item.classList.add('unread'); - if (isNaN(parseInt(badge))) { - badgeEl.innerHTML = ''; - } else { - badgeEl.innerHTML = badge; - } - } else { - badge = undefined; - item.classList.remove('unread'); - badgeEl.innerHTML = ''; - } - this.emit('badge-setted', hostUrl, badge); - } - - getGlobalBadge () { - let count = 0; - let title = ''; - const instanceEls = this.listElement.querySelectorAll('li.instance'); - for (let i = instanceEls.length - 1; i >= 0; i--) { - const instanceEl = instanceEls[i]; - const text = instanceEl.querySelector('.badge').innerHTML; - if (!isNaN(parseInt(text))) { - count += parseInt(text); - } - if (title === '' && instanceEl.classList.contains('unread') === true) { - title = '•'; - } - } - if (count > 0) { - title = count.toString(); - } - return { - count: count, - title: title, - showAlert: (title !== '') - }; - } - - hide () { - document.body.classList.add('hide-server-list'); - localStorage.setItem('sidebar-closed', 'true'); - this.emit('hide'); - if (process.platform === 'darwin') { - document.querySelectorAll('webview').forEach( - (webviewObj) => { if (webviewObj.insertCSS) { webviewObj.insertCSS('aside.side-nav{margin-top:15px;overflow:hidden; transition: margin .5s ease-in-out; } .sidebar{padding-top:10px;transition: margin .5s ease-in-out;}'); } }); - } - } - - show () { - document.body.classList.remove('hide-server-list'); - localStorage.setItem('sidebar-closed', 'false'); - this.emit('show'); - if (process.platform === 'darwin') { - document.querySelectorAll('webview').forEach( - (webviewObj) => { if (webviewObj.insertCSS) { webviewObj.insertCSS('aside.side-nav{margin-top:0; overflow:hidden; transition: margin .5s ease-in-out;} .sidebar{padding-top:0;transition: margin .5s ease-in-out;}'); } }); - } - } - - toggle () { - if (this.isHidden()) { - this.show(); - } else { - this.hide(); - } - } - - isHidden () { - return localStorage.getItem('sidebar-closed') === 'true'; - } - - isBefore (a, b) { - if (a.parentNode === b.parentNode) { - for (let cur = a; cur; cur = cur.previousSibling) { - if (cur === b) { - return true; - } - } - } - return false; - } + constructor() { + super(); + + this.sortOrder = JSON.parse(localStorage.getItem(this.sortOrderKey)) || []; + localStorage.setItem(this.sortOrderKey, JSON.stringify(this.sortOrder)); + + this.listElement = document.getElementById('serverList'); + + Object.values(servers.hosts) + .sort((a, b) => this.sortOrder.indexOf(a.url) - this.sortOrder.indexOf(b.url)) + .forEach((host) => { + this.add(host); + }); + + servers.on('host-added', (hostUrl) => { + this.add(servers.get(hostUrl)); + }); + + servers.on('host-removed', (hostUrl) => { + this.remove(hostUrl); + }); + + servers.on('active-setted', (hostUrl) => { + this.setActive(hostUrl); + }); + + servers.on('active-cleared', (hostUrl) => { + this.deactiveAll(hostUrl); + }); + + servers.on('title-setted', (hostUrl, title) => { + this.setLabel(hostUrl, title); + }); + + webview.on('dom-ready', (hostUrl) => { + this.setActive(localStorage.getItem(servers.activeKey)); + webview.getActive().send('request-sidebar-color'); + this.setImage(hostUrl); + if (this.isHidden()) { + this.hide(); + } else { + this.show(); + } + }); + + } + + get sortOrderKey() { + return 'rocket.chat.sortOrder'; + } + + add(host) { + let name = host.title.replace(/^https?:\/\/(?:www\.)?([^\/]+)(.*)/, '$1'); + name = name.split('.'); + name = name[0][0] + (name[1] ? name[1][0] : ''); + name = name.toUpperCase(); + + const initials = document.createElement('span'); + initials.innerHTML = name; + + const tooltip = document.createElement('div'); + tooltip.classList.add('tooltip'); + tooltip.innerHTML = host.title; + + const badge = document.createElement('div'); + badge.classList.add('badge'); + + const img = document.createElement('img'); + img.onload = function() { + img.style.display = 'initial'; + initials.style.display = 'none'; + }; + + let hostOrder = 0; + if (this.sortOrder.includes(host.url)) { + hostOrder = this.sortOrder.indexOf(host.url) + 1; + } else { + hostOrder = this.sortOrder.length + 1; + this.sortOrder.push(host.url); + } + + const hotkey = document.createElement('div'); + hotkey.classList.add('name'); + if (process.platform === 'darwin') { + hotkey.innerHTML = `⌘${ hostOrder }`; + } else { + hotkey.innerHTML = `^${ hostOrder }`; + } + + const item = document.createElement('li'); + item.appendChild(initials); + item.appendChild(tooltip); + item.appendChild(badge); + item.appendChild(img); + item.appendChild(hotkey); + + item.dataset.host = host.url; + item.dataset.sortOrder = hostOrder; + item.setAttribute('server', host.url); + item.classList.add('instance'); + + item.setAttribute('draggable', true); + + item.ondragstart = (event) => { + window.dragged = event.target.nodeName !== 'LI' ? event.target.closest('li') : event.target; + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.dropEffect = 'move'; + event.target.style.opacity = .5; + }; + + item.ondragover = (event) => { + event.preventDefault(); + }; + + item.ondragenter = (event) => { + if (this.isBefore(window.dragged, event.target)) { + event.currentTarget.parentNode.insertBefore(window.dragged, event.currentTarget); + } else if (event.currentTarget !== event.currentTarget.parentNode.lastChild) { + event.currentTarget.parentNode.insertBefore(window.dragged, event.currentTarget.nextSibling); + } else { + event.currentTarget.parentNode.appendChild(window.dragged); + } + }; + + item.ondragend = (event) => { + event.target.style.opacity = ''; + }; + + item.ondrop = (event) => { + event.preventDefault(); + + const newSortOrder = []; + Array.from(event.currentTarget.parentNode.children) + .map((sideBarElement) => { + const url = sideBarElement.dataset.host; + newSortOrder.push(url); + this.remove(url); + + return sideBarElement; + }) + .forEach((sideBarElement) => { + this.sortOrder = newSortOrder; + localStorage.setItem(this.sortOrderKey, JSON.stringify(this.sortOrder)); + + const url = sideBarElement.dataset.host; + const host = { url, title: sideBarElement.querySelector('div.tooltip').innerHTML }; + this.add(host); + this.setImage(url); + }); + + this.setActive(window.dragged.dataset.host); + }; + + item.onclick = () => { + servers.setActive(host.url); + }; + + this.listElement.appendChild(item); + menus.addServer(host, hostOrder); + } + + setImage(hostUrl) { + const img = this.getByUrl(hostUrl).querySelector('img'); + img.src = `${ hostUrl }/assets/favicon.svg?v=${ Math.round(Math.random() * 10000) }`; + } + + remove(hostUrl) { + const el = this.getByUrl(hostUrl); + if (el) { + el.remove(); + menus.removeServer(hostUrl); + } + } + + getByUrl(hostUrl) { + return this.listElement.querySelector(`.instance[server="${ hostUrl }"]`); + } + + getActive() { + return this.listElement.querySelector('.instance.active'); + } + + isActive(hostUrl) { + return !!this.listElement.querySelector(`.instance.active[server="${ hostUrl }"]`); + } + + changeSidebarColor({ color, background }) { + const sidebar = document.querySelector('.server-list'); + if (sidebar) { + sidebar.style.background = background; + sidebar.style.color = color; + } + } + + setActive(hostUrl) { + if (this.isActive(hostUrl)) { + return; + } + + this.deactiveAll(); + const item = this.getByUrl(hostUrl); + if (item) { + item.classList.add('active'); + } + webview.getActive().send && webview.getActive().send('request-sidebar-color'); + } + + deactiveAll() { + let item; + while (!(item = this.getActive()) === false) { + item.classList.remove('active'); + } + } + + setLabel(hostUrl, label) { + this.listElement.querySelector(`.instance[server="${ hostUrl }"] .tooltip`).innerHTML = label; + } + + setBadge(hostUrl, badge) { + const item = this.getByUrl(hostUrl); + const badgeEl = item.querySelector('.badge'); + + if (badge !== null && badge !== undefined && badge !== '') { + item.classList.add('unread'); + if (isNaN(parseInt(badge))) { + badgeEl.innerHTML = ''; + } else { + badgeEl.innerHTML = badge; + } + } else { + badge = undefined; + item.classList.remove('unread'); + badgeEl.innerHTML = ''; + } + this.emit('badge-setted', hostUrl, badge); + } + + getGlobalBadge() { + let count = 0; + let title = ''; + const instanceEls = this.listElement.querySelectorAll('li.instance'); + for (let i = instanceEls.length - 1; i >= 0; i--) { + const instanceEl = instanceEls[i]; + const text = instanceEl.querySelector('.badge').innerHTML; + if (!isNaN(parseInt(text))) { + count += parseInt(text); + } + if (title === '' && instanceEl.classList.contains('unread') === true) { + title = '•'; + } + } + if (count > 0) { + title = count.toString(); + } + return { + count, + title, + showAlert: (title !== ''), + }; + } + + hide() { + document.body.classList.add('hide-server-list'); + localStorage.setItem('sidebar-closed', 'true'); + this.emit('hide'); + if (process.platform === 'darwin') { + document.querySelectorAll('webview').forEach( + (webviewObj) => { if (webviewObj.insertCSS) { webviewObj.insertCSS('aside.side-nav{margin-top:15px;overflow:hidden; transition: margin .5s ease-in-out; } .sidebar{padding-top:10px;transition: margin .5s ease-in-out;}'); } }); + } + } + + show() { + document.body.classList.remove('hide-server-list'); + localStorage.setItem('sidebar-closed', 'false'); + this.emit('show'); + if (process.platform === 'darwin') { + document.querySelectorAll('webview').forEach( + (webviewObj) => { if (webviewObj.insertCSS) { webviewObj.insertCSS('aside.side-nav{margin-top:0; overflow:hidden; transition: margin .5s ease-in-out;} .sidebar{padding-top:0;transition: margin .5s ease-in-out;}'); } }); + } + } + + toggle() { + if (this.isHidden()) { + this.show(); + } else { + this.hide(); + } + } + + isHidden() { + return localStorage.getItem('sidebar-closed') === 'true'; + } + + isBefore(a, b) { + if (a.parentNode === b.parentNode) { + for (let cur = a; cur; cur = cur.previousSibling) { + if (cur === b) { + return true; + } + } + } + return false; + } } export default new SideBar(); @@ -316,57 +316,57 @@ export default new SideBar(); let selectedInstance = null; const instanceMenu = remote.Menu.buildFromTemplate([{ - label: i18n.__('Reload_server'), - click: function () { - webview.getByUrl(selectedInstance.dataset.host).reload(); - } + label: i18n.__('Reload_server'), + click() { + webview.getByUrl(selectedInstance.dataset.host).reload(); + }, }, { - label: i18n.__('Remove_server'), - click: function () { - servers.removeHost(selectedInstance.dataset.host); - } + label: i18n.__('Remove_server'), + click() { + servers.removeHost(selectedInstance.dataset.host); + }, }, { - label: i18n.__('Open_DevTools'), - click: function () { - webview.getByUrl(selectedInstance.dataset.host).openDevTools(); - } + label: i18n.__('Open_DevTools'), + click() { + webview.getByUrl(selectedInstance.dataset.host).openDevTools(); + }, }]); -window.addEventListener('contextmenu', function (e) { - if (e.target.classList.contains('instance') || e.target.parentNode.classList.contains('instance')) { - e.preventDefault(); - if (e.target.classList.contains('instance')) { - selectedInstance = e.target; - } else { - selectedInstance = e.target.parentNode; - } - - instanceMenu.popup(remote.getCurrentWindow()); - } +window.addEventListener('contextmenu', function(e) { + if (e.target.classList.contains('instance') || e.target.parentNode.classList.contains('instance')) { + e.preventDefault(); + if (e.target.classList.contains('instance')) { + selectedInstance = e.target; + } else { + selectedInstance = e.target.parentNode; + } + + instanceMenu.popup(remote.getCurrentWindow()); + } }, false); if (process.platform === 'darwin') { - window.addEventListener('keydown', function (e) { - if (e.key === 'Meta') { - document.getElementsByClassName('server-list')[0].classList.add('command-pressed'); - } - }); - - window.addEventListener('keyup', function (e) { - if (e.key === 'Meta') { - document.getElementsByClassName('server-list')[0].classList.remove('command-pressed'); - } - }); + window.addEventListener('keydown', function(e) { + if (e.key === 'Meta') { + document.getElementsByClassName('server-list')[0].classList.add('command-pressed'); + } + }); + + window.addEventListener('keyup', function(e) { + if (e.key === 'Meta') { + document.getElementsByClassName('server-list')[0].classList.remove('command-pressed'); + } + }); } else { - window.addEventListener('keydown', function (e) { - if (e.key === 'ctrlKey') { - document.getElementsByClassName('server-list')[0].classList.add('command-pressed'); - } - }); - - window.addEventListener('keyup', function (e) { - if (e.key === 'ctrlKey') { - document.getElementsByClassName('server-list')[0].classList.remove('command-pressed'); - } - }); + window.addEventListener('keydown', function(e) { + if (e.key === 'ctrlKey') { + document.getElementsByClassName('server-list')[0].classList.add('command-pressed'); + } + }); + + window.addEventListener('keyup', function(e) { + if (e.key === 'ctrlKey') { + document.getElementsByClassName('server-list')[0].classList.remove('command-pressed'); + } + }); } diff --git a/src/scripts/start.js b/src/scripts/start.js index ecf3cc1bfe..ee7f6aa776 100644 --- a/src/scripts/start.js +++ b/src/scripts/start.js @@ -8,183 +8,183 @@ import webview from './webview'; import tray from './tray'; import './menus'; -sidebar.on('badge-setted', function () { - const badge = sidebar.getGlobalBadge(); - tray.showTrayAlert(badge); +sidebar.on('badge-setted', function() { + const badge = sidebar.getGlobalBadge(); + tray.showTrayAlert(badge); }); -export const start = function () { - const defaultInstance = 'https://open.rocket.chat'; - - // connection check - function online () { - document.body.classList.remove('offline'); - } - - function offline () { - document.body.classList.add('offline'); - } - - if (!navigator.onLine) { - offline(); - } - window.addEventListener('online', online); - window.addEventListener('offline', offline); - // end connection check - - const form = document.querySelector('form'); - const hostField = form.querySelector('[name="host"]'); - const button = form.querySelector('[type="submit"]'); - const invalidUrl = form.querySelector('#invalidUrl'); - - window.addEventListener('load', function () { - hostField.focus(); - }); - - function validateHost () { - return new Promise(function (resolve, reject) { - const execValidation = function () { - invalidUrl.style.display = 'none'; - hostField.classList.remove('wrong'); - - const host = hostField.value.trim(); - hostField.value = host; - - if (host.length === 0) { - button.value = i18n.__('Connect'); - button.disabled = false; - resolve(); - return; - } - - button.value = i18n.__('Validating'); - button.disabled = true; - - servers.validateHost(host, 2000).then(function () { - button.value = i18n.__('Connect'); - button.disabled = false; - resolve(); - }, function (status) { - // If the url begins with HTTP, mark as invalid - if (/^https?:\/\/.+/.test(host) || status === 'basic-auth') { - button.value = i18n.__('Invalid_url'); - invalidUrl.style.display = 'block'; - switch (status) { - case 'basic-auth': - invalidUrl.innerHTML = i18n.__('Auth_needed_try', 'username:password@host'); - break; - case 'invalid': - invalidUrl.innerHTML = i18n.__('No_valid_server_found'); - break; - case 'timeout': - invalidUrl.innerHTML = i18n.__('Timeout_trying_to_connect'); - break; - } - hostField.classList.add('wrong'); - reject(); - return; - } - - // // If the url begins with HTTPS, fallback to HTTP - // if (/^https:\/\/.+/.test(host)) { - // hostField.value = host.replace('https://', 'http://'); - // return execValidation(); - // } - - // If the url isn't localhost, don't have dots and don't have protocol - // try as a .rocket.chat subdomain - if (!/(^https?:\/\/)|(\.)|(^([^:]+:[^@]+@)?localhost(:\d+)?$)/.test(host)) { - hostField.value = `https://${host}.rocket.chat`; - return execValidation(); - } - - // If the url don't start with protocol try HTTPS - if (!/^https?:\/\//.test(host)) { - hostField.value = `https://${host}`; - return execValidation(); - } - }); - }; - execValidation(); - }); - } - - hostField.addEventListener('blur', function () { - validateHost().then(function () {}, function () {}); - }); - - ipcRenderer.on('certificate-reload', function (event, url) { - hostField.value = url.replace(/\/api\/info$/, ''); - validateHost().then(function () {}, function () {}); - }); - - ipcRenderer.on('render-taskbar-icon', (event, messageCount) => { - // Create a canvas from unread messages - function createOverlayIcon (messageCount) { - const canvas = document.createElement('canvas'); - canvas.height = 128; - canvas.width = 128; - - const ctx = canvas.getContext('2d'); - ctx.beginPath(); - - ctx.fillStyle = 'red'; - ctx.arc(64, 64, 64, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = '#ffffff'; - ctx.textAlign = 'center'; - canvas.style.letterSpacing = '-4px'; - ctx.font = 'bold 92px sans-serif'; - ctx.fillText(String(Math.min(99, messageCount)), 64, 98); - - return canvas; - } - ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount)); - }); - - const submit = function () { - validateHost().then(function () { - const input = form.querySelector('[name="host"]'); - let url = input.value; - - if (url.length === 0) { - url = defaultInstance; - } - - url = servers.addHost(url); - if (url !== false) { - sidebar.show(); - servers.setActive(url); - } - - input.value = ''; - }, function () {}); - }; - - hostField.addEventListener('keydown', function (ev) { - if (ev.which === 13) { - ev.preventDefault(); - ev.stopPropagation(); - submit(); - return false; - } - }); - - form.addEventListener('submit', function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - submit(); - return false; - }); - - $('.add-server').on('click', function () { - servers.clearActive(); - webview.showLanding(); - }); - - servers.restoreActive(); +export const start = function() { + const defaultInstance = 'https://open.rocket.chat'; + + // connection check + function online() { + document.body.classList.remove('offline'); + } + + function offline() { + document.body.classList.add('offline'); + } + + if (!navigator.onLine) { + offline(); + } + window.addEventListener('online', online); + window.addEventListener('offline', offline); + // end connection check + + const form = document.querySelector('form'); + const hostField = form.querySelector('[name="host"]'); + const button = form.querySelector('[type="submit"]'); + const invalidUrl = form.querySelector('#invalidUrl'); + + window.addEventListener('load', function() { + hostField.focus(); + }); + + function validateHost() { + return new Promise(function(resolve, reject) { + const execValidation = function() { + invalidUrl.style.display = 'none'; + hostField.classList.remove('wrong'); + + const host = hostField.value.trim(); + hostField.value = host; + + if (host.length === 0) { + button.value = i18n.__('Connect'); + button.disabled = false; + resolve(); + return; + } + + button.value = i18n.__('Validating'); + button.disabled = true; + + servers.validateHost(host, 2000).then(function() { + button.value = i18n.__('Connect'); + button.disabled = false; + resolve(); + }, function(status) { + // If the url begins with HTTP, mark as invalid + if (/^https?:\/\/.+/.test(host) || status === 'basic-auth') { + button.value = i18n.__('Invalid_url'); + invalidUrl.style.display = 'block'; + switch (status) { + case 'basic-auth': + invalidUrl.innerHTML = i18n.__('Auth_needed_try', 'username:password@host'); + break; + case 'invalid': + invalidUrl.innerHTML = i18n.__('No_valid_server_found'); + break; + case 'timeout': + invalidUrl.innerHTML = i18n.__('Timeout_trying_to_connect'); + break; + } + hostField.classList.add('wrong'); + reject(); + return; + } + + // // If the url begins with HTTPS, fallback to HTTP + // if (/^https:\/\/.+/.test(host)) { + // hostField.value = host.replace('https://', 'http://'); + // return execValidation(); + // } + + // If the url isn't localhost, don't have dots and don't have protocol + // try as a .rocket.chat subdomain + if (!/(^https?:\/\/)|(\.)|(^([^:]+:[^@]+@)?localhost(:\d+)?$)/.test(host)) { + hostField.value = `https://${ host }.rocket.chat`; + return execValidation(); + } + + // If the url don't start with protocol try HTTPS + if (!/^https?:\/\//.test(host)) { + hostField.value = `https://${ host }`; + return execValidation(); + } + }); + }; + execValidation(); + }); + } + + hostField.addEventListener('blur', function() { + validateHost().then(function() {}, function() {}); + }); + + ipcRenderer.on('certificate-reload', function(event, url) { + hostField.value = url.replace(/\/api\/info$/, ''); + validateHost().then(function() {}, function() {}); + }); + + ipcRenderer.on('render-taskbar-icon', (event, messageCount) => { + // Create a canvas from unread messages + function createOverlayIcon(messageCount) { + const canvas = document.createElement('canvas'); + canvas.height = 128; + canvas.width = 128; + + const ctx = canvas.getContext('2d'); + ctx.beginPath(); + + ctx.fillStyle = 'red'; + ctx.arc(64, 64, 64, 0, 2 * Math.PI); + ctx.fill(); + ctx.fillStyle = '#ffffff'; + ctx.textAlign = 'center'; + canvas.style.letterSpacing = '-4px'; + ctx.font = 'bold 92px sans-serif'; + ctx.fillText(String(Math.min(99, messageCount)), 64, 98); + + return canvas; + } + ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount)); + }); + + const submit = function() { + validateHost().then(function() { + const input = form.querySelector('[name="host"]'); + let url = input.value; + + if (url.length === 0) { + url = defaultInstance; + } + + url = servers.addHost(url); + if (url !== false) { + sidebar.show(); + servers.setActive(url); + } + + input.value = ''; + }, function() {}); + }; + + hostField.addEventListener('keydown', function(ev) { + if (ev.which === 13) { + ev.preventDefault(); + ev.stopPropagation(); + submit(); + return false; + } + }); + + form.addEventListener('submit', function(ev) { + ev.preventDefault(); + ev.stopPropagation(); + submit(); + return false; + }); + + $('.add-server').on('click', function() { + servers.clearActive(); + webview.showLanding(); + }); + + servers.restoreActive(); }; -window.addEventListener('focus', function () { - webview.focusActive(); +window.addEventListener('focus', function() { + webview.focusActive(); }); diff --git a/src/scripts/tray.js b/src/scripts/tray.js index bd1ca96c7b..f67cf66d77 100644 --- a/src/scripts/tray.js +++ b/src/scripts/tray.js @@ -9,187 +9,187 @@ const { Tray, Menu } = remote; const mainWindow = remote.getCurrentWindow(); const icons = { - win32: { - dir: 'windows' - }, - linux: { - dir: 'linux' - }, - darwin: { - dir: 'osx' - } + win32: { + dir: 'windows', + }, + linux: { + dir: 'linux', + }, + darwin: { + dir: 'osx', + }, }; const statusBullet = { - online: '\u001B[32m•', - away: '\u001B[33m•', - busy: '\u001B[31m•', - offline: '\u001B[37m•' + online: '\u001B[32m•', + away: '\u001B[33m•', + busy: '\u001B[31m•', + offline: '\u001B[37m•', }; const messageCountColor = { - white: '\u001B[37m', - black: '\u001B[0m' + white: '\u001B[37m', + black: '\u001B[0m', }; -function getTrayImagePath (badge) { - let iconFilename; - if (badge.title === '•') { - iconFilename = "icon-tray-dot"; - } else if (badge.count > 0) { - if (badge.count > 9) { - iconFilename = "icon-tray-9plus"; - } else { - iconFilename = `icon-tray-${badge.count}`; - } - } else if (badge.showAlert) { - iconFilename = "icon-tray-alert"; - } else { - iconFilename = "icon-tray-Template"; - } - - if (process.platform === 'win32') { - iconFilename += ".ico"; - } else { - iconFilename += ".png"; - } - - return path.join(__dirname, 'images', icons[process.platform].dir, iconFilename); +function getTrayImagePath(badge) { + let iconFilename; + if (badge.title === '•') { + iconFilename = 'icon-tray-dot'; + } else if (badge.count > 0) { + if (badge.count > 9) { + iconFilename = 'icon-tray-9plus'; + } else { + iconFilename = `icon-tray-${ badge.count }`; + } + } else if (badge.showAlert) { + iconFilename = 'icon-tray-alert'; + } else { + iconFilename = 'icon-tray-Template'; + } + + if (process.platform === 'win32') { + iconFilename += '.ico'; + } else { + iconFilename += '.png'; + } + + return path.join(__dirname, 'images', icons[process.platform].dir, iconFilename); } -function createAppTray () { - const _tray = new Tray(getTrayImagePath({title:'', count:0, showAlert:false})); - mainWindow.tray = _tray; - - const contextMenuShow = Menu.buildFromTemplate([{ - label: i18n.__('Show'), - click () { - mainWindow.show(); - } - }, { - label: i18n.__('Quit'), - click () { - remote.app.quit(); - } - }]); - - const contextMenuHide = Menu.buildFromTemplate([{ - label: i18n.__('Hide'), - click () { - mainWindow.hide(); - } - }, { - label: i18n.__('Quit'), - click () { - remote.app.quit(); - } - }]); - - if (!mainWindow.isMinimized() && !mainWindow.isVisible()) { - _tray.setContextMenu(contextMenuShow); - } else { - _tray.setContextMenu(contextMenuHide); - } - - const onShow = function () { - _tray.setContextMenu(contextMenuHide); - }; - - const onHide = function () { - _tray.setContextMenu(contextMenuShow); - }; - - mainWindow.on('show', onShow); - mainWindow.on('restore', onShow); - - mainWindow.on('hide', onHide); - mainWindow.on('minimize', onHide); - - _tray.setToolTip(remote.app.getName()); - - _tray.on('right-click', function (e, b) { - _tray.popUpContextMenu(undefined, b); - }); - - _tray.on('click', () => { - if (mainWindow.isVisible()) { - return mainWindow.hide(); - } - - mainWindow.show(); - }); - - mainWindow.destroyTray = function () { - mainWindow.removeListener('show', onShow); - mainWindow.removeListener('hide', onHide); - _tray.destroy(); - }; +function createAppTray() { + const _tray = new Tray(getTrayImagePath({ title:'', count:0, showAlert:false })); + mainWindow.tray = _tray; + + const contextMenuShow = Menu.buildFromTemplate([{ + label: i18n.__('Show'), + click() { + mainWindow.show(); + }, + }, { + label: i18n.__('Quit'), + click() { + remote.app.quit(); + }, + }]); + + const contextMenuHide = Menu.buildFromTemplate([{ + label: i18n.__('Hide'), + click() { + mainWindow.hide(); + }, + }, { + label: i18n.__('Quit'), + click() { + remote.app.quit(); + }, + }]); + + if (!mainWindow.isMinimized() && !mainWindow.isVisible()) { + _tray.setContextMenu(contextMenuShow); + } else { + _tray.setContextMenu(contextMenuHide); + } + + const onShow = function() { + _tray.setContextMenu(contextMenuHide); + }; + + const onHide = function() { + _tray.setContextMenu(contextMenuShow); + }; + + mainWindow.on('show', onShow); + mainWindow.on('restore', onShow); + + mainWindow.on('hide', onHide); + mainWindow.on('minimize', onHide); + + _tray.setToolTip(remote.app.getName()); + + _tray.on('right-click', function(e, b) { + _tray.popUpContextMenu(undefined, b); + }); + + _tray.on('click', () => { + if (mainWindow.isVisible()) { + return mainWindow.hide(); + } + + mainWindow.show(); + }); + + mainWindow.destroyTray = function() { + mainWindow.removeListener('show', onShow); + mainWindow.removeListener('hide', onHide); + _tray.destroy(); + }; } -function showTrayAlert (badge, status = 'online') { - if (mainWindow.tray === null || mainWindow.tray === undefined) { - return; - } - - const trayDisplayed = localStorage.getItem('hideTray') !== 'true'; - const hasMentions = badge.showAlert && badge.count > 0; - - if (!mainWindow.isFocused()) { - mainWindow.flashFrame(hasMentions); - } - - if (process.platform === 'win32') { - if (hasMentions) { - mainWindow.webContents.send('render-taskbar-icon', badge.count); - } else { - mainWindow.setOverlayIcon(null, ''); - } - } - - if (process.platform === 'darwin') { - let countColor = messageCountColor['black']; - if (remote.systemPreferences.isDarkMode()) { - countColor = messageCountColor['white']; - } - - let trayTitle = `${statusBullet[status]}`; - if (hasMentions) { - trayTitle = `${statusBullet[status]} ${countColor}${badge.title}`; - } - remote.app.dock.setBadge(badge.title); - if (trayDisplayed) { - mainWindow.tray.setTitle(trayTitle); - } - } - - if (process.platform === 'linux') { - remote.app.setBadgeCount(badge.count); - } - - if (trayDisplayed) { - mainWindow.tray.setImage(getTrayImagePath(badge)); - } +function showTrayAlert(badge, status = 'online') { + if (mainWindow.tray === null || mainWindow.tray === undefined) { + return; + } + + const trayDisplayed = localStorage.getItem('hideTray') !== 'true'; + const hasMentions = badge.showAlert && badge.count > 0; + + if (!mainWindow.isFocused()) { + mainWindow.flashFrame(hasMentions); + } + + if (process.platform === 'win32') { + if (hasMentions) { + mainWindow.webContents.send('render-taskbar-icon', badge.count); + } else { + mainWindow.setOverlayIcon(null, ''); + } + } + + if (process.platform === 'darwin') { + let countColor = messageCountColor.black; + if (remote.systemPreferences.isDarkMode()) { + countColor = messageCountColor.white; + } + + let trayTitle = `${ statusBullet[status] }`; + if (hasMentions) { + trayTitle = `${ statusBullet[status] } ${ countColor }${ badge.title }`; + } + remote.app.dock.setBadge(badge.title); + if (trayDisplayed) { + mainWindow.tray.setTitle(trayTitle); + } + } + + if (process.platform === 'linux') { + remote.app.setBadgeCount(badge.count); + } + + if (trayDisplayed) { + mainWindow.tray.setImage(getTrayImagePath(badge)); + } } -function removeAppTray () { - mainWindow.destroyTray(); +function removeAppTray() { + mainWindow.destroyTray(); } -function toggle () { - if (localStorage.getItem('hideTray') === 'true') { - createAppTray(); - localStorage.setItem('hideTray', 'false'); - } else { - removeAppTray(); - localStorage.setItem('hideTray', 'true'); - } +function toggle() { + if (localStorage.getItem('hideTray') === 'true') { + createAppTray(); + localStorage.setItem('hideTray', 'false'); + } else { + removeAppTray(); + localStorage.setItem('hideTray', 'true'); + } } if (localStorage.getItem('hideTray') !== 'true') { - createAppTray(); + createAppTray(); } export default { - showTrayAlert, - toggle + showTrayAlert, + toggle, }; diff --git a/src/scripts/webview.js b/src/scripts/webview.js index 0a2931003c..f549be4f37 100644 --- a/src/scripts/webview.js +++ b/src/scripts/webview.js @@ -2,212 +2,212 @@ import { EventEmitter } from 'events'; import servers from './servers'; import sidebar from './sidebar'; import tray from './tray'; -import { desktopCapturer, ipcRenderer, shell } from 'electron'; +import { desktopCapturer, ipcRenderer } from 'electron'; class WebView extends EventEmitter { - constructor () { - super(); + constructor() { + super(); - this.webviewParentElement = document.body; + this.webviewParentElement = document.body; - servers.forEach((host) => { - this.add(host); - }); + servers.forEach((host) => { + this.add(host); + }); - servers.on('host-added', (hostUrl) => { - this.add(servers.get(hostUrl)); - }); + servers.on('host-added', (hostUrl) => { + this.add(servers.get(hostUrl)); + }); - servers.on('host-removed', (hostUrl) => { - this.remove(hostUrl); - }); + servers.on('host-removed', (hostUrl) => { + this.remove(hostUrl); + }); - servers.on('active-setted', (hostUrl) => { - this.setActive(hostUrl); - }); + servers.on('active-setted', (hostUrl) => { + this.setActive(hostUrl); + }); - servers.on('active-cleared', (hostUrl) => { - this.deactiveAll(hostUrl); - }); + servers.on('active-cleared', (hostUrl) => { + this.deactiveAll(hostUrl); + }); - servers.once('loaded', () => { - this.loaded(); - }); + servers.once('loaded', () => { + this.loaded(); + }); - ipcRenderer.on('screenshare-result', (e, result) => { - const webviewObj = this.getActive(); - webviewObj.executeJavaScript(` + ipcRenderer.on('screenshare-result', (e, result) => { + const webviewObj = this.getActive(); + webviewObj.executeJavaScript(` window.parent.postMessage({ - sourceId: '${result}' + sourceId: '${ result }' }, '*') `); - }); - } - - loaded () { - document.querySelector('#loading').style.display = 'none'; - document.querySelector('#login-card').style.display = 'block'; - document.querySelector('footer').style.display = 'block'; - } - - loading () { - document.querySelector('#loading').style.display = 'block'; - document.querySelector('#login-card').style.display = 'none'; - document.querySelector('footer').style.display = 'none'; - } - - add (host) { - let webviewObj = this.getByUrl(host.url); - if (webviewObj) { - return; - } - - webviewObj = document.createElement('webview'); - webviewObj.setAttribute('server', host.url); - webviewObj.setAttribute('preload', './preload.js'); - webviewObj.setAttribute('allowpopups', 'on'); - webviewObj.setAttribute('disablewebsecurity', 'on'); - - webviewObj.addEventListener('did-navigate-in-page', (lastPath) => { - if ((lastPath.url).includes(host.url)) { - this.saveLastPath(host.url, lastPath.url); - } - }); - - webviewObj.addEventListener('console-message', (e) => { - console.log('webview:', e.message); - }); - - webviewObj.addEventListener('ipc-message', (event) => { - this.emit('ipc-message-'+event.channel, host.url, event.args); - - switch (event.channel) { - case 'title-changed': - servers.setHostTitle(host.url, event.args[0]); - break; - case 'unread-changed': - sidebar.setBadge(host.url, event.args[0]); - break; - case 'focus': - servers.setActive(host.url); - break; - case 'user-status-manually-set': - const badge = sidebar.getGlobalBadge(); - tray.showTrayAlert(badge, event.args[0]); - break; - case 'get-sourceId': - desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => { - if (error) { - throw error; - } - - sources = sources.map(source => { - source.thumbnail = source.thumbnail.toDataURL(); - return source; - }); - ipcRenderer.send('screenshare', sources); - }); - break; - case 'reload-server': - const active = this.getActive(); - const server = active.getAttribute('server'); - this.loading(); - active.loadURL(server); - break; - case 'sidebar-background': - sidebar.changeSidebarColor(event.args[0]); - break; - } - }); - - webviewObj.addEventListener('dom-ready', () => { - this.emit('dom-ready', host.url); - }); - - webviewObj.addEventListener('did-fail-load', (e) => { - if (e.isMainFrame) { - webviewObj.loadURL('file://' + __dirname + '/loading-error.html'); - } - }); - - webviewObj.addEventListener('did-get-response-details', (e) => { - if (e.resourceType === 'mainFrame' && e.httpResponseCode >= 500) { - webviewObj.loadURL('file://' + __dirname + '/loading-error.html'); - } - }); - - this.webviewParentElement.appendChild(webviewObj); - - webviewObj.src = host.lastPath || host.url; - } - - remove (hostUrl) { - const el = this.getByUrl(hostUrl); - if (el) { - el.remove(); - } - } - - saveLastPath (hostUrl, lastPathUrl) { - const hosts = servers.hosts; - hosts[hostUrl].lastPath = lastPathUrl; - servers.hosts = hosts; - } - - getByUrl (hostUrl) { - return this.webviewParentElement.querySelector(`webview[server="${hostUrl}"]`); - } - - getActive () { - return document.querySelector('webview.active'); - } - - isActive (hostUrl) { - return !!this.webviewParentElement.querySelector(`webview.active[server="${hostUrl}"]`); - } - - deactiveAll () { - let item; - while (!(item = this.getActive()) === false) { - item.classList.remove('active'); - } - document.querySelector('.landing-page').classList.add('hide'); - } - - showLanding () { - this.loaded(); - document.querySelector('.landing-page').classList.remove('hide'); - } - - setActive (hostUrl) { - if (this.isActive(hostUrl)) { - return; - } - - this.deactiveAll(); - const item = this.getByUrl(hostUrl); - if (item) { - item.classList.add('active'); - } - this.focusActive(); - } - - focusActive () { - const active = this.getActive(); - if (active) { - active.focus(); - return true; - } - return false; - } - - goBack () { - this.getActive().goBack(); - } - - goForward () { - this.getActive().goForward(); - } + }); + } + + loaded() { + document.querySelector('#loading').style.display = 'none'; + document.querySelector('#login-card').style.display = 'block'; + document.querySelector('footer').style.display = 'block'; + } + + loading() { + document.querySelector('#loading').style.display = 'block'; + document.querySelector('#login-card').style.display = 'none'; + document.querySelector('footer').style.display = 'none'; + } + + add(host) { + let webviewObj = this.getByUrl(host.url); + if (webviewObj) { + return; + } + + webviewObj = document.createElement('webview'); + webviewObj.setAttribute('server', host.url); + webviewObj.setAttribute('preload', './preload.js'); + webviewObj.setAttribute('allowpopups', 'on'); + webviewObj.setAttribute('disablewebsecurity', 'on'); + + webviewObj.addEventListener('did-navigate-in-page', (lastPath) => { + if ((lastPath.url).includes(host.url)) { + this.saveLastPath(host.url, lastPath.url); + } + }); + + webviewObj.addEventListener('console-message', (e) => { + console.log('webview:', e.message); + }); + + webviewObj.addEventListener('ipc-message', (event) => { + this.emit(`ipc-message-${ event.channel }`, host.url, event.args); + + switch (event.channel) { + case 'title-changed': + servers.setHostTitle(host.url, event.args[0]); + break; + case 'unread-changed': + sidebar.setBadge(host.url, event.args[0]); + break; + case 'focus': + servers.setActive(host.url); + break; + case 'user-status-manually-set': + const badge = sidebar.getGlobalBadge(); + tray.showTrayAlert(badge, event.args[0]); + break; + case 'get-sourceId': + desktopCapturer.getSources({ types: ['window', 'screen'] }, (error, sources) => { + if (error) { + throw error; + } + + sources = sources.map((source) => { + source.thumbnail = source.thumbnail.toDataURL(); + return source; + }); + ipcRenderer.send('screenshare', sources); + }); + break; + case 'reload-server': + const active = this.getActive(); + const server = active.getAttribute('server'); + this.loading(); + active.loadURL(server); + break; + case 'sidebar-background': + sidebar.changeSidebarColor(event.args[0]); + break; + } + }); + + webviewObj.addEventListener('dom-ready', () => { + this.emit('dom-ready', host.url); + }); + + webviewObj.addEventListener('did-fail-load', (e) => { + if (e.isMainFrame) { + webviewObj.loadURL(`file://${ __dirname }/loading-error.html`); + } + }); + + webviewObj.addEventListener('did-get-response-details', (e) => { + if (e.resourceType === 'mainFrame' && e.httpResponseCode >= 500) { + webviewObj.loadURL(`file://${ __dirname }/loading-error.html`); + } + }); + + this.webviewParentElement.appendChild(webviewObj); + + webviewObj.src = host.lastPath || host.url; + } + + remove(hostUrl) { + const el = this.getByUrl(hostUrl); + if (el) { + el.remove(); + } + } + + saveLastPath(hostUrl, lastPathUrl) { + const { hosts } = servers; + hosts[hostUrl].lastPath = lastPathUrl; + servers.hosts = hosts; + } + + getByUrl(hostUrl) { + return this.webviewParentElement.querySelector(`webview[server="${ hostUrl }"]`); + } + + getActive() { + return document.querySelector('webview.active'); + } + + isActive(hostUrl) { + return !!this.webviewParentElement.querySelector(`webview.active[server="${ hostUrl }"]`); + } + + deactiveAll() { + let item; + while (!(item = this.getActive()) === false) { + item.classList.remove('active'); + } + document.querySelector('.landing-page').classList.add('hide'); + } + + showLanding() { + this.loaded(); + document.querySelector('.landing-page').classList.remove('hide'); + } + + setActive(hostUrl) { + if (this.isActive(hostUrl)) { + return; + } + + this.deactiveAll(); + const item = this.getByUrl(hostUrl); + if (item) { + item.classList.add('active'); + } + this.focusActive(); + } + + focusActive() { + const active = this.getActive(); + if (active) { + active.focus(); + return true; + } + return false; + } + + goBack() { + this.getActive().goBack(); + } + + goForward() { + this.getActive().goForward(); + } } export default new WebView(); diff --git a/tasks/build-app.js b/tasks/build-app.js index d00913b530..75cf343d02 100644 --- a/tasks/build-app.js +++ b/tasks/build-app.js @@ -10,55 +10,45 @@ const bundle = require('./bundle'); const utils = require('./utils'); const { beepSound, srcDir, configDir, appDir } = require('./utils'); -gulp.task('public', () => { - return gulp.src(srcDir.path('public/**/*')) - .pipe(plumber()) - .pipe(gulp.dest(appDir.path('public'))); -}); +gulp.task('public', () => gulp.src(srcDir.path('public/**/*')) + .pipe(plumber()) + .pipe(gulp.dest(appDir.path('public')))); -gulp.task('i18n', () => { - return gulp.src(srcDir.path('i18n/lang/**/*')) - .pipe(plumber()) - .pipe(gulp.dest(appDir.path('i18n/lang'))); -}); +gulp.task('i18n', () => gulp.src(srcDir.path('i18n/lang/**/*')) + .pipe(plumber()) + .pipe(gulp.dest(appDir.path('i18n/lang')))); -gulp.task('bundle', () => { - return Promise.all([ - bundle(srcDir.path('background.js'), appDir.path('background.js')), - bundle(srcDir.path('app.js'), appDir.path('app.js')), - bundle(srcDir.path('i18n/index.js'), appDir.path('i18n/index.js')) - ]); -}); +gulp.task('bundle', () => Promise.all([ + bundle(srcDir.path('background.js'), appDir.path('background.js')), + bundle(srcDir.path('app.js'), appDir.path('app.js')), + bundle(srcDir.path('i18n/index.js'), appDir.path('i18n/index.js')), +])); -gulp.task('less', () => { - return gulp.src(srcDir.path('stylesheets/main.less')) - .pipe(plumber()) - .pipe(less()) - .pipe(gulp.dest(appDir.path('stylesheets'))); -}); +gulp.task('less', () => gulp.src(srcDir.path('stylesheets/main.less')) + .pipe(plumber()) + .pipe(less()) + .pipe(gulp.dest(appDir.path('stylesheets')))); -gulp.task('environment', () => { - return gulp.src(configDir.path(`env_${ utils.getEnvName() }.json`)) - .pipe(plumber()) - .pipe(rename('env.json')) - .pipe(gulp.dest(appDir.path('.'))); -}); +gulp.task('environment', () => gulp.src(configDir.path(`env_${ utils.getEnvName() }.json`)) + .pipe(plumber()) + .pipe(rename('env.json')) + .pipe(gulp.dest(appDir.path('.')))); -gulp.task('build-app', [ 'public', 'i18n', 'bundle', 'less', 'environment' ]); +gulp.task('build-app', ['public', 'i18n', 'bundle', 'less', 'environment']); gulp.task('watch', () => { - const runOnChanges = taskName => batch((event, done) => { - gulp.start(taskName, err => { - if (err) { - beepSound(); - } - done(err); - }); - }); - - watch(srcDir.path('public/**/*'), runOnChanges('public')); - watch(srcDir.path('i18n/lang/**/*'), runOnChanges('i18n')); - watch(srcDir.path('**/*.js'), runOnChanges('bundle')); - watch(srcDir.path('**/*.less'), runOnChanges('less')); - watch(configDir.path('**/*'), runOnChanges('environment')); + const runOnChanges = (taskName) => batch((event, done) => { + gulp.start(taskName, (err) => { + if (err) { + beepSound(); + } + done(err); + }); + }); + + watch(srcDir.path('public/**/*'), runOnChanges('public')); + watch(srcDir.path('i18n/lang/**/*'), runOnChanges('i18n')); + watch(srcDir.path('**/*.js'), runOnChanges('bundle')); + watch(srcDir.path('**/*.less'), runOnChanges('less')); + watch(configDir.path('**/*'), runOnChanges('environment')); }); diff --git a/tasks/build-tests.js b/tasks/build-tests.js index ddde2d1c01..1853b2166f 100644 --- a/tasks/build-tests.js +++ b/tasks/build-tests.js @@ -5,30 +5,32 @@ const jetpack = require('fs-jetpack'); const bundle = require('./bundle'); const istanbul = require('rollup-plugin-istanbul'); -const createEntryFile = (srcDir, matching, outputDir, entryFileName, rollupOptions) => { - return srcDir.findAsync('.', { matching }) - .then(specPaths => specPaths.map(path => `import './${ path.replace(/\\/g, '/') }';`)) - .then(imports => ( - "// This file is generated automatically.\n" + - "// All modifications will be lost.\n" + - imports.join('\n') - )) - .then(entryFileContent => srcDir.writeAsync(entryFileName, entryFileContent)) - .then(() => bundle(srcDir.path(entryFileName), outputDir.path(entryFileName), rollupOptions)) - .then(() => srcDir.remove(entryFileName)); +const createEntryFile = async(srcDir, matching, outputDir, entryFileName, rollupOptions) => { + const entryFileContent = srcDir.find('.', { matching }) + .map((path) => `import './${ path.replace(/\\/g, '/') }';`) + .join('\n'); + + srcDir.write(entryFileName, entryFileContent); + + await bundle(srcDir.path(entryFileName), outputDir.path(entryFileName), rollupOptions); + + srcDir.remove(entryFileName); }; -gulp.task('build-unit-tests', [ 'environment' ], () => { - return createEntryFile(jetpack.cwd('src'), '*.spec.js', jetpack.cwd('app'), 'specs.js.autogenerated', { - rollupPlugins: [ - istanbul({ - exclude: ['**/*.spec.js', '**/specs.js.autogenerated'], - sourcemap: true - }) - ] - }); -}); +gulp.task('build-unit-tests', ['environment'], () => createEntryFile( + jetpack.cwd('src'), '*.spec.js', + jetpack.cwd('app'), 'specs.js.autogenerated', + { + rollupPlugins: [ + istanbul({ + exclude: ['**/*.spec.js', '**/specs.js.autogenerated'], + sourcemap: true, + }), + ], + } +)); -gulp.task('build-e2e-tests', [ 'build-app' ], () => { - return createEntryFile(jetpack.cwd('e2e'), '*.e2e.js', jetpack.cwd('app'), 'e2e.js.autogenerated'); -}); +gulp.task('build-e2e-tests', ['build-app'], () => createEntryFile( + jetpack.cwd('src'), '*.e2e.js', + jetpack.cwd('app'), 'e2e.js.autogenerated' +)); diff --git a/tasks/bundle.js b/tasks/bundle.js index 7c9db29bcc..6b5d666a4b 100644 --- a/tasks/bundle.js +++ b/tasks/bundle.js @@ -1,60 +1,46 @@ 'use strict'; const path = require('path'); -const jetpack = require('fs-jetpack'); -const rollup = require('rollup').rollup; +const { rollup } = require('rollup'); const rollupJson = require('rollup-plugin-json'); +const appManifest = require('../package.json'); -const nodeBuiltInModules = ['assert', 'buffer', 'child_process', 'cluster', - 'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events', - 'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'process', 'punycode', - 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers', - 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib']; +const nodeBuiltInModules = ['assert', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto', 'dgram', + 'dns', 'domain', 'events', 'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'process', 'punycode', 'querystring', + 'readline', 'repl', 'stream', 'string_decoder', 'timers', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib']; const electronBuiltInModules = ['electron']; -const generateExternalModulesList = function () { - const appManifest = jetpack.read('./package.json', 'json'); - return [].concat( - nodeBuiltInModules, - electronBuiltInModules, - Object.keys(appManifest.dependencies), - Object.keys(appManifest.devDependencies) - ); -}; +const externalModulesList = [ + ...nodeBuiltInModules, + ...electronBuiltInModules, + ...Object.keys(appManifest.dependencies), + ...Object.keys(appManifest.devDependencies), +]; const cached = {}; -module.exports = function (src, dest, opts) { - opts = opts || {}; - opts.rollupPlugins = opts.rollupPlugins || []; - return rollup({ - input: src, - external: generateExternalModulesList(), - cache: cached[src], - plugins: [].concat( - opts.rollupPlugins, - rollupJson() - ), - }) - .then(function (bundle) { - cached[src] = bundle; - - const jsFile = path.basename(dest); - return bundle.generate({ - format: 'cjs', - sourcemap: true, - sourcemapFile: jsFile, - }); - }) - .then(function (result) { - // Wrap code in self invoking function so the variables don't - // pollute the global namespace. - const isolatedCode = '(function () {' + result.code + '\n}());'; - const jsFile = path.basename(dest); - return Promise.all([ - jetpack.writeAsync(dest, isolatedCode + '\n//# sourcemappingURL=' + jsFile + '.map'), - jetpack.writeAsync(dest + '.map', result.map.toString()), - ]); - }); +module.exports = async(src, dest, opts = {}) => { + const inputOptions = { + input: src, + external: externalModulesList, + cache: cached[src], + plugins: [ + ...(opts.rollupPlugins || []), + rollupJson(), + ], + }; + + const outputOptions = { + format: 'cjs', + file: dest, + intro: '(function () {', + outro: '})()', + sourcemap: true, + sourcemapFile: path.basename(dest), + }; + + const bundle = await rollup(inputOptions); + cached[src] = bundle; + await bundle.write(outputOptions); }; diff --git a/tasks/release.js b/tasks/release.js index c95d312e26..24daef28e8 100644 --- a/tasks/release.js +++ b/tasks/release.js @@ -8,16 +8,11 @@ const { getEnvName } = require('./utils'); const publish = getEnvName() !== 'production' ? 'never' : 'onTagOrDraft'; gulp.task('release:darwin', () => build({ publish, x64: true, mac: [] })); -gulp.task('release:win32', () => build({ publish, x64: true, ia32: true, win: [ 'nsis', 'appx' ] })); -gulp.task('release:linux', (cb) => { - build({ publish, x64: true, linux: [], c: { productName: 'rocketchat' } }) - .then(() => build({ - publish, - ia32: true, - linux: config.linux.target.filter(target => target !== 'snap'), - c: { productName: 'rocketchat' } - })) - .then(() => cb(), (error) => cb(error)); +gulp.task('release:win32', () => build({ publish, x64: true, ia32: true, win: ['nsis', 'appx'] })); +gulp.task('release:linux', async() => { + const allLinuxTargetsButSnap = config.linux.target.filter((target) => target !== 'snap'); + await build({ publish, x64: true, linux: [], c: { productName: 'rocketchat' } }); + await build({ publish, ia32: true, linux: allLinuxTargetsButSnap, c: { productName: 'rocketchat' } }); }); gulp.task('release', (cb) => runSequence('build-app', `release:${ process.platform }`, cb)); diff --git a/tasks/start.js b/tasks/start.js index 6542c062af..89a64c0b88 100644 --- a/tasks/start.js +++ b/tasks/start.js @@ -4,7 +4,7 @@ const gulp = require('gulp'); const childProcess = require('child_process'); const electron = require('electron'); -gulp.task('start', [ 'build-app', 'watch' ], () => { - childProcess.spawn(electron, [ '.' ], { stdio: 'inherit' }) - .on('close', () => process.exit()); +gulp.task('start', ['build-app', 'watch'], () => { + childProcess.spawn(electron, ['.'], { stdio: 'inherit' }) + .on('close', () => process.exit()); }); diff --git a/yarn.lock b/yarn.lock index 325d2d7b23..916409455f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27,6 +27,12 @@ bindings "~1.2.1" nan "^2.0.0" +"@rocket.chat/eslint-config@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@rocket.chat/eslint-config/-/eslint-config-0.1.2.tgz#f9c5041b8a0e849de9eb4013e4e3efcbe9951d0a" + dependencies: + eslint-plugin-import "^2.14.0" + "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" @@ -1115,6 +1121,10 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control- version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + conventional-changelog-angular@^1.6.6: version "1.6.6" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz#b27f2b315c16d0a1f23eb181309d0e6a4698ea0f" @@ -1372,7 +1382,7 @@ dateformat@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" -debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: +debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1532,6 +1542,13 @@ dmg-builder@5.3.1: parse-color "^1.0.0" sanitize-filename "^1.6.1" +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -1781,6 +1798,35 @@ escodegen@1.8.x: optionalDependencies: source-map "~0.2.0" +eslint-import-resolver-node@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" + dependencies: + debug "^2.6.9" + resolve "^1.5.0" + +eslint-module-utils@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-import@^2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz#6b17626d2e3e6ad52cfce8807a845d15e22111a8" + dependencies: + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.2.0" + has "^1.0.1" + lodash "^4.17.4" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + resolve "^1.6.0" + eslint-scope@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" @@ -2295,6 +2341,10 @@ fstream@^1.0.0, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" @@ -2774,6 +2824,12 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + dependencies: + function-bind "^1.1.1" + he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -3241,7 +3297,7 @@ isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -3532,6 +3588,15 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -3940,7 +4005,7 @@ mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -4789,6 +4854,12 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -4825,6 +4896,12 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + plist@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-2.1.0.tgz#57ccdb7a0821df21831217a3cad54e3e146a1025" @@ -5098,6 +5175,13 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -5113,6 +5197,14 @@ read-pkg@^1.0.0, read-pkg@^1.1.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -5329,7 +5421,7 @@ resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.5.0: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.5.0, resolve@^1.6.0: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" dependencies: