diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..4a002ec5 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +dist/ +node_modules/ +release/ +src/node_modules/ diff --git a/.eslintrc-platform-v3.4.0.json b/.eslintrc-platform-v3.4.0.json new file mode 100644 index 00000000..c5c100e6 --- /dev/null +++ b/.eslintrc-platform-v3.4.0.json @@ -0,0 +1,240 @@ +{ + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "impliedStrict": true, + "modules": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "env": { + "browser": true, + "node": true, + "jquery": true, + "es6": true + }, + "globals": { + "jest": true, + "describe": true, + "it": true, + "expect": true, + "before": true, + "after": true + }, + "rules": { + "array-bracket-spacing": [2, "never"], + "array-callback-return": 2, + "arrow-body-style": 0, + "arrow-parens": [2, "always"], + "arrow-spacing": [2, { "before": true, "after": true }], + "block-scoped-var": 2, + "brace-style": [2, "1tbs", { "allowSingleLine": false }], + "camelcase": [2, {"properties": "never"}], + "comma-dangle": [2, "never"], + "comma-spacing": [2, {"before": false, "after": true}], + "comma-style": [2, "last"], + "complexity": [1, 10], + "computed-property-spacing": [2, "never"], + "consistent-return": 2, + "consistent-this": [2, "self"], + "constructor-super": 2, + "curly": [2, "all"], + "dot-location": [2, "object"], + "dot-notation": 2, + "eqeqeq": [2, "smart"], + "func-names": 2, + "func-style": [2, "declaration"], + "generator-star-spacing": [2, {"before": false, "after": true}], + "global-require": 2, + "guard-for-in": 2, + "id-blacklist": 0, + "indent": [2, 4, {"SwitchCase": 0}], + "jsx-quotes": [2, "prefer-single"], + "key-spacing": [2, {"beforeColon": false, "afterColon": true}], + "keyword-spacing": [2, {"before": true, "after": true, "overrides": {}}], + "linebreak-style": 2, + "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }], + "max-lines": [1, {"max": 450, "skipBlankLines": true, "skipComments": false}], + "max-nested-callbacks": [1, {"max":1}], + "max-nested-callbacks": [2, {"max":2}], + "max-statements-per-line": [2, {"max": 1}], + "new-cap": 2, + "new-parens": 2, + "newline-before-return": 0, + "newline-per-chained-call": 0, + "no-alert": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-case-declarations": 2, + "no-class-assign": 2, + "no-cond-assign": [2, "except-parens"], + "no-confusing-arrow": 2, + "no-console": 2, + "no-const-assign": 2, + "no-constant-condition": 2, + "no-debugger": 2, + "no-div-regex": 2, + "no-dupe-args": 2, + "no-dupe-class-members": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-duplicate-imports": [2, {"includeExports": true}], + "no-else-return": 2, + "no-empty": 2, + "no-empty-function": 2, + "no-empty-pattern": 2, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-label": 2, + "no-extra-parens": 0, + "no-extra-semi": 2, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implicit-globals": 0, + "no-implied-eval": 2, + "no-inner-declarations": 0, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-lonely-if": 2, + "no-loop-func": 2, + "no-magic-numbers": [1, { "ignore": [-1, 0, 1, 2], "enforceConst": true, "detectObjects": true } ], + "no-mixed-operators": [2, {"allowSamePrecedence": false}], + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": [2, { "exceptions": { "Property": false } }], + "no-multi-str": 0, + "no-multiple-empty-lines": [2, {"max": 1}], + "no-native-reassign": 2, + "no-negated-condition": 2, + "no-nested-ternary": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-symbol": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-param-reassign": 2, + "no-process-env": 2, + "no-process-exit": 2, + "no-proto": 2, + "no-redeclare": 2, + "no-return-assign": [2, "always"], + "no-script-url": 2, + "no-self-assign": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": [2, {"hoist": "functions"}], + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-ternary": 0, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": [2, { "skipBlankLines": false }], + "no-undef-init": 2, + "no-undefined": 2, + "no-underscore-dangle": 2, + "no-unexpected-multiline": 2, + "no-unmodified-loop-condition": 2, + "no-unneeded-ternary": [2, {"defaultAssignment": false}], + "no-unreachable": 2, + "no-unsafe-finally": 2, + "no-unused-expressions": 2, + "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], + "no-use-before-define": [2, "nofunc"], + "no-useless-computed-key": 2, + "no-useless-concat": 2, + "no-useless-constructor": 2, + "no-useless-escape": 2, + "no-useless-rename": 2, + "no-var": 0, + "no-void": 2, + "no-warning-comments": 1, + "no-whitespace-before-property": 2, + "no-with": 2, + "object-curly-newline": 0, + "object-curly-spacing": [2, "never"], + "object-property-newline": [2, {"allowMultiplePropertiesPerLine": true}], + "object-shorthand": [2, "always"], + "one-var": [2, "never"], + "one-var-declaration-per-line": 0, + "operator-linebreak": [2, "after"], + "padded-blocks": [2, "never"], + "prefer-arrow-callback": 2, + "prefer-const": 2, + "prefer-reflect": 2, + "prefer-rest-params": 2, + "prefer-spread": 2, + "prefer-template": 0, + "quote-props": [2, "as-needed"], + "quotes": [2, "single", "avoid-escape"], + "radix": 2, + "react/display-name": [2, { "ignoreTranspilerName": false }], + "react/jsx-boolean-value": [2, "always"], + "react/jsx-closing-bracket-location": [2, { "location": "tag-aligned" }], + "react/jsx-curly-spacing": [2, "never"], + "react/jsx-equals-spacing": [2, "never"], + "react/jsx-first-prop-new-line": [2, "multiline"], + "react/jsx-handler-names": 0, + "react/jsx-indent": [2, 4], + "react/jsx-indent-props": [2, 4], + "react/jsx-key": 2, + "react/jsx-max-props-per-line": [2, { "maximum": 1 }], + "react/jsx-no-bind": 0, + "react/jsx-no-duplicate-props": [2, { "ignoreCase": false }], + "react/jsx-no-literals": 2, + "react/jsx-no-target-blank": 2, + "react/jsx-no-undef": 2, + "react/jsx-pascal-case": 2, + "react/jsx-filename-extension": 2, + "react/jsx-space-before-closing": [2, "never"], + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/no-comment-textnodes": 2, + "react/no-danger": 0, + "react/no-deprecated": 2, + "react/no-did-mount-set-state": 2, + "react/no-did-update-set-state": 2, + "react/no-direct-mutation-state": 2, + "react/no-is-mounted": 2, + "react/no-multi-comp": [2, { "ignoreStateless": true }], + "react/no-render-return-value": 2, + "react/no-set-state": 0, + "react/no-string-refs": 0, + "react/no-unknown-property": 2, + "react/prefer-es6-class": 2, + "react/prefer-stateless-function": 0, + "react/prop-types": 2, + "react/require-optimization": 1, + "react/require-render-return": 2, + "react/self-closing-comp": 2, + "react/sort-comp": 0, + "react/wrap-multilines": 2, + "require-yield": 2, + "rest-spread-spacing": [2, "never"], + "semi": [2, "always"], + "semi-spacing": [2, {"before": false, "after": true}], + "sort-imports": 0, + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "never"], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "template-curly-spacing": [2, "never"], + "valid-typeof": 2, + "vars-on-top": 0, + "wrap-iife": [2, "outside"], + "wrap-regex": 2, + "yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}] + } +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..6fecbe88 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": "./.eslintrc-platform-v3.4.0.json", + "rules": { + "global-require": 1, + "indent": [2, 2, {"SwitchCase": 0}], + "no-console": 0, + "no-eval": 1, + "no-process-env": 0, + "no-native-reassign": ["error", {"exceptions": ["Notification"]}], + "no-underscore-dangle": 1, + "react/jsx-boolean-value": [1, "always"], + "react/jsx-indent": [2, 2], + "react/jsx-indent-props": [2, 2], + "react/no-multi-comp": 1, + "react/prefer-es6-class": 1, + "react/prop-types": 1 + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3fe99f64..5eb455be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ Please see http://www.mattermost.org/feature-requests/ . ## Pull request Pull requests are welcome. Thank you for your great work! -1. When you edit the code, please run `npm run prettify` to format your code before `git commit`. +1. When you edit the code, please confirm `npm test` successfully finishes. 2. Please update `CHANGELOG.md` and `docs/*.md` if it's necessary. 3. In the description of your pull request, please include: * Operating System version on which you tested diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 4b1c4472..71bcabd6 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -Before submitting, please confirm you've +Before submitting, please confirm you've - [ ] read and understood our [Contributing Guidelines](https://github.com/mattermost/desktop/blob/master/CONTRIBUTING.md) - [ ] completed [Mattermost Contributor Agreement](http://www.mattermost.org/mattermost-contributor-agreement/) - [ ] executed `npm run prettify` for proper code formatting diff --git a/docs/development.md b/docs/development.md index b4b0c9a4..37c884da 100644 --- a/docs/development.md +++ b/docs/development.md @@ -62,7 +62,7 @@ $ npm run test:app Test coding style: ``` -$ npm run test:code +$ npm run lint:js ``` ### Helper commmands diff --git a/gulpfile.js b/gulpfile.js index 0786734f..aa437a27 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,9 +3,6 @@ var gulp = require('gulp'); var prettify = require('gulp-jsbeautifier'); var diff = require('gulp-diff'); -var esformatter = require('gulp-esformatter'); -var esformatter_origin = require('esformatter'); -var through = require('through2'); var electron = require('electron-connect').server.create({ path: './dist' }); @@ -18,10 +15,10 @@ const distPackageAuthor = 'Mattermost, Inc.'; var sources = ['**/*.js', '**/*.json', '**/*.css', '**/*.html', '!**/node_modules/**', '!dist/**', '!release/**', '!**/test_config.json']; -gulp.task('prettify', ['prettify:sources', 'prettify:jsx']); -gulp.task('prettify:verify', ['prettify:sources:verify', 'prettify:jsx:verify']); +gulp.task('prettify', ['prettify:sources']); +gulp.task('prettify:verify', ['prettify:sources:verify']); -var prettify_options = { +var prettifyOptions = { html: { indent_size: 2, end_with_newline: true @@ -32,62 +29,35 @@ var prettify_options = { }, js: { indent_size: 2, - brace_style: "end-expand", + brace_style: 'end-expand', end_with_newline: true } }; -gulp.task('prettify:sources', ['sync-meta'], function() { - return gulp.src(sources) - .pipe(prettify(prettify_options)) - .pipe(prettify.reporter()) - .pipe(diff()) - .pipe(diff.reporter({ +gulp.task('prettify:sources', ['sync-meta'], () => { + return gulp.src(sources). + pipe(prettify(prettifyOptions)). + pipe(prettify.reporter()). + pipe(diff()). + pipe(diff.reporter({ quiet: true, fail: false - })) - .pipe(gulp.dest('.')); + })). + pipe(gulp.dest('.')); }); -gulp.task('prettify:sources:verify', function() { - return gulp.src(sources) - .pipe(prettify(prettify_options)) - .pipe(prettify.reporter()) - .pipe(diff()) - .pipe(diff.reporter({ +gulp.task('prettify:sources:verify', () => { + return gulp.src(sources). + pipe(prettify(prettifyOptions)). + pipe(prettify.reporter()). + pipe(diff()). + pipe(diff.reporter({ quiet: true, fail: true })); }); - -var esformatter_jsx_options = { - indent: { - value: ' ' - }, - plugins: ['esformatter-jsx'] -}; - -gulp.task('prettify:jsx', function() { - return gulp.src('src/browser/**/*.jsx') - .pipe(esformatter(esformatter_jsx_options)) - .pipe(gulp.dest('src/browser')); -}); - -gulp.task('prettify:jsx:verify', function() { - return gulp.src('src/browser/**/*.jsx') - .pipe(through.obj(function(file, enc, cb) { - var result = esformatter_origin.diff.unified(file.contents.toString(), esformatter_origin.rc(file.path, esformatter_jsx_options)); - if (result !== "") { - console.log('Error: ' + file.path + ' must be formatted'); - process.exit(1); - } - cb(); - })); -}); - - -gulp.task('build', ['sync-meta', 'copy'], function(cb) { +gulp.task('build', ['sync-meta', 'copy'], (cb) => { const appPackageJson = require('./src/package.json'); const distPackageJson = Object.assign({}, appPackageJson, { author: { @@ -100,23 +70,23 @@ gulp.task('build', ['sync-meta', 'copy'], function(cb) { gulp.task('copy', ['copy:resources', 'copy:html/css', 'copy:modules']); -gulp.task('copy:resources', function() { - return gulp.src('src/resources/**') - .pipe(gulp.dest('dist/resources')); +gulp.task('copy:resources', () => { + return gulp.src('src/resources/**'). + pipe(gulp.dest('dist/resources')); }); -gulp.task('copy:html/css', function() { - return gulp.src(['src/browser/**/*.html', 'src/browser/**/*.css']) - .pipe(gulp.dest('dist/browser')); +gulp.task('copy:html/css', () => { + return gulp.src(['src/browser/**/*.html', 'src/browser/**/*.css']). + pipe(gulp.dest('dist/browser')); }); -gulp.task('copy:modules', function() { - return gulp.src(['src/node_modules/bootstrap/dist/**']) - .pipe(gulp.dest('dist/browser/modules/bootstrap')); +gulp.task('copy:modules', () => { + return gulp.src(['src/node_modules/bootstrap/dist/**']). + pipe(gulp.dest('dist/browser/modules/bootstrap')); }); function spawnWebpack(config, cb) { - const ext = process.platform === 'win32' ? '.cmd' : '' + const ext = process.platform === 'win32' ? '.cmd' : ''; spawn(path.resolve(`./node_modules/.bin/webpack${ext}`), ['--config', config], { stdio: 'inherit' }).on('exit', (code) => { @@ -132,7 +102,7 @@ gulp.task('webpack:renderer', (cb) => { spawnWebpack('webpack.config.renderer.js', cb); }); -gulp.task('watch', ['build', 'webpack:main', 'webpack:renderer'], function() { +gulp.task('watch', ['build', 'webpack:main', 'webpack:renderer'], () => { var options = ['--livereload']; electron.start(options); @@ -140,13 +110,13 @@ gulp.task('watch', ['build', 'webpack:main', 'webpack:renderer'], function() { gulp.watch(['src/browser/**/*.js', 'src/browser/**/*.jsx'], ['webpack:renderer']); gulp.watch(['src/browser/**/*.css', 'src/browser/**/*.html', 'src/resources/**/*.png'], ['copy']); - gulp.watch(['dist/main.js', 'dist/resources/**'], function() { + gulp.watch(['dist/main.js', 'dist/resources/**'], () => { electron.restart(options); }); gulp.watch(['dist/browser/*.js'], electron.reload); }); -gulp.task('sync-meta', function() { +gulp.task('sync-meta', () => { var appPackageJson = require('./src/package.json'); var packageJson = require('./package.json'); appPackageJson.name = packageJson.name; diff --git a/package.json b/package.json index 7b18e615..248af5cc 100644 --- a/package.json +++ b/package.json @@ -25,17 +25,19 @@ "start": "electron dist", "watch": "gulp watch", "serve": "gulp watch", - "test": "npm run build && mocha --reporter mocha-circleci-reporter --recursive test/specs && gulp prettify:verify", + "test": "npm run build && npm run test:app && npm run lint:js", + "test:app": "mocha --reporter mocha-circleci-reporter --recursive test/specs", "package:all": "npm run package:windows && npm run package:mac && npm run package:linux", "package:windows": "build --win --x64 --ia32 --em.name=mattermost && npm run manipulate-windows-zip", "package:mac": "build --mac --x64 --ia32", "package:linux": "build --linux --x64 --ia32 --em.name=mattermost-desktop", "manipulate-windows-zip": "node scripts/manipulate_windows_zip.js", - "prettify": "gulp prettify" + "lint:js": "eslint ." }, "devDependencies": { "7zip-bin": "^2.0.1", "babel-core": "^6.7.5", + "babel-eslint": "^6.1.2", "babel-loader": "^6.2.4", "babel-preset-react": "^6.5.0", "chai": "^3.5.0", @@ -45,8 +47,8 @@ "electron-builder": "^7.11.2", "electron-connect": "~0.6.0", "electron-prebuilt": "1.4.2", - "esformatter": "^0.9.6", - "esformatter-jsx": "^7.0.1", + "eslint": "^3.4.0", + "eslint-plugin-react": "^6.2.0", "gulp": "^3.9.1", "gulp-diff": "^1.0.0", "gulp-esformatter": "^6.0.0", @@ -55,7 +57,6 @@ "mocha": "^3.0.2", "mocha-circleci-reporter": "0.0.2", "spectron": "~3.4.0", - "through2": "^2.0.1", "webpack": "^1.13.1", "webpack-merge": "^0.14.1" }, diff --git a/src/browser/components/loginModal.jsx b/src/browser/components/loginModal.jsx index 4a1c1c0c..1ddb2d51 100644 --- a/src/browser/components/loginModal.jsx +++ b/src/browser/components/loginModal.jsx @@ -11,7 +11,7 @@ const Col = ReactBootstrap.Col; const Button = ReactBootstrap.Button; const LoginModal = React.createClass({ - handleSubmit: function(event) { + handleSubmit(event) { event.preventDefault(); const usernameNode = ReactDOM.findDOMNode(this.refs.username); const passwordNode = ReactDOM.findDOMNode(this.refs.password); @@ -19,7 +19,7 @@ const LoginModal = React.createClass({ usernameNode.value = ''; passwordNode.value = ''; }, - render: function() { + render() { var theServer = ''; if (!this.props.show) { theServer = ''; @@ -30,34 +30,54 @@ const LoginModal = React.createClass({ } const message = `${theServer} requires a username and password.`; return ( - + - Authentication Required + {'Authentication Required'}

{ message }

-
+ - User Name - - + {'User Name'} + + - Password - - + {'Password'} + + - -
- - { ' ' } - -
+ +
+ + { ' ' } + +
diff --git a/src/browser/index.jsx b/src/browser/index.jsx index 2428d97a..29bf0d08 100644 --- a/src/browser/index.jsx +++ b/src/browser/index.jsx @@ -1,8 +1,8 @@ 'use strict'; -window.eval = global.eval = function() { - throw new Error("Sorry, Mattermost does not support window.eval() for security reasons."); -} +window.eval = global.eval = () => { + throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.'); +}; const React = require('react'); const ReactDOM = require('react-dom'); @@ -13,25 +13,34 @@ const Row = ReactBootstrap.Row; const Col = ReactBootstrap.Col; const Nav = ReactBootstrap.Nav; const NavItem = ReactBootstrap.NavItem; -const Badge = ReactBootstrap.Badge; -const ListGroup = ReactBootstrap.ListGroup; -const ListGroupItem = ReactBootstrap.ListGroupItem; const LoginModal = require('./components/loginModal.jsx'); -const {remote, ipcRenderer, webFrame, shell} = require('electron'); +const {remote, ipcRenderer, shell} = require('electron'); +const electronContextMenu = require('electron-context-menu'); const osLocale = require('os-locale'); const fs = require('fs'); const url = require('url'); -const path = require('path'); const settings = require('../common/settings'); +const badge = require('./js/badge'); remote.getCurrentWindow().removeAllListeners('focus'); +var config; +try { + var configFile = remote.getGlobal('config-file'); + config = settings.readFileSync(configFile); +} catch (e) { + window.location = 'settings.html'; +} +if (config.teams.length === 0) { + window.location = 'settings.html'; +} + var MainPage = React.createClass({ - getInitialState: function() { + getInitialState() { return { key: 0, unreadCounts: new Array(this.props.teams.length), @@ -41,84 +50,85 @@ var MainPage = React.createClass({ loginQueue: [] }; }, - componentDidMount: function() { - var thisObj = this; - ipcRenderer.on('login-request', function(event, request, authInfo) { - thisObj.setState({ + componentDidMount() { + var self = this; + ipcRenderer.on('login-request', (event, request, authInfo) => { + self.setState({ loginRequired: true }); - const loginQueue = thisObj.state.loginQueue; + const loginQueue = self.state.loginQueue; loginQueue.push({ - request: request, - authInfo: authInfo + request, + authInfo }); - thisObj.setState({ - loginQueue: loginQueue + self.setState({ + loginQueue }); }); + // can't switch tabs sequencially for some reason... ipcRenderer.on('switch-tab', (event, key) => { this.handleSelect(key); }); - ipcRenderer.on('select-next-tab', (event) => { + ipcRenderer.on('select-next-tab', () => { this.handleSelect(this.state.key + 1); }); - ipcRenderer.on('select-previous-tab', (event) => { + ipcRenderer.on('select-previous-tab', () => { this.handleSelect(this.state.key - 1); }); // reload the activated tab - ipcRenderer.on('reload-tab', (event) => { + ipcRenderer.on('reload-tab', () => { this.refs[`mattermostView${this.state.key}`].reload(); }); - ipcRenderer.on('clear-cache-and-reload-tab', (event) => { + ipcRenderer.on('clear-cache-and-reload-tab', () => { this.refs[`mattermostView${this.state.key}`].clearCacheAndReload(); }); // activate search box in current tab - ipcRenderer.on('activate-search-box', (event) => { - let webview = document.getElementById('mattermostView' + thisObj.state.key); + ipcRenderer.on('activate-search-box', () => { + const webview = document.getElementById('mattermostView' + self.state.key); webview.send('activate-search-box'); }); // activate search box in current chunnel - ipcRenderer.on('activate-search-box-in-channel', (event) => { - let webview = document.getElementById('mattermostView' + thisObj.state.key); + ipcRenderer.on('activate-search-box-in-channel', () => { + const webview = document.getElementById('mattermostView' + self.state.key); webview.send('activate-search-box-in-channel'); }); - var focusListener = function() { - thisObj.handleOnTeamFocused(thisObj.state.key); - thisObj.refs[`mattermostView${thisObj.state.key}`].focusOnWebView(); - }; + function focusListener() { + self.handleOnTeamFocused(self.state.key); + self.refs[`mattermostView${self.state.key}`].focusOnWebView(); + } var currentWindow = remote.getCurrentWindow(); currentWindow.on('focus', focusListener); - window.addEventListener('beforeunload', function() { + window.addEventListener('beforeunload', () => { currentWindow.removeListener('focus', focusListener); }); //goBack and goForward ipcRenderer.on('go-back', () => { - const mattermost = thisObj.refs[`mattermostView${thisObj.state.key}`]; + const mattermost = self.refs[`mattermostView${self.state.key}`]; if (mattermost.canGoBack()) { mattermost.goBack(); } }); ipcRenderer.on('go-forward', () => { - const mattermost = thisObj.refs[`mattermostView${thisObj.state.key}`]; + const mattermost = self.refs[`mattermostView${self.state.key}`]; if (mattermost.canGoForward()) { mattermost.goForward(); } }); }, - componentDidUpdate: function(prevProps, prevState) { + componentDidUpdate(prevProps, prevState) { if (prevState.key !== this.state.key) { // i.e. When tab has been changed this.refs[`mattermostView${this.state.key}`].focusOnWebView(); } }, - handleSelect: function(key) { + handleSelect(key) { const newKey = (this.props.teams.length + key) % this.props.teams.length; this.setState({ key: newKey @@ -130,13 +140,14 @@ var MainPage = React.createClass({ title: webview.getTitle() }); }, - handleUnreadCountChange: function(index, unreadCount, mentionCount, isUnread, isMentioned) { + handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned) { var unreadCounts = this.state.unreadCounts; var mentionCounts = this.state.mentionCounts; var unreadAtActive = this.state.unreadAtActive; var mentionAtActiveCounts = this.state.mentionAtActiveCounts; unreadCounts[index] = unreadCount; mentionCounts[index] = mentionCount; + // Never turn on the unreadAtActive flag at current focused tab. if (this.state.key !== index || !remote.getCurrentWindow().isFocused()) { unreadAtActive[index] = unreadAtActive[index] || isUnread; @@ -145,49 +156,49 @@ var MainPage = React.createClass({ } } this.setState({ - unreadCounts: unreadCounts, - mentionCounts: mentionCounts, - unreadAtActive: unreadAtActive, - mentionAtActiveCounts: mentionAtActiveCounts + unreadCounts, + mentionCounts, + unreadAtActive, + mentionAtActiveCounts }); this.handleUnreadCountTotalChange(); }, - markReadAtActive: function(index) { + markReadAtActive(index) { var unreadAtActive = this.state.unreadAtActive; var mentionAtActiveCounts = this.state.mentionAtActiveCounts; unreadAtActive[index] = false; mentionAtActiveCounts[index] = 0; this.setState({ - unreadAtActive: unreadAtActive, - mentionAtActiveCounts: mentionAtActiveCounts + unreadAtActive, + mentionAtActiveCounts }); this.handleUnreadCountTotalChange(); }, - handleUnreadCountTotalChange: function() { + handleUnreadCountTotalChange() { if (this.props.onUnreadCountChange) { - var allUnreadCount = this.state.unreadCounts.reduce(function(prev, curr) { + var allUnreadCount = this.state.unreadCounts.reduce((prev, curr) => { return prev + curr; }, 0); - this.state.unreadAtActive.forEach(function(state) { + this.state.unreadAtActive.forEach((state) => { if (state) { allUnreadCount += 1; } }); - var allMentionCount = this.state.mentionCounts.reduce(function(prev, curr) { + var allMentionCount = this.state.mentionCounts.reduce((prev, curr) => { return prev + curr; }, 0); - this.state.mentionAtActiveCounts.forEach(function(count) { + this.state.mentionAtActiveCounts.forEach((count) => { allMentionCount += count; }); this.props.onUnreadCountChange(allUnreadCount, allMentionCount); } }, - handleOnTeamFocused: function(index) { + handleOnTeamFocused(index) { // Turn off the flag to indicate whether unread message of active channel contains at current tab. this.markReadAtActive(index); }, - visibleStyle: function(visible) { + visibleStyle(visible) { var visibility = visible ? 'visible' : 'hidden'; return { position: 'absolute', @@ -195,66 +206,91 @@ var MainPage = React.createClass({ right: 0, bottom: 0, left: 0, - visibility: visibility + visibility }; }, - handleLogin: function(request, username, password) { + handleLogin(request, username, password) { ipcRenderer.send('login-credentials', request, username, password); const loginQueue = this.state.loginQueue; loginQueue.shift(); this.setState(loginQueue); }, - handleLoginCancel: function() { + handleLoginCancel() { const loginQueue = this.state.loginQueue; loginQueue.shift(); this.setState(loginQueue); }, - render: function() { - var thisObj = this; + render() { + var self = this; - var tabs_row; + var tabsRow; if (this.props.teams.length > 1) { - tabs_row = ( + tabsRow = ( - + ); } - var views = this.props.teams.map(function(team, index) { - var handleUnreadCountChange = function(unreadCount, mentionCount, isUnread, isMentioned) { - thisObj.handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned); - }; - var handleNotificationClick = function() { - thisObj.handleSelect(index); + var views = this.props.teams.map((team, index) => { + function handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned) { + self.handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned); + } + function handleNotificationClick() { + self.handleSelect(index); } var id = 'mattermostView' + index; - var is_active = thisObj.state.key === index; - return () + var isActive = self.state.key === index; + return ( + ); }); - var views_row = ( - { views } - ); + var viewsRow = ( + + {views} + ); var request = null; var authServerURL = null; var authInfo = null; if (this.state.loginQueue.length !== 0) { request = this.state.loginQueue[0].request; - const tmp_url = url.parse(this.state.loginQueue[0].request.url); - authServerURL = `${tmp_url.protocol}//${tmp_url.host}`; + const tmpURL = url.parse(this.state.loginQueue[0].request.url); + authServerURL = `${tmpURL.protocol}//${tmpURL.host}`; authInfo = this.state.loginQueue[0].authInfo; } return (
- + - { tabs_row } - { views_row } + { tabsRow } + { viewsRow }
); @@ -262,9 +298,9 @@ var MainPage = React.createClass({ }); var TabBar = React.createClass({ - render: function() { - var thisObj = this; - var tabs = this.props.teams.map(function(team, index) { + render() { + var self = this; + var tabs = this.props.teams.map((team, index) => { var unreadCount = 0; var badgeStyle = { background: '#FF1744', @@ -276,48 +312,64 @@ var TabBar = React.createClass({ lineHeight: '20px', height: '19px', marginLeft: '5px', - borderRadius: '50%', + borderRadius: '50%' }; - if (thisObj.props.unreadCounts[index] > 0) { - unreadCount = thisObj.props.unreadCounts[index]; + if (self.props.unreadCounts[index] > 0) { + unreadCount = self.props.unreadCounts[index]; } - if (thisObj.props.unreadAtActive[index]) { + if (self.props.unreadAtActive[index]) { unreadCount += 1; } var mentionCount = 0; - if (thisObj.props.mentionCounts[index] > 0) { - mentionCount = thisObj.props.mentionCounts[index]; + if (self.props.mentionCounts[index] > 0) { + mentionCount = self.props.mentionCounts[index]; } - if (thisObj.props.mentionAtActiveCounts[index] > 0) { - mentionCount += thisObj.props.mentionAtActiveCounts[index]; + if (self.props.mentionAtActiveCounts[index] > 0) { + mentionCount += self.props.mentionAtActiveCounts[index]; } - var badge; - if (mentionCount != 0) { - badge = (
- { mentionCount } -
); + var badgeDiv; + if (mentionCount !== 0) { + badgeDiv = ( +
+ {mentionCount} +
); } - if (unreadCount == 0) { - var id = 'teamTabItem' + index; - return ( - { team.name } - { ' ' } - { badge } - ); - } else { - var id = 'teamTabItem' + index; - return ( - { team.name } - { ' ' } - { badge } - ); + var id = 'teamTabItem' + index; + if (unreadCount === 0) { + return ( + + { team.name } + { ' ' } + { badgeDiv } + ); } + return ( + + { team.name } + { ' ' } + { badgeDiv } + ); }); return ( -