Merge pull request #371 from yuya-oc/latest-react-bootstrap
Latest react bootstrap
This commit is contained in:
@@ -7,11 +7,8 @@
|
|||||||
"no-eval": 1,
|
"no-eval": 1,
|
||||||
"no-process-env": 0,
|
"no-process-env": 0,
|
||||||
"no-underscore-dangle": 1,
|
"no-underscore-dangle": 1,
|
||||||
"react/jsx-boolean-value": [1, "always"],
|
|
||||||
"react/jsx-indent": [2, 2],
|
"react/jsx-indent": [2, 2],
|
||||||
"react/jsx-indent-props": [2, 2],
|
"react/jsx-indent-props": [2, 2],
|
||||||
"react/no-multi-comp": 1,
|
"react/prefer-es6-class": 1
|
||||||
"react/prefer-es6-class": 1,
|
|
||||||
"react/prop-types": 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
95
src/browser/components/ErrorView.jsx
Normal file
95
src/browser/components/ErrorView.jsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// ErrorCode: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
const {Grid, Row, Col} = require('react-bootstrap');
|
||||||
|
|
||||||
|
const errorPage = {
|
||||||
|
tableStyle: {
|
||||||
|
display: 'table',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '0',
|
||||||
|
left: '0'
|
||||||
|
},
|
||||||
|
|
||||||
|
cellStyle: {
|
||||||
|
display: 'table-cell',
|
||||||
|
verticalAlign: 'top',
|
||||||
|
paddingTop: '2em'
|
||||||
|
},
|
||||||
|
|
||||||
|
bullets: {
|
||||||
|
paddingLeft: '15px',
|
||||||
|
lineHeight: '1.7'
|
||||||
|
},
|
||||||
|
|
||||||
|
techInfo: {
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#aaa'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ErrorView extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
id={this.props.id}
|
||||||
|
style={this.props.style}
|
||||||
|
>
|
||||||
|
<div style={errorPage.tableStyle}>
|
||||||
|
<div style={errorPage.cellStyle}>
|
||||||
|
<Row>
|
||||||
|
<Col
|
||||||
|
xs={0}
|
||||||
|
sm={1}
|
||||||
|
md={1}
|
||||||
|
lg={2}
|
||||||
|
/>
|
||||||
|
<Col
|
||||||
|
xs={12}
|
||||||
|
sm={10}
|
||||||
|
md={10}
|
||||||
|
lg={8}
|
||||||
|
>
|
||||||
|
<h2>{'Cannot connect to Mattermost'}</h2>
|
||||||
|
<hr/>
|
||||||
|
<p>{'We\'re having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work please verify that:'}</p>
|
||||||
|
<br/>
|
||||||
|
<ul style={errorPage.bullets}>
|
||||||
|
<li>{'Your computer is connected to the internet.'}</li>
|
||||||
|
<li>{'The Mattermost URL '}
|
||||||
|
<a href={this.props.errorInfo.validatedURL}>
|
||||||
|
{this.props.errorInfo.validatedURL}
|
||||||
|
</a>{' is correct.'}</li>
|
||||||
|
<li>{'You can reach '}
|
||||||
|
<a href={this.props.errorInfo.validatedURL}>
|
||||||
|
{this.props.errorInfo.validatedURL}
|
||||||
|
</a>{' from a browser window.'}</li>
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
<div style={errorPage.techInfo}>
|
||||||
|
{this.props.errorInfo.errorDescription}{' ('}
|
||||||
|
{this.props.errorInfo.errorCode }{')'}</div>
|
||||||
|
</Col>
|
||||||
|
<Col
|
||||||
|
xs={0}
|
||||||
|
sm={1}
|
||||||
|
md={1}
|
||||||
|
lg={2}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorView.propTypes = {
|
||||||
|
errorInfo: React.PropTypes.object,
|
||||||
|
id: React.PropTypes.number,
|
||||||
|
style: React.PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ErrorView;
|
@@ -1,16 +1,13 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require('react-dom');
|
const ReactDOM = require('react-dom');
|
||||||
const ReactBootstrap = require('react-bootstrap');
|
const {Button, Col, ControlLabel, Form, FormGroup, FormControl, Modal} = require('react-bootstrap');
|
||||||
const Modal = ReactBootstrap.Modal;
|
|
||||||
const Form = ReactBootstrap.Form;
|
|
||||||
const FormGroup = ReactBootstrap.FormGroup;
|
|
||||||
const FormControl = ReactBootstrap.FormControl;
|
|
||||||
const ControlLabel = ReactBootstrap.ControlLabel;
|
|
||||||
const Col = ReactBootstrap.Col;
|
|
||||||
|
|
||||||
const Button = ReactBootstrap.Button;
|
class LoginModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
const LoginModal = React.createClass({
|
|
||||||
handleSubmit(event) {
|
handleSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const usernameNode = ReactDOM.findDOMNode(this.refs.username);
|
const usernameNode = ReactDOM.findDOMNode(this.refs.username);
|
||||||
@@ -18,7 +15,8 @@ const LoginModal = React.createClass({
|
|||||||
this.props.onLogin(this.props.request, usernameNode.value, passwordNode.value);
|
this.props.onLogin(this.props.request, usernameNode.value, passwordNode.value);
|
||||||
usernameNode.value = '';
|
usernameNode.value = '';
|
||||||
passwordNode.value = '';
|
passwordNode.value = '';
|
||||||
},
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var theServer = '';
|
var theServer = '';
|
||||||
if (!this.props.show) {
|
if (!this.props.show) {
|
||||||
@@ -39,7 +37,7 @@ const LoginModal = React.createClass({
|
|||||||
{ message }
|
{ message }
|
||||||
</p>
|
</p>
|
||||||
<Form
|
<Form
|
||||||
horizontal
|
horizontal={true}
|
||||||
onSubmit={this.handleSubmit}
|
onSubmit={this.handleSubmit}
|
||||||
>
|
>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
@@ -85,6 +83,15 @@ const LoginModal = React.createClass({
|
|||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
LoginModal.propTypes = {
|
||||||
|
authInfo: React.PropTypes.object,
|
||||||
|
authServerURL: React.PropTypes.string,
|
||||||
|
onCancel: React.PropTypes.func,
|
||||||
|
onLogin: React.PropTypes.func,
|
||||||
|
request: React.PropTypes.object,
|
||||||
|
show: React.PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = LoginModal;
|
module.exports = LoginModal;
|
281
src/browser/components/MainPage.jsx
Normal file
281
src/browser/components/MainPage.jsx
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const {Grid, Row} = require('react-bootstrap');
|
||||||
|
|
||||||
|
const {ipcRenderer, remote} = require('electron');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
|
const LoginModal = require('./LoginModal.jsx');
|
||||||
|
const MattermostView = require('./MattermostView.jsx');
|
||||||
|
const TabBar = require('./TabBar.jsx');
|
||||||
|
|
||||||
|
const MainPage = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
disablewebsecurity: React.PropTypes.bool.isRequired,
|
||||||
|
onUnreadCountChange: React.PropTypes.func.isRequired,
|
||||||
|
teams: React.PropTypes.array.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
key: 0,
|
||||||
|
unreadCounts: new Array(this.props.teams.length),
|
||||||
|
mentionCounts: new Array(this.props.teams.length),
|
||||||
|
unreadAtActive: new Array(this.props.teams.length),
|
||||||
|
mentionAtActiveCounts: new Array(this.props.teams.length),
|
||||||
|
loginQueue: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidMount() {
|
||||||
|
var self = this;
|
||||||
|
ipcRenderer.on('login-request', (event, request, authInfo) => {
|
||||||
|
self.setState({
|
||||||
|
loginRequired: true
|
||||||
|
});
|
||||||
|
const loginQueue = self.state.loginQueue;
|
||||||
|
loginQueue.push({
|
||||||
|
request,
|
||||||
|
authInfo
|
||||||
|
});
|
||||||
|
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', () => {
|
||||||
|
this.handleSelect(this.state.key + 1);
|
||||||
|
});
|
||||||
|
ipcRenderer.on('select-previous-tab', () => {
|
||||||
|
this.handleSelect(this.state.key - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// reload the activated tab
|
||||||
|
ipcRenderer.on('reload-tab', () => {
|
||||||
|
this.refs[`mattermostView${this.state.key}`].reload();
|
||||||
|
});
|
||||||
|
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', () => {
|
||||||
|
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', () => {
|
||||||
|
const webview = document.getElementById('mattermostView' + self.state.key);
|
||||||
|
webview.send('activate-search-box-in-channel');
|
||||||
|
});
|
||||||
|
|
||||||
|
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', () => {
|
||||||
|
currentWindow.removeListener('focus', focusListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://github.com/mattermost/desktop/pull/371#issuecomment-263072803
|
||||||
|
currentWindow.webContents.on('devtools-closed', () => {
|
||||||
|
focusListener();
|
||||||
|
});
|
||||||
|
|
||||||
|
//goBack and goForward
|
||||||
|
ipcRenderer.on('go-back', () => {
|
||||||
|
const mattermost = self.refs[`mattermostView${self.state.key}`];
|
||||||
|
if (mattermost.canGoBack()) {
|
||||||
|
mattermost.goBack();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('go-forward', () => {
|
||||||
|
const mattermost = self.refs[`mattermostView${self.state.key}`];
|
||||||
|
if (mattermost.canGoForward()) {
|
||||||
|
mattermost.goForward();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (prevState.key !== this.state.key) { // i.e. When tab has been changed
|
||||||
|
this.refs[`mattermostView${this.state.key}`].focusOnWebView();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSelect(key) {
|
||||||
|
const newKey = (this.props.teams.length + key) % this.props.teams.length;
|
||||||
|
this.setState({
|
||||||
|
key: newKey
|
||||||
|
});
|
||||||
|
this.handleOnTeamFocused(newKey);
|
||||||
|
|
||||||
|
var webview = document.getElementById('mattermostView' + newKey);
|
||||||
|
ipcRenderer.send('update-title', {
|
||||||
|
title: webview.getTitle()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
if (isMentioned) {
|
||||||
|
mentionAtActiveCounts[index]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
unreadCounts,
|
||||||
|
mentionCounts,
|
||||||
|
unreadAtActive,
|
||||||
|
mentionAtActiveCounts
|
||||||
|
});
|
||||||
|
this.handleUnreadCountTotalChange();
|
||||||
|
},
|
||||||
|
markReadAtActive(index) {
|
||||||
|
var unreadAtActive = this.state.unreadAtActive;
|
||||||
|
var mentionAtActiveCounts = this.state.mentionAtActiveCounts;
|
||||||
|
unreadAtActive[index] = false;
|
||||||
|
mentionAtActiveCounts[index] = 0;
|
||||||
|
this.setState({
|
||||||
|
unreadAtActive,
|
||||||
|
mentionAtActiveCounts
|
||||||
|
});
|
||||||
|
this.handleUnreadCountTotalChange();
|
||||||
|
},
|
||||||
|
handleUnreadCountTotalChange() {
|
||||||
|
if (this.props.onUnreadCountChange) {
|
||||||
|
var allUnreadCount = this.state.unreadCounts.reduce((prev, curr) => {
|
||||||
|
return prev + curr;
|
||||||
|
}, 0);
|
||||||
|
this.state.unreadAtActive.forEach((state) => {
|
||||||
|
if (state) {
|
||||||
|
allUnreadCount += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var allMentionCount = this.state.mentionCounts.reduce((prev, curr) => {
|
||||||
|
return prev + curr;
|
||||||
|
}, 0);
|
||||||
|
this.state.mentionAtActiveCounts.forEach((count) => {
|
||||||
|
allMentionCount += count;
|
||||||
|
});
|
||||||
|
this.props.onUnreadCountChange(allUnreadCount, allMentionCount);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleOnTeamFocused(index) {
|
||||||
|
// Turn off the flag to indicate whether unread message of active channel contains at current tab.
|
||||||
|
this.markReadAtActive(index);
|
||||||
|
},
|
||||||
|
|
||||||
|
visibleStyle(visible) {
|
||||||
|
var visibility = visible ? 'visible' : 'hidden';
|
||||||
|
return {
|
||||||
|
position: 'absolute',
|
||||||
|
top: (this.props.teams.length > 1) ? 42 : 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
visibility
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleLogin(request, username, password) {
|
||||||
|
ipcRenderer.send('login-credentials', request, username, password);
|
||||||
|
const loginQueue = this.state.loginQueue;
|
||||||
|
loginQueue.shift();
|
||||||
|
this.setState({loginQueue});
|
||||||
|
},
|
||||||
|
handleLoginCancel() {
|
||||||
|
const loginQueue = this.state.loginQueue;
|
||||||
|
loginQueue.shift();
|
||||||
|
this.setState({loginQueue});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var tabsRow;
|
||||||
|
if (this.props.teams.length > 1) {
|
||||||
|
tabsRow = (
|
||||||
|
<Row>
|
||||||
|
<TabBar
|
||||||
|
id='tabBar'
|
||||||
|
teams={this.props.teams}
|
||||||
|
unreadCounts={this.state.unreadCounts}
|
||||||
|
mentionCounts={this.state.mentionCounts}
|
||||||
|
unreadAtActive={this.state.unreadAtActive}
|
||||||
|
mentionAtActiveCounts={this.state.mentionAtActiveCounts}
|
||||||
|
activeKey={this.state.key}
|
||||||
|
onSelect={this.handleSelect}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 isActive = self.state.key === index;
|
||||||
|
return (
|
||||||
|
<MattermostView
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
style={self.visibleStyle(isActive)}
|
||||||
|
src={team.url}
|
||||||
|
name={team.name}
|
||||||
|
disablewebsecurity={this.props.disablewebsecurity}
|
||||||
|
onUnreadCountChange={handleUnreadCountChange}
|
||||||
|
onNotificationClick={handleNotificationClick}
|
||||||
|
ref={id}
|
||||||
|
active={isActive}
|
||||||
|
/>);
|
||||||
|
});
|
||||||
|
var viewsRow = (
|
||||||
|
<Row>
|
||||||
|
{views}
|
||||||
|
</Row>);
|
||||||
|
|
||||||
|
var request = null;
|
||||||
|
var authServerURL = null;
|
||||||
|
var authInfo = null;
|
||||||
|
if (this.state.loginQueue.length !== 0) {
|
||||||
|
request = this.state.loginQueue[0].request;
|
||||||
|
const tmpURL = url.parse(this.state.loginQueue[0].request.url);
|
||||||
|
authServerURL = `${tmpURL.protocol}//${tmpURL.host}`;
|
||||||
|
authInfo = this.state.loginQueue[0].authInfo;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LoginModal
|
||||||
|
show={this.state.loginQueue.length !== 0}
|
||||||
|
request={request}
|
||||||
|
authInfo={authInfo}
|
||||||
|
authServerURL={authServerURL}
|
||||||
|
onLogin={this.handleLogin}
|
||||||
|
onCancel={this.handleLoginCancel}
|
||||||
|
/>
|
||||||
|
<Grid fluid={true}>
|
||||||
|
{ tabsRow }
|
||||||
|
{ viewsRow }
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = MainPage;
|
231
src/browser/components/MattermostView.jsx
Normal file
231
src/browser/components/MattermostView.jsx
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const {findDOMNode} = require('react-dom');
|
||||||
|
const {ipcRenderer, shell} = require('electron');
|
||||||
|
const fs = require('fs');
|
||||||
|
const url = require('url');
|
||||||
|
const osLocale = require('os-locale');
|
||||||
|
const electronContextMenu = require('electron-context-menu');
|
||||||
|
|
||||||
|
const ErrorView = require('./ErrorView.jsx');
|
||||||
|
|
||||||
|
const MattermostView = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
disablewebsecurity: React.PropTypes.bool,
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
id: React.PropTypes.string,
|
||||||
|
onUnreadCountChange: React.PropTypes.func,
|
||||||
|
src: React.PropTypes.string,
|
||||||
|
style: React.PropTypes.object
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
errorInfo: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned) {
|
||||||
|
if (this.props.onUnreadCountChange) {
|
||||||
|
this.props.onUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
var self = this;
|
||||||
|
var webview = findDOMNode(this.refs.webview);
|
||||||
|
|
||||||
|
// This option allows insecure content, when set to true it is possible to
|
||||||
|
// load content via HTTP while the mattermost server serves HTTPS
|
||||||
|
if (this.props.disablewebsecurity === true) {
|
||||||
|
webview.setAttribute('webpreferences', 'allowDisplayingInsecureContent');
|
||||||
|
}
|
||||||
|
|
||||||
|
webview.addEventListener('did-fail-load', (e) => {
|
||||||
|
console.log(self.props.name, 'webview did-fail-load', e);
|
||||||
|
if (e.errorCode === -3) { // An operation was aborted (due to user action).
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setState({
|
||||||
|
errorInfo: e
|
||||||
|
});
|
||||||
|
function reload() {
|
||||||
|
window.removeEventListener('online', reload);
|
||||||
|
self.reload();
|
||||||
|
}
|
||||||
|
if (navigator.onLine) {
|
||||||
|
setTimeout(reload, 30000);
|
||||||
|
} else {
|
||||||
|
window.addEventListener('online', reload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open link in browserWindow. for exmaple, attached files.
|
||||||
|
webview.addEventListener('new-window', (e) => {
|
||||||
|
var currentURL = url.parse(webview.getURL());
|
||||||
|
var destURL = url.parse(e.url);
|
||||||
|
if (destURL.protocol !== 'http:' && destURL.protocol !== 'https:') {
|
||||||
|
ipcRenderer.send('confirm-protocol', destURL.protocol, e.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentURL.host === destURL.host) {
|
||||||
|
// New window should disable nodeIntergration.
|
||||||
|
window.open(e.url, 'Mattermost', 'nodeIntegration=no');
|
||||||
|
} else {
|
||||||
|
// if the link is external, use default browser.
|
||||||
|
shell.openExternal(e.url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
webview.addEventListener('dom-ready', () => {
|
||||||
|
// webview.openDevTools();
|
||||||
|
|
||||||
|
// Use 'Meiryo UI' and 'MS Gothic' to prevent CJK fonts on Windows(JP).
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
function applyCssFile(cssFile) {
|
||||||
|
fs.readFile(cssFile, 'utf8', (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
webview.insertCSS(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
osLocale((err, locale) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (locale === 'ja_JP') {
|
||||||
|
applyCssFile(__dirname + '/css/jp_fonts.css');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
electronContextMenu({
|
||||||
|
window: webview
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
webview.addEventListener('ipc-message', (event) => {
|
||||||
|
switch (event.channel) {
|
||||||
|
case 'onUnreadCountChange':
|
||||||
|
var unreadCount = event.args[0];
|
||||||
|
var mentionCount = event.args[1];
|
||||||
|
|
||||||
|
// isUnread and isMentioned is pulse flag.
|
||||||
|
var isUnread = event.args[2];
|
||||||
|
var isMentioned = event.args[3];
|
||||||
|
self.handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned);
|
||||||
|
break;
|
||||||
|
case 'onNotificationClick':
|
||||||
|
self.props.onNotificationClick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
webview.addEventListener('page-title-updated', (event) => {
|
||||||
|
if (self.props.active) {
|
||||||
|
ipcRenderer.send('update-title', {
|
||||||
|
title: event.title
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
webview.addEventListener('console-message', (e) => {
|
||||||
|
const message = `[${this.props.name}] ${e.message}`;
|
||||||
|
switch (e.level) {
|
||||||
|
case 0:
|
||||||
|
console.log(message);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
console.warn(message);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
console.error(message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
this.setState({
|
||||||
|
errorInfo: null
|
||||||
|
});
|
||||||
|
var webview = findDOMNode(this.refs.webview);
|
||||||
|
webview.reload();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearCacheAndReload() {
|
||||||
|
this.setState({
|
||||||
|
errorInfo: null
|
||||||
|
});
|
||||||
|
var webContents = findDOMNode(this.refs.webview).getWebContents();
|
||||||
|
webContents.session.clearCache(() => {
|
||||||
|
webContents.reload();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
focusOnWebView() {
|
||||||
|
const webview = findDOMNode(this.refs.webview);
|
||||||
|
if (!webview.getWebContents().isFocused()) {
|
||||||
|
webview.focus();
|
||||||
|
webview.getWebContents().focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
canGoBack() {
|
||||||
|
const webview = findDOMNode(this.refs.webview);
|
||||||
|
return webview.getWebContents().canGoBack();
|
||||||
|
},
|
||||||
|
|
||||||
|
canGoForward() {
|
||||||
|
const webview = findDOMNode(this.refs.webview);
|
||||||
|
return webview.getWebContents().canGoForward();
|
||||||
|
},
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
const webview = findDOMNode(this.refs.webview);
|
||||||
|
webview.getWebContents().goBack();
|
||||||
|
},
|
||||||
|
|
||||||
|
goForward() {
|
||||||
|
const webview = findDOMNode(this.refs.webview);
|
||||||
|
webview.getWebContents().goForward();
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const errorView = this.state.errorInfo ? (
|
||||||
|
<ErrorView
|
||||||
|
id={this.props.id + '-fail'}
|
||||||
|
style={this.props.style}
|
||||||
|
className='errorView'
|
||||||
|
errorInfo={this.state.errorInfo}
|
||||||
|
/>) : null;
|
||||||
|
|
||||||
|
// 'disablewebsecurity' is necessary to display external images.
|
||||||
|
// However, it allows also CSS/JavaScript.
|
||||||
|
// So webview should use 'allowDisplayingInsecureContent' as same as BrowserWindow.
|
||||||
|
|
||||||
|
// Need to keep webview mounted when failed to load.
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ errorView }
|
||||||
|
<webview
|
||||||
|
id={this.props.id}
|
||||||
|
className='mattermostView'
|
||||||
|
style={this.props.style}
|
||||||
|
preload='webview/mattermost.js'
|
||||||
|
src={this.props.src}
|
||||||
|
ref='webview'
|
||||||
|
nodeintegration='false'
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = MattermostView;
|
387
src/browser/components/SettingsPage.jsx
Normal file
387
src/browser/components/SettingsPage.jsx
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const {Button, Checkbox, Col, FormGroup, Grid, Navbar, Row} = require('react-bootstrap');
|
||||||
|
|
||||||
|
const {ipcRenderer, remote} = require('electron');
|
||||||
|
const AutoLaunch = require('auto-launch');
|
||||||
|
|
||||||
|
const settings = require('../../common/settings');
|
||||||
|
|
||||||
|
const TeamList = require('./TeamList.jsx');
|
||||||
|
|
||||||
|
const appLauncher = new AutoLaunch({
|
||||||
|
name: 'Mattermost',
|
||||||
|
isHidden: true
|
||||||
|
});
|
||||||
|
|
||||||
|
function backToIndex() {
|
||||||
|
remote.getCurrentWindow().loadURL('file://' + __dirname + '/index.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsPage = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
configFile: React.PropTypes.string
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
var initialState;
|
||||||
|
try {
|
||||||
|
initialState = settings.readFileSync(this.props.configFile);
|
||||||
|
} catch (e) {
|
||||||
|
initialState = settings.loadDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialState.showAddTeamForm = false;
|
||||||
|
initialState.trayWasVisible = remote.getCurrentWindow().trayWasVisible;
|
||||||
|
|
||||||
|
return initialState;
|
||||||
|
},
|
||||||
|
componentDidMount() {
|
||||||
|
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||||
|
var self = this;
|
||||||
|
appLauncher.isEnabled().then((enabled) => {
|
||||||
|
self.setState({
|
||||||
|
autostart: enabled
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleTeamsChange(teams) {
|
||||||
|
this.setState({
|
||||||
|
showAddTeamForm: false,
|
||||||
|
teams
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSave() {
|
||||||
|
var config = {
|
||||||
|
teams: this.state.teams,
|
||||||
|
hideMenuBar: this.state.hideMenuBar,
|
||||||
|
showTrayIcon: this.state.showTrayIcon,
|
||||||
|
trayIconTheme: this.state.trayIconTheme,
|
||||||
|
disablewebsecurity: this.state.disablewebsecurity,
|
||||||
|
version: settings.version,
|
||||||
|
minimizeToTray: this.state.minimizeToTray,
|
||||||
|
toggleWindowOnTrayIconClick: this.state.toggleWindowOnTrayIconClick,
|
||||||
|
notifications: {
|
||||||
|
flashWindow: this.state.notifications.flashWindow
|
||||||
|
},
|
||||||
|
showUnreadBadge: this.state.showUnreadBadge
|
||||||
|
};
|
||||||
|
settings.writeFileSync(this.props.configFile, config);
|
||||||
|
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||||
|
var currentWindow = remote.getCurrentWindow();
|
||||||
|
currentWindow.setAutoHideMenuBar(config.hideMenuBar);
|
||||||
|
currentWindow.setMenuBarVisibility(!config.hideMenuBar);
|
||||||
|
|
||||||
|
var autostart = this.state.autostart;
|
||||||
|
appLauncher.isEnabled().then((enabled) => {
|
||||||
|
if (enabled && !autostart) {
|
||||||
|
appLauncher.disable();
|
||||||
|
} else if (!enabled && autostart) {
|
||||||
|
appLauncher.enable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send('update-menu', config);
|
||||||
|
ipcRenderer.send('update-config');
|
||||||
|
|
||||||
|
backToIndex();
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
backToIndex();
|
||||||
|
},
|
||||||
|
handleChangeDisableWebSecurity() {
|
||||||
|
this.setState({
|
||||||
|
disablewebsecurity: !this.refs.disablewebsecurity.props.checked
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleChangeHideMenuBar() {
|
||||||
|
this.setState({
|
||||||
|
hideMenuBar: !this.refs.hideMenuBar.props.checked
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleChangeShowTrayIcon() {
|
||||||
|
var shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked;
|
||||||
|
this.setState({
|
||||||
|
showTrayIcon: shouldShowTrayIcon
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' && !shouldShowTrayIcon) {
|
||||||
|
this.setState({
|
||||||
|
minimizeToTray: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleChangeTrayIconTheme() {
|
||||||
|
this.setState({
|
||||||
|
trayIconTheme: !this.refs.trayIconTheme.props.checked
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleChangeAutoStart() {
|
||||||
|
this.setState({
|
||||||
|
autostart: !this.refs.autostart.props.checked
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleChangeMinimizeToTray() {
|
||||||
|
var shouldMinimizeToTray =
|
||||||
|
(process.platform !== 'darwin' || !this.refs.showTrayIcon.props.checked) &&
|
||||||
|
!this.refs.minimizeToTray.props.checked;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
minimizeToTray: shouldMinimizeToTray
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleChangeToggleWindowOnTrayIconClick() {
|
||||||
|
this.setState({
|
||||||
|
toggleWindowOnTrayIconClick: !this.refs.toggleWindowOnTrayIconClick.props.checked
|
||||||
|
});
|
||||||
|
},
|
||||||
|
toggleShowTeamForm() {
|
||||||
|
this.setState({
|
||||||
|
showAddTeamForm: !this.state.showAddTeamForm
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleFlashWindow() {
|
||||||
|
this.setState({
|
||||||
|
notifications: {
|
||||||
|
flashWindow: this.refs.flashWindow.props.checked ? 0 : 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleShowUnreadBadge() {
|
||||||
|
this.setState({
|
||||||
|
showUnreadBadge: !this.refs.showUnreadBadge.props.checked
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
var teamsRow = (
|
||||||
|
<Row>
|
||||||
|
<Col md={12}>
|
||||||
|
<TeamList
|
||||||
|
teams={this.state.teams}
|
||||||
|
showAddTeamForm={this.state.showAddTeamForm}
|
||||||
|
onTeamsChange={this.handleTeamsChange}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
|
||||||
|
var options = [];
|
||||||
|
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='inputHideMenuBar'
|
||||||
|
id='inputHideMenuBar'
|
||||||
|
ref='hideMenuBar'
|
||||||
|
checked={this.state.hideMenuBar}
|
||||||
|
onChange={this.handleChangeHideMenuBar}
|
||||||
|
>{'Hide menu bar (Press Alt to show menu bar)'}</Checkbox>);
|
||||||
|
}
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='inputShowTrayIcon'
|
||||||
|
id='inputShowTrayIcon'
|
||||||
|
ref='showTrayIcon'
|
||||||
|
checked={this.state.showTrayIcon}
|
||||||
|
onChange={this.handleChangeShowTrayIcon}
|
||||||
|
>{process.platform === 'darwin' ?
|
||||||
|
'Show icon on menu bar (need to restart the application)' :
|
||||||
|
'Show icon in notification area (need to restart the application)'}</Checkbox>);
|
||||||
|
}
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='inputTrayIconTheme'
|
||||||
|
ref='trayIconTheme'
|
||||||
|
type='select'
|
||||||
|
value={this.state.trayIconTheme}
|
||||||
|
onChange={this.handleChangeTrayIconTheme}
|
||||||
|
>{'Icon theme (Need to restart the application)'}
|
||||||
|
<option value='light'>{'Light'}</option>
|
||||||
|
<option value='dark'>{'Dark'}</option>
|
||||||
|
</Checkbox>);
|
||||||
|
}
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='inputDisableWebSecurity'
|
||||||
|
id='inputDisableWebSecurity'
|
||||||
|
ref='disablewebsecurity'
|
||||||
|
checked={this.state.disablewebsecurity}
|
||||||
|
onChange={this.handleChangeDisableWebSecurity}
|
||||||
|
>{'Allow mixed content (Enabling allows both secure and insecure content, images and scripts to render and execute. Disabling allows only secure content.)'}</Checkbox>);
|
||||||
|
|
||||||
|
//OSX has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX
|
||||||
|
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='inputAutoStart'
|
||||||
|
id='inputAutoStart'
|
||||||
|
ref='autostart'
|
||||||
|
checked={this.state.autostart}
|
||||||
|
onChange={this.handleChangeAutoStart}
|
||||||
|
>{'Start app on login.'}</Checkbox>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='inputMinimizeToTray'
|
||||||
|
id='inputMinimizeToTray'
|
||||||
|
ref='minimizeToTray'
|
||||||
|
disabled={!this.state.showTrayIcon || !this.state.trayWasVisible}
|
||||||
|
checked={this.state.minimizeToTray}
|
||||||
|
onChange={this.handleChangeMinimizeToTray}
|
||||||
|
>{this.state.trayWasVisible || !this.state.showTrayIcon ? 'Leave app running in notification area when the window is closed' : 'Leave app running in notification area when the window is closed (available on next restart)'}</Checkbox>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='inputToggleWindowOnTrayIconClick'
|
||||||
|
id='inputToggleWindowOnTrayIconClick'
|
||||||
|
ref='toggleWindowOnTrayIconClick'
|
||||||
|
checked={this.state.toggleWindowOnTrayIconClick}
|
||||||
|
onChange={this.handleChangeToggleWindowOnTrayIconClick}
|
||||||
|
>{'Toggle window visibility when clicking on the tray icon.'}</Checkbox>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='inputShowUnreadBadge'
|
||||||
|
id='inputShowUnreadBadge'
|
||||||
|
ref='showUnreadBadge'
|
||||||
|
checked={this.state.showUnreadBadge}
|
||||||
|
onChange={this.handleShowUnreadBadge}
|
||||||
|
>{'Show red badge on taskbar icon to indicate unread messages. Regardless of this setting, mentions are always indicated with a red badge and item count on the taskbar icon.'}</Checkbox>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||||
|
options.push(
|
||||||
|
<Checkbox
|
||||||
|
key='flashWindow'
|
||||||
|
id='inputflashWindow'
|
||||||
|
ref='flashWindow'
|
||||||
|
checked={this.state.notifications.flashWindow === 2}
|
||||||
|
onChange={this.handleFlashWindow}
|
||||||
|
>{'Flash the taskbar icon when a new message is received.'}</Checkbox>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsPage = {
|
||||||
|
navbar: {
|
||||||
|
backgroundColor: '#fff'
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: '0',
|
||||||
|
top: '10px',
|
||||||
|
fontSize: '35px',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
color: '#bbb',
|
||||||
|
cursor: 'pointer'
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: '24px',
|
||||||
|
margin: '0',
|
||||||
|
padding: '1em 0'
|
||||||
|
},
|
||||||
|
sectionHeading: {
|
||||||
|
fontSize: '20px',
|
||||||
|
margin: '0',
|
||||||
|
padding: '1em 0'
|
||||||
|
},
|
||||||
|
sectionHeadingLink: {
|
||||||
|
marginTop: '24px',
|
||||||
|
display: 'inline-block',
|
||||||
|
fontSize: '15px'
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
padding: '0.4em 0'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var optionsRow = (options.length > 0) ? (
|
||||||
|
<Row>
|
||||||
|
<Col md={12}>
|
||||||
|
<h2 style={settingsPage.sectionHeading}>{'App options'}</h2>
|
||||||
|
{ options.map((opt, i) => (
|
||||||
|
<FormGroup key={`fromGroup${i}`}>
|
||||||
|
{opt}
|
||||||
|
</FormGroup>
|
||||||
|
)) }
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Navbar
|
||||||
|
className='navbar-fixed-top'
|
||||||
|
style={settingsPage.navbar}
|
||||||
|
>
|
||||||
|
<div style={{position: 'relative'}}>
|
||||||
|
<h1 style={settingsPage.heading}>{'Settings'}</h1>
|
||||||
|
<div
|
||||||
|
style={settingsPage.close}
|
||||||
|
onClick={this.handleCancel}
|
||||||
|
>
|
||||||
|
<span>{'×'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Navbar>
|
||||||
|
<Grid
|
||||||
|
className='settingsPage'
|
||||||
|
style={{padding: '100px 15px'}}
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<Col
|
||||||
|
md={10}
|
||||||
|
xs={8}
|
||||||
|
>
|
||||||
|
<h2 style={settingsPage.sectionHeading}>{'Team Management'}</h2>
|
||||||
|
</Col>
|
||||||
|
<Col
|
||||||
|
md={2}
|
||||||
|
xs={4}
|
||||||
|
>
|
||||||
|
<p className='text-right'>
|
||||||
|
<a
|
||||||
|
style={settingsPage.sectionHeadingLink}
|
||||||
|
href='#'
|
||||||
|
onClick={this.toggleShowTeamForm}
|
||||||
|
>{'⊞ Add new team'}</a>
|
||||||
|
</p>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{ teamsRow }
|
||||||
|
<hr/>
|
||||||
|
{ optionsRow }
|
||||||
|
</Grid>
|
||||||
|
<Navbar className='navbar-fixed-bottom'>
|
||||||
|
<div
|
||||||
|
className='text-right'
|
||||||
|
style={settingsPage.footer}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
id='btnCancel'
|
||||||
|
className='btn-link'
|
||||||
|
onClick={this.handleCancel}
|
||||||
|
>{'Cancel'}</Button>
|
||||||
|
{ ' ' }
|
||||||
|
<Button
|
||||||
|
id='btnSave'
|
||||||
|
className='navbar-btn'
|
||||||
|
bsStyle='primary'
|
||||||
|
onClick={this.handleSave}
|
||||||
|
disabled={this.state.teams.length === 0}
|
||||||
|
>{'Save'}</Button>
|
||||||
|
</div>
|
||||||
|
</Navbar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = SettingsPage;
|
90
src/browser/components/TabBar.jsx
Normal file
90
src/browser/components/TabBar.jsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const {Nav, NavItem} = require('react-bootstrap');
|
||||||
|
|
||||||
|
class TabBar extends React.Component {
|
||||||
|
render() {
|
||||||
|
var self = this;
|
||||||
|
var tabs = this.props.teams.map((team, index) => {
|
||||||
|
var unreadCount = 0;
|
||||||
|
var badgeStyle = {
|
||||||
|
background: '#FF1744',
|
||||||
|
float: 'right',
|
||||||
|
color: 'white',
|
||||||
|
minWidth: '19px',
|
||||||
|
fontSize: '12px',
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: '20px',
|
||||||
|
height: '19px',
|
||||||
|
marginLeft: '5px',
|
||||||
|
borderRadius: '50%'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (self.props.unreadCounts[index] > 0) {
|
||||||
|
unreadCount = self.props.unreadCounts[index];
|
||||||
|
}
|
||||||
|
if (self.props.unreadAtActive[index]) {
|
||||||
|
unreadCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mentionCount = 0;
|
||||||
|
if (self.props.mentionCounts[index] > 0) {
|
||||||
|
mentionCount = self.props.mentionCounts[index];
|
||||||
|
}
|
||||||
|
if (self.props.mentionAtActiveCounts[index] > 0) {
|
||||||
|
mentionCount += self.props.mentionAtActiveCounts[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
var badgeDiv;
|
||||||
|
if (mentionCount !== 0) {
|
||||||
|
badgeDiv = (
|
||||||
|
<div style={badgeStyle}>
|
||||||
|
{mentionCount}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
var id = 'teamTabItem' + index;
|
||||||
|
if (unreadCount === 0) {
|
||||||
|
return (
|
||||||
|
<NavItem
|
||||||
|
className='teamTabItem'
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
eventKey={index}
|
||||||
|
>
|
||||||
|
{ team.name }
|
||||||
|
{ ' ' }
|
||||||
|
{ badgeDiv }
|
||||||
|
</NavItem>);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<NavItem
|
||||||
|
className='teamTabItem'
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
eventKey={index}
|
||||||
|
>
|
||||||
|
<b>{ team.name }</b>
|
||||||
|
{ ' ' }
|
||||||
|
{ badgeDiv }
|
||||||
|
</NavItem>);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Nav
|
||||||
|
id={this.props.id}
|
||||||
|
bsStyle='tabs'
|
||||||
|
activeKey={this.props.activeKey}
|
||||||
|
onSelect={this.props.onSelect}
|
||||||
|
>
|
||||||
|
{ tabs }
|
||||||
|
</Nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TabBar.propTypes = {
|
||||||
|
activeKey: React.PropTypes.number,
|
||||||
|
id: React.PropTypes.string,
|
||||||
|
onSelect: React.PropTypes.func,
|
||||||
|
teams: React.PropTypes.array
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = TabBar;
|
107
src/browser/components/TeamList.jsx
Normal file
107
src/browser/components/TeamList.jsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const {ListGroup} = require('react-bootstrap');
|
||||||
|
const TeamListItem = require('./TeamListItem.jsx');
|
||||||
|
const TeamListItemNew = require('./TeamListItemNew.jsx');
|
||||||
|
|
||||||
|
const TeamList = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
onTeamsChange: React.PropTypes.func,
|
||||||
|
showAddTeamForm: React.PropTypes.bool,
|
||||||
|
teams: React.PropTypes.array
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
showTeamListItemNew: false,
|
||||||
|
team: {
|
||||||
|
url: '',
|
||||||
|
name: '',
|
||||||
|
index: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleTeamRemove(index) {
|
||||||
|
console.log(index);
|
||||||
|
var teams = this.props.teams;
|
||||||
|
teams.splice(index, 1);
|
||||||
|
this.props.onTeamsChange(teams);
|
||||||
|
},
|
||||||
|
handleTeamAdd(team) {
|
||||||
|
var teams = this.props.teams;
|
||||||
|
|
||||||
|
// check if team already exists and then change existing team or add new one
|
||||||
|
if ((typeof team.index !== 'undefined') && teams[team.index]) {
|
||||||
|
teams[team.index].name = team.name;
|
||||||
|
teams[team.index].url = team.url;
|
||||||
|
} else {
|
||||||
|
teams.push(team);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
showTeamListItemNew: false,
|
||||||
|
team: {
|
||||||
|
url: '',
|
||||||
|
name: '',
|
||||||
|
index: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onTeamsChange(teams);
|
||||||
|
},
|
||||||
|
handleTeamEditing(teamName, teamUrl, teamIndex) {
|
||||||
|
this.setState({
|
||||||
|
showTeamListItemNew: true,
|
||||||
|
team: {
|
||||||
|
url: teamUrl,
|
||||||
|
name: teamName,
|
||||||
|
index: teamIndex
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
var self = this;
|
||||||
|
var teamNodes = this.props.teams.map((team, i) => {
|
||||||
|
function handleTeamRemove() {
|
||||||
|
self.handleTeamRemove(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTeamEditing() {
|
||||||
|
self.handleTeamEditing(team.name, team.url, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TeamListItem
|
||||||
|
index={i}
|
||||||
|
key={'teamListItem' + i}
|
||||||
|
name={team.name}
|
||||||
|
url={team.url}
|
||||||
|
onTeamRemove={handleTeamRemove}
|
||||||
|
onTeamEditing={handleTeamEditing}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
var addTeamForm;
|
||||||
|
if (this.props.showAddTeamForm || this.state.showTeamListItemNew) {
|
||||||
|
addTeamForm = (
|
||||||
|
<TeamListItemNew
|
||||||
|
key={this.state.team.index}
|
||||||
|
onTeamAdd={this.handleTeamAdd}
|
||||||
|
teamIndex={this.state.team.index}
|
||||||
|
teamName={this.state.team.name}
|
||||||
|
teamUrl={this.state.team.url}
|
||||||
|
/>);
|
||||||
|
} else {
|
||||||
|
addTeamForm = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListGroup className='teamList'>
|
||||||
|
{ teamNodes }
|
||||||
|
{ addTeamForm }
|
||||||
|
</ListGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = TeamList;
|
53
src/browser/components/TeamListItem.jsx
Normal file
53
src/browser/components/TeamListItem.jsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const React = require('react');
|
||||||
|
|
||||||
|
class TeamListItem extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleTeamRemove = this.handleTeamRemove.bind(this);
|
||||||
|
this.handleTeamEditing = this.handleTeamEditing.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTeamRemove() {
|
||||||
|
this.props.onTeamRemove();
|
||||||
|
}
|
||||||
|
handleTeamEditing() {
|
||||||
|
this.props.onTeamEditing();
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
var style = {
|
||||||
|
left: {
|
||||||
|
display: 'inline-block'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className='teamListItem list-group-item'>
|
||||||
|
<div style={style.left}>
|
||||||
|
<h4 className='list-group-item-heading'>{ this.props.name }</h4>
|
||||||
|
<p className='list-group-item-text'>
|
||||||
|
{ this.props.url }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='pull-right'>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
onClick={this.handleTeamEditing}
|
||||||
|
>{'Edit'}</a>
|
||||||
|
{' - '}
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
onClick={this.handleTeamRemove}
|
||||||
|
>{'Remove'}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TeamListItem.propTypes = {
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
onTeamEditing: React.PropTypes.func,
|
||||||
|
onTeamRemove: React.PropTypes.func,
|
||||||
|
url: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = TeamListItem;
|
148
src/browser/components/TeamListItemNew.jsx
Normal file
148
src/browser/components/TeamListItemNew.jsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const {findDOMNode} = require('react-dom');
|
||||||
|
const {Button, HelpBlock, ListGroupItem} = require('react-bootstrap');
|
||||||
|
|
||||||
|
const TeamListItemNew = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
onTeamAdd: React.PropTypes.func,
|
||||||
|
teamIndex: React.PropTypes.number,
|
||||||
|
teamName: React.PropTypes.string,
|
||||||
|
teamUrl: React.PropTypes.string
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
name: this.props.teamName,
|
||||||
|
url: this.props.teamUrl,
|
||||||
|
index: this.props.teamIndex,
|
||||||
|
errorMessage: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleSubmit(e) {
|
||||||
|
console.log('submit');
|
||||||
|
e.preventDefault();
|
||||||
|
const errorMessage = this.getValidationErrorMessage();
|
||||||
|
if (errorMessage) {
|
||||||
|
this.setState({
|
||||||
|
errorMessage
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onTeamAdd({
|
||||||
|
name: this.state.name.trim(),
|
||||||
|
url: this.state.url.trim(),
|
||||||
|
index: this.state.index
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
name: '',
|
||||||
|
url: '',
|
||||||
|
index: '',
|
||||||
|
errorMessage: null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleNameChange(e) {
|
||||||
|
console.log('name');
|
||||||
|
this.setState({
|
||||||
|
name: e.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleURLChange(e) {
|
||||||
|
console.log('url');
|
||||||
|
this.setState({
|
||||||
|
url: e.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getValidationErrorMessage() {
|
||||||
|
if (this.state.name.trim() === '') {
|
||||||
|
return 'Name is required.';
|
||||||
|
} else if (this.state.url.trim() === '') {
|
||||||
|
return 'URL is required.';
|
||||||
|
} else if (!(/^https?:\/\/.*/).test(this.state.url.trim())) {
|
||||||
|
return 'URL should start with http:// or https://.';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const inputTeamName = findDOMNode(this.refs.inputTeamName);
|
||||||
|
const setErrorMessage = () => {
|
||||||
|
this.setState({
|
||||||
|
errorMessage: this.getValidationErrorMessage()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
inputTeamName.addEventListener('invalid', setErrorMessage);
|
||||||
|
const inputTeamURL = findDOMNode(this.refs.inputTeamURL);
|
||||||
|
inputTeamURL.addEventListener('invalid', setErrorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var existingTeam = false;
|
||||||
|
if (this.state.name !== '' && this.state.url !== '') {
|
||||||
|
existingTeam = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var btnAddText;
|
||||||
|
if (existingTeam) {
|
||||||
|
btnAddText = 'Save';
|
||||||
|
} else {
|
||||||
|
btnAddText = 'Add';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListGroupItem>
|
||||||
|
<form
|
||||||
|
className='form-inline'
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
>
|
||||||
|
<div className='form-group'>
|
||||||
|
<label htmlFor='inputTeamName'>{'Name'}</label>
|
||||||
|
{ ' ' }
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
required={true}
|
||||||
|
className='form-control'
|
||||||
|
id='inputTeamName'
|
||||||
|
ref='inputTeamName'
|
||||||
|
placeholder='Example team'
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={this.handleNameChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{ ' ' }
|
||||||
|
<div className='form-group'>
|
||||||
|
<label htmlFor='inputTeamURL'>{'URL'}</label>
|
||||||
|
{ ' ' }
|
||||||
|
<input
|
||||||
|
type='url'
|
||||||
|
required={true}
|
||||||
|
className='form-control'
|
||||||
|
id='inputTeamURL'
|
||||||
|
ref='inputTeamURL'
|
||||||
|
placeholder='https://example.com/team'
|
||||||
|
value={this.state.url}
|
||||||
|
onChange={this.handleURLChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{ ' ' }
|
||||||
|
<Button type='submit'>
|
||||||
|
{ btnAddText }
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
{ (() => {
|
||||||
|
if (this.state.errorMessage !== null) {
|
||||||
|
return (
|
||||||
|
<HelpBlock style={{color: '#777777'}}>
|
||||||
|
{ this.state.errorMessage }
|
||||||
|
</HelpBlock>);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})() }
|
||||||
|
</ListGroupItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = TeamListItemNew;
|
@@ -6,22 +6,8 @@ window.eval = global.eval = () => {
|
|||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require('react-dom');
|
const ReactDOM = require('react-dom');
|
||||||
const ReactBootstrap = require('react-bootstrap');
|
const {remote, ipcRenderer} = require('electron');
|
||||||
|
const MainPage = require('./components/MainPage.jsx');
|
||||||
const Grid = ReactBootstrap.Grid;
|
|
||||||
const Row = ReactBootstrap.Row;
|
|
||||||
const Col = ReactBootstrap.Col;
|
|
||||||
const Nav = ReactBootstrap.Nav;
|
|
||||||
const NavItem = ReactBootstrap.NavItem;
|
|
||||||
|
|
||||||
const LoginModal = require('./components/loginModal.jsx');
|
|
||||||
|
|
||||||
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 settings = require('../common/settings');
|
const settings = require('../common/settings');
|
||||||
const badge = require('./js/badge');
|
const badge = require('./js/badge');
|
||||||
@@ -39,635 +25,6 @@ if (config.teams.length === 0) {
|
|||||||
window.location = 'settings.html';
|
window.location = 'settings.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
var MainPage = React.createClass({
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
key: 0,
|
|
||||||
unreadCounts: new Array(this.props.teams.length),
|
|
||||||
mentionCounts: new Array(this.props.teams.length),
|
|
||||||
unreadAtActive: new Array(this.props.teams.length),
|
|
||||||
mentionAtActiveCounts: new Array(this.props.teams.length),
|
|
||||||
loginQueue: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount() {
|
|
||||||
var self = this;
|
|
||||||
ipcRenderer.on('login-request', (event, request, authInfo) => {
|
|
||||||
self.setState({
|
|
||||||
loginRequired: true
|
|
||||||
});
|
|
||||||
const loginQueue = self.state.loginQueue;
|
|
||||||
loginQueue.push({
|
|
||||||
request,
|
|
||||||
authInfo
|
|
||||||
});
|
|
||||||
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', () => {
|
|
||||||
this.handleSelect(this.state.key + 1);
|
|
||||||
});
|
|
||||||
ipcRenderer.on('select-previous-tab', () => {
|
|
||||||
this.handleSelect(this.state.key - 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// reload the activated tab
|
|
||||||
ipcRenderer.on('reload-tab', () => {
|
|
||||||
this.refs[`mattermostView${this.state.key}`].reload();
|
|
||||||
});
|
|
||||||
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', () => {
|
|
||||||
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', () => {
|
|
||||||
const webview = document.getElementById('mattermostView' + self.state.key);
|
|
||||||
webview.send('activate-search-box-in-channel');
|
|
||||||
});
|
|
||||||
|
|
||||||
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', () => {
|
|
||||||
currentWindow.removeListener('focus', focusListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
//goBack and goForward
|
|
||||||
ipcRenderer.on('go-back', () => {
|
|
||||||
const mattermost = self.refs[`mattermostView${self.state.key}`];
|
|
||||||
if (mattermost.canGoBack()) {
|
|
||||||
mattermost.goBack();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('go-forward', () => {
|
|
||||||
const mattermost = self.refs[`mattermostView${self.state.key}`];
|
|
||||||
if (mattermost.canGoForward()) {
|
|
||||||
mattermost.goForward();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
if (prevState.key !== this.state.key) { // i.e. When tab has been changed
|
|
||||||
this.refs[`mattermostView${this.state.key}`].focusOnWebView();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSelect(key) {
|
|
||||||
const newKey = (this.props.teams.length + key) % this.props.teams.length;
|
|
||||||
this.setState({
|
|
||||||
key: newKey
|
|
||||||
});
|
|
||||||
this.handleOnTeamFocused(newKey);
|
|
||||||
|
|
||||||
var webview = document.getElementById('mattermostView' + newKey);
|
|
||||||
ipcRenderer.send('update-title', {
|
|
||||||
title: webview.getTitle()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
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;
|
|
||||||
if (isMentioned) {
|
|
||||||
mentionAtActiveCounts[index]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
unreadCounts,
|
|
||||||
mentionCounts,
|
|
||||||
unreadAtActive,
|
|
||||||
mentionAtActiveCounts
|
|
||||||
});
|
|
||||||
this.handleUnreadCountTotalChange();
|
|
||||||
},
|
|
||||||
markReadAtActive(index) {
|
|
||||||
var unreadAtActive = this.state.unreadAtActive;
|
|
||||||
var mentionAtActiveCounts = this.state.mentionAtActiveCounts;
|
|
||||||
unreadAtActive[index] = false;
|
|
||||||
mentionAtActiveCounts[index] = 0;
|
|
||||||
this.setState({
|
|
||||||
unreadAtActive,
|
|
||||||
mentionAtActiveCounts
|
|
||||||
});
|
|
||||||
this.handleUnreadCountTotalChange();
|
|
||||||
},
|
|
||||||
handleUnreadCountTotalChange() {
|
|
||||||
if (this.props.onUnreadCountChange) {
|
|
||||||
var allUnreadCount = this.state.unreadCounts.reduce((prev, curr) => {
|
|
||||||
return prev + curr;
|
|
||||||
}, 0);
|
|
||||||
this.state.unreadAtActive.forEach((state) => {
|
|
||||||
if (state) {
|
|
||||||
allUnreadCount += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var allMentionCount = this.state.mentionCounts.reduce((prev, curr) => {
|
|
||||||
return prev + curr;
|
|
||||||
}, 0);
|
|
||||||
this.state.mentionAtActiveCounts.forEach((count) => {
|
|
||||||
allMentionCount += count;
|
|
||||||
});
|
|
||||||
this.props.onUnreadCountChange(allUnreadCount, allMentionCount);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleOnTeamFocused(index) {
|
|
||||||
// Turn off the flag to indicate whether unread message of active channel contains at current tab.
|
|
||||||
this.markReadAtActive(index);
|
|
||||||
},
|
|
||||||
|
|
||||||
visibleStyle(visible) {
|
|
||||||
var visibility = visible ? 'visible' : 'hidden';
|
|
||||||
return {
|
|
||||||
position: 'absolute',
|
|
||||||
top: (this.props.teams.length > 1) ? 42 : 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
visibility
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleLogin(request, username, password) {
|
|
||||||
ipcRenderer.send('login-credentials', request, username, password);
|
|
||||||
const loginQueue = this.state.loginQueue;
|
|
||||||
loginQueue.shift();
|
|
||||||
this.setState(loginQueue);
|
|
||||||
},
|
|
||||||
handleLoginCancel() {
|
|
||||||
const loginQueue = this.state.loginQueue;
|
|
||||||
loginQueue.shift();
|
|
||||||
this.setState(loginQueue);
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var tabsRow;
|
|
||||||
if (this.props.teams.length > 1) {
|
|
||||||
tabsRow = (
|
|
||||||
<Row>
|
|
||||||
<TabBar
|
|
||||||
id='tabBar'
|
|
||||||
teams={this.props.teams}
|
|
||||||
unreadCounts={this.state.unreadCounts}
|
|
||||||
mentionCounts={this.state.mentionCounts}
|
|
||||||
unreadAtActive={this.state.unreadAtActive}
|
|
||||||
mentionAtActiveCounts={this.state.mentionAtActiveCounts}
|
|
||||||
activeKey={this.state.key}
|
|
||||||
onSelect={this.handleSelect}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 isActive = self.state.key === index;
|
|
||||||
return (
|
|
||||||
<MattermostView
|
|
||||||
key={id}
|
|
||||||
id={id}
|
|
||||||
style={self.visibleStyle(isActive)}
|
|
||||||
src={team.url}
|
|
||||||
name={team.name}
|
|
||||||
onUnreadCountChange={handleUnreadCountChange}
|
|
||||||
onNotificationClick={handleNotificationClick}
|
|
||||||
ref={id}
|
|
||||||
active={isActive}
|
|
||||||
/>);
|
|
||||||
});
|
|
||||||
var viewsRow = (
|
|
||||||
<Row>
|
|
||||||
{views}
|
|
||||||
</Row>);
|
|
||||||
|
|
||||||
var request = null;
|
|
||||||
var authServerURL = null;
|
|
||||||
var authInfo = null;
|
|
||||||
if (this.state.loginQueue.length !== 0) {
|
|
||||||
request = this.state.loginQueue[0].request;
|
|
||||||
const tmpURL = url.parse(this.state.loginQueue[0].request.url);
|
|
||||||
authServerURL = `${tmpURL.protocol}//${tmpURL.host}`;
|
|
||||||
authInfo = this.state.loginQueue[0].authInfo;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<LoginModal
|
|
||||||
show={this.state.loginQueue.length !== 0}
|
|
||||||
request={request}
|
|
||||||
authInfo={authInfo}
|
|
||||||
authServerURL={authServerURL}
|
|
||||||
onLogin={this.handleLogin}
|
|
||||||
onCancel={this.handleLoginCancel}
|
|
||||||
/>
|
|
||||||
<Grid fluid>
|
|
||||||
{ tabsRow }
|
|
||||||
{ viewsRow }
|
|
||||||
</Grid>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var TabBar = React.createClass({
|
|
||||||
render() {
|
|
||||||
var self = this;
|
|
||||||
var tabs = this.props.teams.map((team, index) => {
|
|
||||||
var unreadCount = 0;
|
|
||||||
var badgeStyle = {
|
|
||||||
background: '#FF1744',
|
|
||||||
float: 'right',
|
|
||||||
color: 'white',
|
|
||||||
minWidth: '19px',
|
|
||||||
fontSize: '12px',
|
|
||||||
textAlign: 'center',
|
|
||||||
lineHeight: '20px',
|
|
||||||
height: '19px',
|
|
||||||
marginLeft: '5px',
|
|
||||||
borderRadius: '50%'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (self.props.unreadCounts[index] > 0) {
|
|
||||||
unreadCount = self.props.unreadCounts[index];
|
|
||||||
}
|
|
||||||
if (self.props.unreadAtActive[index]) {
|
|
||||||
unreadCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mentionCount = 0;
|
|
||||||
if (self.props.mentionCounts[index] > 0) {
|
|
||||||
mentionCount = self.props.mentionCounts[index];
|
|
||||||
}
|
|
||||||
if (self.props.mentionAtActiveCounts[index] > 0) {
|
|
||||||
mentionCount += self.props.mentionAtActiveCounts[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
var badgeDiv;
|
|
||||||
if (mentionCount !== 0) {
|
|
||||||
badgeDiv = (
|
|
||||||
<div style={badgeStyle}>
|
|
||||||
{mentionCount}
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
var id = 'teamTabItem' + index;
|
|
||||||
if (unreadCount === 0) {
|
|
||||||
return (
|
|
||||||
<NavItem
|
|
||||||
className='teamTabItem'
|
|
||||||
key={id}
|
|
||||||
id={id}
|
|
||||||
eventKey={index}
|
|
||||||
>
|
|
||||||
{ team.name }
|
|
||||||
{ ' ' }
|
|
||||||
{ badgeDiv }
|
|
||||||
</NavItem>);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<NavItem
|
|
||||||
className='teamTabItem'
|
|
||||||
key={id}
|
|
||||||
id={id}
|
|
||||||
eventKey={index}
|
|
||||||
>
|
|
||||||
<b>{ team.name }</b>
|
|
||||||
{ ' ' }
|
|
||||||
{ badgeDiv }
|
|
||||||
</NavItem>);
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Nav
|
|
||||||
id={this.props.id}
|
|
||||||
bsStyle='tabs'
|
|
||||||
activeKey={this.props.activeKey}
|
|
||||||
onSelect={this.props.onSelect}
|
|
||||||
>
|
|
||||||
{ tabs }
|
|
||||||
</Nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var MattermostView = React.createClass({
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
errorInfo: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned) {
|
|
||||||
if (this.props.onUnreadCountChange) {
|
|
||||||
this.props.onUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
var self = this;
|
|
||||||
var webview = ReactDOM.findDOMNode(this.refs.webview);
|
|
||||||
|
|
||||||
// This option allows insecure content, when set to true it is possible to
|
|
||||||
// load content via HTTP while the mattermost server serves HTTPS
|
|
||||||
if (config.disablewebsecurity === true) {
|
|
||||||
webview.setAttribute('webpreferences', 'allowDisplayingInsecureContent');
|
|
||||||
}
|
|
||||||
|
|
||||||
webview.addEventListener('did-fail-load', (e) => {
|
|
||||||
console.log(self.props.name, 'webview did-fail-load', e);
|
|
||||||
if (e.errorCode === -3) { // An operation was aborted (due to user action).
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.setState({
|
|
||||||
errorInfo: e
|
|
||||||
});
|
|
||||||
function reload() {
|
|
||||||
window.removeEventListener('online', reload);
|
|
||||||
self.reload();
|
|
||||||
}
|
|
||||||
if (navigator.onLine) {
|
|
||||||
setTimeout(reload, 30000);
|
|
||||||
} else {
|
|
||||||
window.addEventListener('online', reload);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open link in browserWindow. for exmaple, attached files.
|
|
||||||
webview.addEventListener('new-window', (e) => {
|
|
||||||
var currentURL = url.parse(webview.getURL());
|
|
||||||
var destURL = url.parse(e.url);
|
|
||||||
if (destURL.protocol !== 'http:' && destURL.protocol !== 'https:') {
|
|
||||||
ipcRenderer.send('confirm-protocol', destURL.protocol, e.url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (currentURL.host === destURL.host) {
|
|
||||||
// New window should disable nodeIntergration.
|
|
||||||
window.open(e.url, 'Mattermost', 'nodeIntegration=no');
|
|
||||||
} else {
|
|
||||||
// if the link is external, use default browser.
|
|
||||||
shell.openExternal(e.url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
webview.addEventListener('dom-ready', () => {
|
|
||||||
// webview.openDevTools();
|
|
||||||
|
|
||||||
// Use 'Meiryo UI' and 'MS Gothic' to prevent CJK fonts on Windows(JP).
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
function applyCssFile(cssFile) {
|
|
||||||
fs.readFile(cssFile, 'utf8', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
webview.insertCSS(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
osLocale((err, locale) => {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (locale === 'ja_JP') {
|
|
||||||
applyCssFile(__dirname + '/css/jp_fonts.css');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
electronContextMenu({
|
|
||||||
window: webview
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
webview.addEventListener('ipc-message', (event) => {
|
|
||||||
switch (event.channel) {
|
|
||||||
case 'onUnreadCountChange':
|
|
||||||
var unreadCount = event.args[0];
|
|
||||||
var mentionCount = event.args[1];
|
|
||||||
|
|
||||||
// isUnread and isMentioned is pulse flag.
|
|
||||||
var isUnread = event.args[2];
|
|
||||||
var isMentioned = event.args[3];
|
|
||||||
self.handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned);
|
|
||||||
break;
|
|
||||||
case 'onNotificationClick':
|
|
||||||
self.props.onNotificationClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
webview.addEventListener('page-title-updated', (event) => {
|
|
||||||
if (self.props.active) {
|
|
||||||
ipcRenderer.send('update-title', {
|
|
||||||
title: event.title
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
webview.addEventListener('console-message', (e) => {
|
|
||||||
const message = `[${this.props.name}] ${e.message}`;
|
|
||||||
switch (e.level) {
|
|
||||||
case 0:
|
|
||||||
console.log(message);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
console.warn(message);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
console.error(message);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
reload() {
|
|
||||||
this.setState({
|
|
||||||
errorInfo: null
|
|
||||||
});
|
|
||||||
var webview = ReactDOM.findDOMNode(this.refs.webview);
|
|
||||||
webview.reload();
|
|
||||||
},
|
|
||||||
clearCacheAndReload() {
|
|
||||||
this.setState({
|
|
||||||
errorInfo: null
|
|
||||||
});
|
|
||||||
var webContents = ReactDOM.findDOMNode(this.refs.webview).getWebContents();
|
|
||||||
webContents.session.clearCache(() => {
|
|
||||||
webContents.reload();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
focusOnWebView() {
|
|
||||||
const webview = ReactDOM.findDOMNode(this.refs.webview);
|
|
||||||
if (!webview.getWebContents().isFocused()) {
|
|
||||||
webview.focus();
|
|
||||||
webview.getWebContents().focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
canGoBack() {
|
|
||||||
const webview = ReactDOM.findDOMNode(this.refs.webview);
|
|
||||||
return webview.getWebContents().canGoBack();
|
|
||||||
},
|
|
||||||
|
|
||||||
canGoForward() {
|
|
||||||
const webview = ReactDOM.findDOMNode(this.refs.webview);
|
|
||||||
return webview.getWebContents().canGoForward();
|
|
||||||
},
|
|
||||||
|
|
||||||
goBack() {
|
|
||||||
const webview = ReactDOM.findDOMNode(this.refs.webview);
|
|
||||||
webview.getWebContents().goBack();
|
|
||||||
},
|
|
||||||
|
|
||||||
goForward() {
|
|
||||||
const webview = ReactDOM.findDOMNode(this.refs.webview);
|
|
||||||
webview.getWebContents().goForward();
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const errorView = this.state.errorInfo ? (
|
|
||||||
<ErrorView
|
|
||||||
id={this.props.id + '-fail'}
|
|
||||||
style={this.props.style}
|
|
||||||
className='errorView'
|
|
||||||
errorInfo={this.state.errorInfo}
|
|
||||||
/>) : null;
|
|
||||||
|
|
||||||
// 'disablewebsecurity' is necessary to display external images.
|
|
||||||
// However, it allows also CSS/JavaScript.
|
|
||||||
// So webview should use 'allowDisplayingInsecureContent' as same as BrowserWindow.
|
|
||||||
|
|
||||||
// Need to keep webview mounted when failed to load.
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ errorView }
|
|
||||||
<webview
|
|
||||||
id={this.props.id}
|
|
||||||
className='mattermostView'
|
|
||||||
style={this.props.style}
|
|
||||||
preload='webview/mattermost.js'
|
|
||||||
src={this.props.src}
|
|
||||||
ref='webview'
|
|
||||||
nodeintegration='false'
|
|
||||||
/>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ErrorCode: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h
|
|
||||||
const errorPage = {
|
|
||||||
tableStyle: {
|
|
||||||
display: 'table',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
top: '0',
|
|
||||||
left: '0'
|
|
||||||
},
|
|
||||||
|
|
||||||
cellStyle: {
|
|
||||||
display: 'table-cell',
|
|
||||||
verticalAlign: 'top',
|
|
||||||
paddingTop: '2em'
|
|
||||||
},
|
|
||||||
|
|
||||||
bullets: {
|
|
||||||
paddingLeft: '15px',
|
|
||||||
lineHeight: '1.7'
|
|
||||||
},
|
|
||||||
|
|
||||||
techInfo: {
|
|
||||||
fontSize: '12px',
|
|
||||||
color: '#aaa'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var ErrorView = React.createClass({
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
id={this.props.id}
|
|
||||||
style={this.props.style}
|
|
||||||
>
|
|
||||||
<div style={errorPage.tableStyle}>
|
|
||||||
<div style={errorPage.cellStyle}>
|
|
||||||
<Row>
|
|
||||||
<Col
|
|
||||||
xs={0}
|
|
||||||
sm={1}
|
|
||||||
md={1}
|
|
||||||
lg={2}
|
|
||||||
/>
|
|
||||||
<Col
|
|
||||||
xs={12}
|
|
||||||
sm={10}
|
|
||||||
md={10}
|
|
||||||
lg={8}
|
|
||||||
>
|
|
||||||
<h2>{'Cannot connect to Mattermost'}</h2>
|
|
||||||
<hr/>
|
|
||||||
<p>{'We\'re having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work please verify that:'}</p>
|
|
||||||
<br/>
|
|
||||||
<ul style={errorPage.bullets}>
|
|
||||||
<li>{'Your computer is connected to the internet.'}</li>
|
|
||||||
<li>{'The Mattermost URL '}
|
|
||||||
<a href={this.props.errorInfo.validatedURL}>
|
|
||||||
{this.props.errorInfo.validatedURL}
|
|
||||||
</a>{' is correct.'}</li>
|
|
||||||
<li>{'You can reach '}
|
|
||||||
<a href={this.props.errorInfo.validatedURL}>
|
|
||||||
{this.props.errorInfo.validatedURL}
|
|
||||||
</a>{' from a browser window.'}</li>
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
<div style={errorPage.techInfo}>
|
|
||||||
{this.props.errorInfo.errorDescription}{' ('}
|
|
||||||
{this.props.errorInfo.errorCode }{')'}</div>
|
|
||||||
</Col>
|
|
||||||
<Col
|
|
||||||
xs={0}
|
|
||||||
sm={1}
|
|
||||||
md={1}
|
|
||||||
lg={2}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function showUnreadBadgeWindows(unreadCount, mentionCount) {
|
function showUnreadBadgeWindows(unreadCount, mentionCount) {
|
||||||
function sendBadge(dataURL, description) {
|
function sendBadge(dataURL, description) {
|
||||||
// window.setOverlayIcon() does't work with NativeImage across remote boundaries.
|
// window.setOverlayIcon() does't work with NativeImage across remote boundaries.
|
||||||
@@ -734,6 +91,7 @@ function showUnreadBadge(unreadCount, mentionCount) {
|
|||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<MainPage
|
<MainPage
|
||||||
|
disablewebsecurity={config.disablewebsecurity}
|
||||||
teams={config.teams}
|
teams={config.teams}
|
||||||
onUnreadCountChange={showUnreadBadge}
|
onUnreadCountChange={showUnreadBadge}
|
||||||
/>,
|
/>,
|
||||||
|
@@ -4,666 +4,11 @@ window.eval = global.eval = () => {
|
|||||||
throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.');
|
throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.');
|
||||||
};
|
};
|
||||||
|
|
||||||
const {remote, ipcRenderer} = require('electron');
|
const {remote} = require('electron');
|
||||||
const settings = require('../common/settings');
|
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require('react-dom');
|
const ReactDOM = require('react-dom');
|
||||||
const {Grid, Row, Col, Input, Button, ListGroup, ListGroupItem, HelpBlock, Navbar} = require('react-bootstrap');
|
const SettingsPage = require('./components/SettingsPage.jsx');
|
||||||
var AutoLaunch = require('auto-launch');
|
|
||||||
|
|
||||||
var appLauncher = new AutoLaunch({
|
|
||||||
name: 'Mattermost',
|
|
||||||
isHidden: true
|
|
||||||
});
|
|
||||||
|
|
||||||
function backToIndex() {
|
|
||||||
remote.getCurrentWindow().loadURL('file://' + __dirname + '/index.html');
|
|
||||||
}
|
|
||||||
|
|
||||||
var SettingsPage = React.createClass({
|
|
||||||
getInitialState() {
|
|
||||||
var initialState;
|
|
||||||
try {
|
|
||||||
initialState = settings.readFileSync(this.props.configFile);
|
|
||||||
} catch (e) {
|
|
||||||
initialState = settings.loadDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialState.showAddTeamForm = false;
|
|
||||||
initialState.trayWasVisible = remote.getCurrentWindow().trayWasVisible;
|
|
||||||
|
|
||||||
return initialState;
|
|
||||||
},
|
|
||||||
componentDidMount() {
|
|
||||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
|
||||||
var self = this;
|
|
||||||
appLauncher.isEnabled().then((enabled) => {
|
|
||||||
self.setState({
|
|
||||||
autostart: enabled
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleTeamsChange(teams) {
|
|
||||||
this.setState({
|
|
||||||
showAddTeamForm: false,
|
|
||||||
teams
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleSave() {
|
|
||||||
var config = {
|
|
||||||
teams: this.state.teams,
|
|
||||||
hideMenuBar: this.state.hideMenuBar,
|
|
||||||
showTrayIcon: this.state.showTrayIcon,
|
|
||||||
trayIconTheme: this.state.trayIconTheme,
|
|
||||||
disablewebsecurity: this.state.disablewebsecurity,
|
|
||||||
version: settings.version,
|
|
||||||
minimizeToTray: this.state.minimizeToTray,
|
|
||||||
toggleWindowOnTrayIconClick: this.state.toggleWindowOnTrayIconClick,
|
|
||||||
notifications: {
|
|
||||||
flashWindow: this.state.notifications.flashWindow
|
|
||||||
},
|
|
||||||
showUnreadBadge: this.state.showUnreadBadge
|
|
||||||
};
|
|
||||||
settings.writeFileSync(this.props.configFile, config);
|
|
||||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
|
||||||
var currentWindow = remote.getCurrentWindow();
|
|
||||||
currentWindow.setAutoHideMenuBar(config.hideMenuBar);
|
|
||||||
currentWindow.setMenuBarVisibility(!config.hideMenuBar);
|
|
||||||
|
|
||||||
var autostart = this.state.autostart;
|
|
||||||
appLauncher.isEnabled().then((enabled) => {
|
|
||||||
if (enabled && !autostart) {
|
|
||||||
appLauncher.disable();
|
|
||||||
} else if (!enabled && autostart) {
|
|
||||||
appLauncher.enable();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcRenderer.send('update-menu', config);
|
|
||||||
ipcRenderer.send('update-config');
|
|
||||||
|
|
||||||
backToIndex();
|
|
||||||
},
|
|
||||||
handleCancel() {
|
|
||||||
backToIndex();
|
|
||||||
},
|
|
||||||
handleChangeDisableWebSecurity() {
|
|
||||||
this.setState({
|
|
||||||
disablewebsecurity: this.refs.disablewebsecurity.getChecked()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleChangeHideMenuBar() {
|
|
||||||
this.setState({
|
|
||||||
hideMenuBar: this.refs.hideMenuBar.getChecked()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleChangeShowTrayIcon() {
|
|
||||||
var shouldShowTrayIcon = this.refs.showTrayIcon.getChecked();
|
|
||||||
this.setState({
|
|
||||||
showTrayIcon: shouldShowTrayIcon
|
|
||||||
});
|
|
||||||
|
|
||||||
if (process.platform === 'darwin' && !shouldShowTrayIcon) {
|
|
||||||
this.setState({
|
|
||||||
minimizeToTray: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleChangeTrayIconTheme() {
|
|
||||||
this.setState({
|
|
||||||
trayIconTheme: this.refs.trayIconTheme.getValue()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleChangeAutoStart() {
|
|
||||||
this.setState({
|
|
||||||
autostart: this.refs.autostart.getChecked()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleChangeMinimizeToTray() {
|
|
||||||
var shouldMinimizeToTray =
|
|
||||||
(process.platform !== 'darwin' || this.refs.showTrayIcon.getChecked()) &&
|
|
||||||
this.refs.minimizeToTray.getChecked();
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
minimizeToTray: shouldMinimizeToTray
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleChangeToggleWindowOnTrayIconClick() {
|
|
||||||
this.setState({
|
|
||||||
toggleWindowOnTrayIconClick: this.refs.toggleWindowOnTrayIconClick.getChecked()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
toggleShowTeamForm() {
|
|
||||||
this.setState({
|
|
||||||
showAddTeamForm: !this.state.showAddTeamForm
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleFlashWindow() {
|
|
||||||
this.setState({
|
|
||||||
notifications: {
|
|
||||||
flashWindow: this.refs.flashWindow.getChecked() ? 2 : 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleShowUnreadBadge() {
|
|
||||||
this.setState({
|
|
||||||
showUnreadBadge: this.refs.showUnreadBadge.getChecked()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
var teamsRow = (
|
|
||||||
<Row>
|
|
||||||
<Col md={12}>
|
|
||||||
<TeamList
|
|
||||||
teams={this.state.teams}
|
|
||||||
showAddTeamForm={this.state.showAddTeamForm}
|
|
||||||
onTeamsChange={this.handleTeamsChange}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
|
|
||||||
var options = [];
|
|
||||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='inputHideMenuBar'
|
|
||||||
id='inputHideMenuBar'
|
|
||||||
ref='hideMenuBar'
|
|
||||||
type='checkbox'
|
|
||||||
label='Hide menu bar (Press Alt to show menu bar)'
|
|
||||||
checked={this.state.hideMenuBar}
|
|
||||||
onChange={this.handleChangeHideMenuBar}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
if (process.platform === 'darwin' || process.platform === 'linux') {
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='inputShowTrayIcon'
|
|
||||||
id='inputShowTrayIcon'
|
|
||||||
ref='showTrayIcon'
|
|
||||||
type='checkbox'
|
|
||||||
label={process.platform === 'darwin' ?
|
|
||||||
'Show icon on menu bar (need to restart the application)' :
|
|
||||||
'Show icon in notification area (need to restart the application)'}
|
|
||||||
checked={this.state.showTrayIcon}
|
|
||||||
onChange={this.handleChangeShowTrayIcon}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
if (process.platform === 'linux') {
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='inputTrayIconTheme'
|
|
||||||
ref='trayIconTheme'
|
|
||||||
type='select'
|
|
||||||
label='Icon theme (Need to restart the application)'
|
|
||||||
value={this.state.trayIconTheme}
|
|
||||||
onChange={this.handleChangeTrayIconTheme}
|
|
||||||
>
|
|
||||||
<option value='light'>{'Light'}</option>
|
|
||||||
<option value='dark'>{'Dark'}</option>
|
|
||||||
</Input>);
|
|
||||||
}
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='inputDisableWebSecurity'
|
|
||||||
id='inputDisableWebSecurity'
|
|
||||||
ref='disablewebsecurity'
|
|
||||||
type='checkbox'
|
|
||||||
label='Allow mixed content (Enabling allows both secure and insecure content, images and scripts to render and execute. Disabling allows only secure content.)'
|
|
||||||
checked={this.state.disablewebsecurity}
|
|
||||||
onChange={this.handleChangeDisableWebSecurity}
|
|
||||||
/>);
|
|
||||||
|
|
||||||
//OSX has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX
|
|
||||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='inputAutoStart'
|
|
||||||
id='inputAutoStart'
|
|
||||||
ref='autostart'
|
|
||||||
type='checkbox'
|
|
||||||
label='Start app on login.'
|
|
||||||
checked={this.state.autostart}
|
|
||||||
onChange={this.handleChangeAutoStart}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === 'darwin' || process.platform === 'linux') {
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='inputMinimizeToTray'
|
|
||||||
id='inputMinimizeToTray'
|
|
||||||
ref='minimizeToTray'
|
|
||||||
type='checkbox'
|
|
||||||
label={this.state.trayWasVisible || !this.state.showTrayIcon ? 'Leave app running in notification area when the window is closed' : 'Leave app running in notification area when the window is closed (available on next restart)'}
|
|
||||||
disabled={!this.state.showTrayIcon || !this.state.trayWasVisible}
|
|
||||||
checked={this.state.minimizeToTray}
|
|
||||||
onChange={this.handleChangeMinimizeToTray}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='inputToggleWindowOnTrayIconClick'
|
|
||||||
id='inputToggleWindowOnTrayIconClick'
|
|
||||||
ref='toggleWindowOnTrayIconClick'
|
|
||||||
type='checkbox'
|
|
||||||
label='Toggle window visibility when clicking on the tray icon.'
|
|
||||||
checked={this.state.toggleWindowOnTrayIconClick}
|
|
||||||
onChange={this.handleChangeToggleWindowOnTrayIconClick}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='inputShowUnreadBadge'
|
|
||||||
id='inputShowUnreadBadge'
|
|
||||||
ref='showUnreadBadge'
|
|
||||||
type='checkbox'
|
|
||||||
label='Show red badge on taskbar icon to indicate unread messages. Regardless of this setting, mentions are always indicated with a red badge and item count on the taskbar icon.'
|
|
||||||
checked={this.state.showUnreadBadge}
|
|
||||||
onChange={this.handleShowUnreadBadge}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
|
||||||
options.push(
|
|
||||||
<Input
|
|
||||||
key='flashWindow'
|
|
||||||
id='inputflashWindow'
|
|
||||||
ref='flashWindow'
|
|
||||||
type='checkbox'
|
|
||||||
label='Flash the taskbar icon when a new message is received.'
|
|
||||||
checked={this.state.notifications.flashWindow === 2}
|
|
||||||
onChange={this.handleFlashWindow}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
const settingsPage = {
|
|
||||||
navbar: {
|
|
||||||
backgroundColor: '#fff'
|
|
||||||
},
|
|
||||||
close: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: '0',
|
|
||||||
top: '10px',
|
|
||||||
fontSize: '35px',
|
|
||||||
fontWeight: 'normal',
|
|
||||||
color: '#bbb',
|
|
||||||
cursor: 'pointer'
|
|
||||||
},
|
|
||||||
heading: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: '24px',
|
|
||||||
margin: '0',
|
|
||||||
padding: '1em 0'
|
|
||||||
},
|
|
||||||
sectionHeading: {
|
|
||||||
fontSize: '20px',
|
|
||||||
margin: '0',
|
|
||||||
padding: '1em 0'
|
|
||||||
},
|
|
||||||
sectionHeadingLink: {
|
|
||||||
marginTop: '24px',
|
|
||||||
display: 'inline-block',
|
|
||||||
fontSize: '15px'
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
padding: '0.4em 0'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var optionsRow = (options.length > 0) ? (
|
|
||||||
<Row>
|
|
||||||
<Col md={12}>
|
|
||||||
<h2 style={settingsPage.sectionHeading}>{'App options'}</h2>
|
|
||||||
{ options }
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Navbar
|
|
||||||
className='navbar-fixed-top'
|
|
||||||
style={settingsPage.navbar}
|
|
||||||
>
|
|
||||||
<div style={{position: 'relative'}}>
|
|
||||||
<h1 style={settingsPage.heading}>{'Settings'}</h1>
|
|
||||||
<div
|
|
||||||
style={settingsPage.close}
|
|
||||||
onClick={this.handleCancel}
|
|
||||||
>
|
|
||||||
<span>{'×'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Navbar>
|
|
||||||
<Grid
|
|
||||||
className='settingsPage'
|
|
||||||
style={{padding: '100px 15px'}}
|
|
||||||
>
|
|
||||||
<Row>
|
|
||||||
<Col
|
|
||||||
md={10}
|
|
||||||
xs={8}
|
|
||||||
>
|
|
||||||
<h2 style={settingsPage.sectionHeading}>{'Team Management'}</h2>
|
|
||||||
</Col>
|
|
||||||
<Col
|
|
||||||
md={2}
|
|
||||||
xs={4}
|
|
||||||
>
|
|
||||||
<p className='text-right'>
|
|
||||||
<a
|
|
||||||
style={settingsPage.sectionHeadingLink}
|
|
||||||
href='#'
|
|
||||||
onClick={this.toggleShowTeamForm}
|
|
||||||
>{'⊞ Add new team'}</a>
|
|
||||||
</p>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{ teamsRow }
|
|
||||||
<hr/>
|
|
||||||
{ optionsRow }
|
|
||||||
</Grid>
|
|
||||||
<Navbar className='navbar-fixed-bottom'>
|
|
||||||
<div
|
|
||||||
className='text-right'
|
|
||||||
style={settingsPage.footer}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
id='btnCancel'
|
|
||||||
className='btn btn-link'
|
|
||||||
onClick={this.handleCancel}
|
|
||||||
>{'Cancel'}</button>
|
|
||||||
{ ' ' }
|
|
||||||
<button
|
|
||||||
id='btnSave'
|
|
||||||
className='btn btn-primary navbar-btn'
|
|
||||||
bsStyle='primary'
|
|
||||||
onClick={this.handleSave}
|
|
||||||
disabled={this.state.teams.length === 0}
|
|
||||||
>{'Save'}</button>
|
|
||||||
</div>
|
|
||||||
</Navbar>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var TeamList = React.createClass({
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
showTeamListItemNew: false,
|
|
||||||
team: {
|
|
||||||
url: '',
|
|
||||||
name: '',
|
|
||||||
index: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleTeamRemove(index) {
|
|
||||||
console.log(index);
|
|
||||||
var teams = this.props.teams;
|
|
||||||
teams.splice(index, 1);
|
|
||||||
this.props.onTeamsChange(teams);
|
|
||||||
},
|
|
||||||
handleTeamAdd(team) {
|
|
||||||
var teams = this.props.teams;
|
|
||||||
|
|
||||||
// check if team already exists and then change existing team or add new one
|
|
||||||
if ((typeof team.index !== 'undefined') && teams[team.index]) {
|
|
||||||
teams[team.index].name = team.name;
|
|
||||||
teams[team.index].url = team.url;
|
|
||||||
} else {
|
|
||||||
teams.push(team);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
showTeamListItemNew: false,
|
|
||||||
team: {
|
|
||||||
url: '',
|
|
||||||
name: '',
|
|
||||||
index: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onTeamsChange(teams);
|
|
||||||
},
|
|
||||||
handleTeamEditing(teamName, teamUrl, teamIndex) {
|
|
||||||
this.setState({
|
|
||||||
showTeamListItemNew: true,
|
|
||||||
team: {
|
|
||||||
url: teamUrl,
|
|
||||||
name: teamName,
|
|
||||||
index: teamIndex
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
var self = this;
|
|
||||||
var teamNodes = this.props.teams.map((team, i) => {
|
|
||||||
function handleTeamRemove() {
|
|
||||||
self.handleTeamRemove(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTeamEditing() {
|
|
||||||
self.handleTeamEditing(team.name, team.url, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TeamListItem
|
|
||||||
index={i}
|
|
||||||
key={'teamListItem' + i}
|
|
||||||
name={team.name}
|
|
||||||
url={team.url}
|
|
||||||
onTeamRemove={handleTeamRemove}
|
|
||||||
onTeamEditing={handleTeamEditing}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
var addTeamForm;
|
|
||||||
if (this.props.showAddTeamForm || this.state.showTeamListItemNew) {
|
|
||||||
addTeamForm = (
|
|
||||||
<TeamListItemNew
|
|
||||||
key={this.state.team.index}
|
|
||||||
onTeamAdd={this.handleTeamAdd}
|
|
||||||
teamIndex={this.state.team.index}
|
|
||||||
teamName={this.state.team.name}
|
|
||||||
teamUrl={this.state.team.url}
|
|
||||||
/>);
|
|
||||||
} else {
|
|
||||||
addTeamForm = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListGroup class='teamList'>
|
|
||||||
{ teamNodes }
|
|
||||||
{ addTeamForm }
|
|
||||||
</ListGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var TeamListItem = React.createClass({
|
|
||||||
handleTeamRemove() {
|
|
||||||
this.props.onTeamRemove();
|
|
||||||
},
|
|
||||||
handleTeamEditing() {
|
|
||||||
this.props.onTeamEditing();
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
var style = {
|
|
||||||
left: {
|
|
||||||
display: 'inline-block'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div className='teamListItem list-group-item'>
|
|
||||||
<div style={style.left}>
|
|
||||||
<h4 className='list-group-item-heading'>{ this.props.name }</h4>
|
|
||||||
<p className='list-group-item-text'>
|
|
||||||
{ this.props.url }
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className='pull-right'>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
onClick={this.handleTeamEditing}
|
|
||||||
>{'Edit'}</a>
|
|
||||||
{' - '}
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
onClick={this.handleTeamRemove}
|
|
||||||
>{'Remove'}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var TeamListItemNew = React.createClass({
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
name: this.props.teamName,
|
|
||||||
url: this.props.teamUrl,
|
|
||||||
index: this.props.teamIndex,
|
|
||||||
errorMessage: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleSubmit(e) {
|
|
||||||
console.log('submit');
|
|
||||||
e.preventDefault();
|
|
||||||
const errorMessage = this.getValidationErrorMessage();
|
|
||||||
if (errorMessage) {
|
|
||||||
this.setState({
|
|
||||||
errorMessage
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onTeamAdd({
|
|
||||||
name: this.state.name.trim(),
|
|
||||||
url: this.state.url.trim(),
|
|
||||||
index: this.state.index
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
name: '',
|
|
||||||
url: '',
|
|
||||||
index: '',
|
|
||||||
errorMessage: null
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleNameChange(e) {
|
|
||||||
console.log('name');
|
|
||||||
this.setState({
|
|
||||||
name: e.target.value
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleURLChange(e) {
|
|
||||||
console.log('url');
|
|
||||||
this.setState({
|
|
||||||
url: e.target.value
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getValidationErrorMessage() {
|
|
||||||
if (this.state.name.trim() === '') {
|
|
||||||
return 'Name is required.';
|
|
||||||
} else if (this.state.url.trim() === '') {
|
|
||||||
return 'URL is required.';
|
|
||||||
} else if (!(/^https?:\/\/.*/).test(this.state.url.trim())) {
|
|
||||||
return 'URL should start with http:// or https://.';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const inputTeamName = ReactDOM.findDOMNode(this.refs.inputTeamName);
|
|
||||||
const setErrorMessage = () => {
|
|
||||||
this.setState({
|
|
||||||
errorMessage: this.getValidationErrorMessage()
|
|
||||||
});
|
|
||||||
};
|
|
||||||
inputTeamName.addEventListener('invalid', setErrorMessage);
|
|
||||||
const inputTeamURL = ReactDOM.findDOMNode(this.refs.inputTeamURL);
|
|
||||||
inputTeamURL.addEventListener('invalid', setErrorMessage);
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
var existingTeam = false;
|
|
||||||
if (this.state.name !== '' && this.state.url !== '') {
|
|
||||||
existingTeam = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var btnAddText;
|
|
||||||
if (existingTeam) {
|
|
||||||
btnAddText = 'Save';
|
|
||||||
} else {
|
|
||||||
btnAddText = 'Add';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListGroupItem>
|
|
||||||
<form
|
|
||||||
className='form-inline'
|
|
||||||
onSubmit={this.handleSubmit}
|
|
||||||
>
|
|
||||||
<div className='form-group'>
|
|
||||||
<label htmlFor='inputTeamName'>{'Name'}</label>
|
|
||||||
{ ' ' }
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
required
|
|
||||||
className='form-control'
|
|
||||||
id='inputTeamName'
|
|
||||||
ref='inputTeamName'
|
|
||||||
placeholder='Example team'
|
|
||||||
value={this.state.name}
|
|
||||||
onChange={this.handleNameChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{ ' ' }
|
|
||||||
<div className='form-group'>
|
|
||||||
<label htmlFor='inputTeamURL'>{'URL'}</label>
|
|
||||||
{ ' ' }
|
|
||||||
<input
|
|
||||||
type='url'
|
|
||||||
required
|
|
||||||
className='form-control'
|
|
||||||
id='inputTeamURL'
|
|
||||||
ref='inputTeamURL'
|
|
||||||
placeholder='https://example.com/team'
|
|
||||||
value={this.state.url}
|
|
||||||
onChange={this.handleURLChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{ ' ' }
|
|
||||||
<Button type='submit'>
|
|
||||||
{ btnAddText }
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
{ (() => {
|
|
||||||
if (this.state.errorMessage !== null) {
|
|
||||||
return (
|
|
||||||
<HelpBlock style={{color: '#777777'}}>
|
|
||||||
{ this.state.errorMessage }
|
|
||||||
</HelpBlock>);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})() }
|
|
||||||
</ListGroupItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var configFile = remote.getGlobal('config-file');
|
var configFile = remote.getGlobal('config-file');
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"os-locale": "^1.4.0",
|
"os-locale": "^1.4.0",
|
||||||
"react": "^15.3.0",
|
"react": "^15.3.0",
|
||||||
"react-bootstrap": "~0.29.0",
|
"react-bootstrap": "~0.30.6",
|
||||||
"react-dom": "^15.3.0",
|
"react-dom": "^15.3.0",
|
||||||
"yargs": "^3.32.0"
|
"yargs": "^3.32.0"
|
||||||
}
|
}
|
||||||
|
@@ -65,9 +65,9 @@ describe('browser/settings.html', function desc() {
|
|||||||
return this.app.client.
|
return this.app.client.
|
||||||
loadSettingsPage().
|
loadSettingsPage().
|
||||||
scroll('#inputHideMenuBar').
|
scroll('#inputHideMenuBar').
|
||||||
isSelected('#inputHideMenuBar input').then((isSelected) => {
|
isSelected('#inputHideMenuBar').then((isSelected) => {
|
||||||
if (isSelected !== v) {
|
if (isSelected !== v) {
|
||||||
return this.app.client.click('#inputHideMenuBar input');
|
return this.app.client.click('#inputHideMenuBar');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).
|
}).
|
||||||
@@ -83,7 +83,7 @@ describe('browser/settings.html', function desc() {
|
|||||||
return this.app.client. // confirm actual behavior
|
return this.app.client. // confirm actual behavior
|
||||||
browserWindow.isMenuBarAutoHide().should.eventually.equal(v).
|
browserWindow.isMenuBarAutoHide().should.eventually.equal(v).
|
||||||
loadSettingsPage().
|
loadSettingsPage().
|
||||||
isSelected('#inputHideMenuBar input').should.eventually.equal(v);
|
isSelected('#inputHideMenuBar').should.eventually.equal(v);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -98,9 +98,9 @@ describe('browser/settings.html', function desc() {
|
|||||||
return this.app.client.
|
return this.app.client.
|
||||||
loadSettingsPage().
|
loadSettingsPage().
|
||||||
scroll('#inputDisableWebSecurity').
|
scroll('#inputDisableWebSecurity').
|
||||||
isSelected('#inputDisableWebSecurity input').then((isSelected) => {
|
isSelected('#inputDisableWebSecurity').then((isSelected) => {
|
||||||
if (isSelected !== v) {
|
if (isSelected !== v) {
|
||||||
return this.app.client.click('#inputDisableWebSecurity input');
|
return this.app.client.click('#inputDisableWebSecurity');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).
|
}).
|
||||||
@@ -125,7 +125,7 @@ describe('browser/settings.html', function desc() {
|
|||||||
});
|
});
|
||||||
}).
|
}).
|
||||||
loadSettingsPage().
|
loadSettingsPage().
|
||||||
isSelected('#inputDisableWebSecurity input').should.eventually.equal(v);
|
isSelected('#inputDisableWebSecurity').should.eventually.equal(v);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user