Merge pull request #545 from mattermost/mm-2066
MM-2066 Refactoring for style guide
Этот коммит содержится в:
@@ -4,48 +4,49 @@
|
|||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
var AsyncClient = require('../utils/async_client.jsx');
|
var AsyncClient = require('../utils/async_client.jsx');
|
||||||
var LoadingScreen = require('./loading_screen.jsx');
|
var LoadingScreen = require('./loading_screen.jsx');
|
||||||
var utils = require('../utils/utils.jsx');
|
var Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
function getStateFromStoresForAudits() {
|
export default class AccessHistoryModal extends React.Component {
|
||||||
return {
|
constructor(props) {
|
||||||
audits: UserStore.getAudits()
|
super(props);
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
this.onAuditChange = this.onAuditChange.bind(this);
|
||||||
displayName: 'AccessHistoryModal',
|
this.handleMoreInfo = this.handleMoreInfo.bind(this);
|
||||||
componentDidMount: function() {
|
|
||||||
UserStore.addAuditsChangeListener(this.onListenerChange);
|
this.state = this.getStateFromStoresForAudits();
|
||||||
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function() {
|
this.state.moreInfo = [];
|
||||||
|
}
|
||||||
|
getStateFromStoresForAudits() {
|
||||||
|
return {
|
||||||
|
audits: UserStore.getAudits()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
UserStore.addAuditsChangeListener(this.onAuditChange);
|
||||||
|
$(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function show() {
|
||||||
AsyncClient.getAudits();
|
AsyncClient.getAudits();
|
||||||
});
|
});
|
||||||
|
|
||||||
var self = this;
|
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function hide() {
|
||||||
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function() {
|
|
||||||
$('#user_settings').modal('show');
|
$('#user_settings').modal('show');
|
||||||
self.setState({moreInfo: []});
|
this.setState({moreInfo: []});
|
||||||
});
|
}.bind(this));
|
||||||
},
|
}
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount() {
|
||||||
UserStore.removeAuditsChangeListener(this.onListenerChange);
|
UserStore.removeAuditsChangeListener(this.onAuditChange);
|
||||||
},
|
}
|
||||||
onListenerChange: function() {
|
onAuditChange() {
|
||||||
var newState = getStateFromStoresForAudits();
|
var newState = this.getStateFromStoresForAudits();
|
||||||
if (!utils.areStatesEqual(newState.audits, this.state.audits)) {
|
if (!Utils.areStatesEqual(newState.audits, this.state.audits)) {
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
handleMoreInfo: function(index) {
|
handleMoreInfo(index) {
|
||||||
var newMoreInfo = this.state.moreInfo;
|
var newMoreInfo = this.state.moreInfo;
|
||||||
newMoreInfo[index] = true;
|
newMoreInfo[index] = true;
|
||||||
this.setState({moreInfo: newMoreInfo});
|
this.setState({moreInfo: newMoreInfo});
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
render() {
|
||||||
var initialState = getStateFromStoresForAudits();
|
|
||||||
initialState.moreInfo = [];
|
|
||||||
return initialState;
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var accessList = [];
|
var accessList = [];
|
||||||
var currentHistoryDate = null;
|
var currentHistoryDate = null;
|
||||||
|
|
||||||
@@ -63,7 +64,16 @@ module.exports = React.createClass({
|
|||||||
currentAudit.session_id = 'N/A (Login attempt)';
|
currentAudit.session_id = 'N/A (Login attempt)';
|
||||||
}
|
}
|
||||||
|
|
||||||
var moreInfo = (<a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
|
var moreInfo = (
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
className='theme'
|
||||||
|
onClick={this.handleMoreInfo.bind(this, i)}
|
||||||
|
>
|
||||||
|
More info
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
if (this.state.moreInfo[i]) {
|
if (this.state.moreInfo[i]) {
|
||||||
moreInfo = (
|
moreInfo = (
|
||||||
<div>
|
<div>
|
||||||
@@ -75,7 +85,7 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
var divider = null;
|
var divider = null;
|
||||||
if (i < this.state.audits.length - 1) {
|
if (i < this.state.audits.length - 1) {
|
||||||
divider = (<div className='divider-light'></div>)
|
divider = (<div className='divider-light'></div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
accessList[i] = (
|
accessList[i] = (
|
||||||
@@ -102,14 +112,36 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='modal fade' ref='modal' id='access-history' tabIndex='-1' role='dialog' aria-hidden='true'>
|
<div
|
||||||
|
className='modal fade'
|
||||||
|
ref='modal'
|
||||||
|
id='access-history'
|
||||||
|
tabIndex='-1'
|
||||||
|
role='dialog'
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
<div className='modal-dialog modal-lg'>
|
<div className='modal-dialog modal-lg'>
|
||||||
<div className='modal-content'>
|
<div className='modal-content'>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>
|
<button
|
||||||
<h4 className='modal-title' id='myModalLabel'>Access History</h4>
|
type='button'
|
||||||
|
className='close'
|
||||||
|
data-dismiss='modal'
|
||||||
|
aria-label='Close'
|
||||||
|
>
|
||||||
|
<span aria-hidden='true'>×</span>
|
||||||
|
</button>
|
||||||
|
<h4
|
||||||
|
className='modal-title'
|
||||||
|
id='myModalLabel'
|
||||||
|
>
|
||||||
|
Access History
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div ref='modalBody' className='modal-body'>
|
<div
|
||||||
|
ref='modalBody'
|
||||||
|
className='modal-body'
|
||||||
|
>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,4 +150,4 @@ module.exports = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|||||||
@@ -4,26 +4,29 @@
|
|||||||
var SettingItemMin = require('./setting_item_min.jsx');
|
var SettingItemMin = require('./setting_item_min.jsx');
|
||||||
var SettingItemMax = require('./setting_item_max.jsx');
|
var SettingItemMax = require('./setting_item_max.jsx');
|
||||||
|
|
||||||
var utils = require('../utils/utils.jsx');
|
var Utils = require('../utils/utils.jsx');
|
||||||
var client = require('../utils/client.jsx');
|
var Client = require('../utils/client.jsx');
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
var ChannelStore = require('../stores/channel_store.jsx');
|
var ChannelStore = require('../stores/channel_store.jsx');
|
||||||
|
|
||||||
export default class ChannelNotifications extends React.Component {
|
export default class ChannelNotifications extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onListenerChange = this.onListenerChange.bind(this);
|
this.onListenerChange = this.onListenerChange.bind(this);
|
||||||
this.updateSection = this.updateSection.bind(this);
|
this.updateSection = this.updateSection.bind(this);
|
||||||
this.handleUpdate = this.handleUpdate.bind(this);
|
this.handleUpdate = this.handleUpdate.bind(this);
|
||||||
this.handleRadioClick = this.handleRadioClick.bind(this);
|
this.handleRadioClick = this.handleRadioClick.bind(this);
|
||||||
this.handleQuietToggle = this.handleQuietToggle.bind(this);
|
this.handleQuietToggle = this.handleQuietToggle.bind(this);
|
||||||
|
this.createDesktopSection = this.createDesktopSection.bind(this);
|
||||||
|
this.createQuietSection = this.createQuietSection.bind(this);
|
||||||
|
|
||||||
this.state = {notifyLevel: '', title: '', channelId: '', activeSection: ''};
|
this.state = {notifyLevel: '', title: '', channelId: '', activeSection: ''};
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
ChannelStore.addChangeListener(this.onListenerChange);
|
ChannelStore.addChangeListener(this.onListenerChange);
|
||||||
|
|
||||||
var self = this;
|
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function showModal(e) {
|
||||||
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function showModal(e) {
|
|
||||||
var button = e.relatedTarget;
|
var button = e.relatedTarget;
|
||||||
var channelId = button.getAttribute('data-channelid');
|
var channelId = button.getAttribute('data-channelid');
|
||||||
|
|
||||||
@@ -34,8 +37,8 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
quietMode = true;
|
quietMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId});
|
this.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId});
|
||||||
});
|
}.bind(this));
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
ChannelStore.removeChangeListener(this.onListenerChange);
|
||||||
@@ -55,7 +58,7 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
newState.notifyLevel = notifyLevel;
|
newState.notifyLevel = notifyLevel;
|
||||||
newState.quietMode = quietMode;
|
newState.quietMode = quietMode;
|
||||||
|
|
||||||
if (!utils.areStatesEqual(this.state, newState)) {
|
if (!Utils.areStatesEqual(this.state, newState)) {
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +81,7 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.updateNotifyLevel(data,
|
Client.updateNotifyLevel(data,
|
||||||
function success() {
|
function success() {
|
||||||
var member = ChannelStore.getMember(channelId);
|
var member = ChannelStore.getMember(channelId);
|
||||||
member.notify_level = notifyLevel;
|
member.notify_level = notifyLevel;
|
||||||
@@ -92,25 +95,15 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
}
|
}
|
||||||
handleRadioClick(notifyLevel) {
|
handleRadioClick(notifyLevel) {
|
||||||
this.setState({notifyLevel: notifyLevel, quietMode: false});
|
this.setState({notifyLevel: notifyLevel, quietMode: false});
|
||||||
this.refs.modal.getDOMNode().focus();
|
React.findDOMNode(this.refs.modal).focus();
|
||||||
}
|
}
|
||||||
handleQuietToggle(quietMode) {
|
handleQuietToggle(quietMode) {
|
||||||
this.setState({notifyLevel: 'none', quietMode: quietMode});
|
this.setState({notifyLevel: 'none', quietMode: quietMode});
|
||||||
this.refs.modal.getDOMNode().focus();
|
React.findDOMNode(this.refs.modal).focus();
|
||||||
}
|
}
|
||||||
render() {
|
createDesktopSection(serverError) {
|
||||||
var serverError = null;
|
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var describe = '';
|
|
||||||
var inputs = [];
|
|
||||||
|
|
||||||
var handleUpdateSection;
|
var handleUpdateSection;
|
||||||
|
|
||||||
var desktopSection;
|
|
||||||
if (this.state.activeSection === 'desktop') {
|
if (this.state.activeSection === 'desktop') {
|
||||||
var notifyActive = [false, false, false];
|
var notifyActive = [false, false, false];
|
||||||
if (this.state.notifyLevel === 'mention') {
|
if (this.state.notifyLevel === 'mention') {
|
||||||
@@ -121,6 +114,8 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
notifyActive[2] = true;
|
notifyActive[2] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var inputs = [];
|
||||||
|
|
||||||
inputs.push(
|
inputs.push(
|
||||||
<div>
|
<div>
|
||||||
<div className='radio'>
|
<div className='radio'>
|
||||||
@@ -128,7 +123,7 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
checked={notifyActive[0]}
|
checked={notifyActive[0]}
|
||||||
onChange={self.handleRadioClick.bind(this, 'all')}
|
onChange={this.handleRadioClick.bind(this, 'all')}
|
||||||
>
|
>
|
||||||
For all activity
|
For all activity
|
||||||
</input>
|
</input>
|
||||||
@@ -140,7 +135,7 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
checked={notifyActive[1]}
|
checked={notifyActive[1]}
|
||||||
onChange={self.handleRadioClick.bind(this, 'mention')}
|
onChange={this.handleRadioClick.bind(this, 'mention')}
|
||||||
>
|
>
|
||||||
Only for mentions
|
Only for mentions
|
||||||
</input>
|
</input>
|
||||||
@@ -152,7 +147,7 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
checked={notifyActive[2]}
|
checked={notifyActive[2]}
|
||||||
onChange={self.handleRadioClick.bind(this, 'none')}
|
onChange={this.handleRadioClick.bind(this, 'none')}
|
||||||
>
|
>
|
||||||
Never
|
Never
|
||||||
</input>
|
</input>
|
||||||
@@ -162,12 +157,12 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
handleUpdateSection = function updateSection(e) {
|
handleUpdateSection = function updateSection(e) {
|
||||||
self.updateSection('');
|
this.updateSection('');
|
||||||
self.onListenerChange();
|
this.onListenerChange();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
}.bind(this);
|
||||||
|
|
||||||
desktopSection = (
|
return (
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
title='Send desktop notifications'
|
title='Send desktop notifications'
|
||||||
inputs={inputs}
|
inputs={inputs}
|
||||||
@@ -176,30 +171,32 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
updateSection={handleUpdateSection}
|
updateSection={handleUpdateSection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
if (this.state.notifyLevel === 'mention') {
|
|
||||||
describe = 'Only for mentions';
|
|
||||||
} else if (this.state.notifyLevel === 'all') {
|
|
||||||
describe = 'For all activity';
|
|
||||||
} else {
|
|
||||||
describe = 'Never';
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUpdateSection = function updateSection(e) {
|
|
||||||
self.updateSection('desktop');
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
desktopSection = (
|
|
||||||
<SettingItemMin
|
|
||||||
title='Send desktop notifications'
|
|
||||||
describe={describe}
|
|
||||||
updateSection={handleUpdateSection}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var quietSection;
|
var describe;
|
||||||
|
if (this.state.notifyLevel === 'mention') {
|
||||||
|
describe = 'Only for mentions';
|
||||||
|
} else if (this.state.notifyLevel === 'all') {
|
||||||
|
describe = 'For all activity';
|
||||||
|
} else {
|
||||||
|
describe = 'Never';
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdateSection = function updateSection(e) {
|
||||||
|
this.updateSection('desktop');
|
||||||
|
e.preventDefault();
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingItemMin
|
||||||
|
title='Send desktop notifications'
|
||||||
|
describe={describe}
|
||||||
|
updateSection={handleUpdateSection}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
createQuietSection(serverError) {
|
||||||
|
var handleUpdateSection;
|
||||||
if (this.state.activeSection === 'quiet') {
|
if (this.state.activeSection === 'quiet') {
|
||||||
var quietActive = [false, false];
|
var quietActive = [false, false];
|
||||||
if (this.state.quietMode) {
|
if (this.state.quietMode) {
|
||||||
@@ -208,6 +205,8 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
quietActive[1] = true;
|
quietActive[1] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var inputs = [];
|
||||||
|
|
||||||
inputs.push(
|
inputs.push(
|
||||||
<div>
|
<div>
|
||||||
<div className='radio'>
|
<div className='radio'>
|
||||||
@@ -215,7 +214,7 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
checked={quietActive[0]}
|
checked={quietActive[0]}
|
||||||
onChange={self.handleQuietToggle.bind(this, true)}
|
onChange={this.handleQuietToggle.bind(this, true)}
|
||||||
>
|
>
|
||||||
On
|
On
|
||||||
</input>
|
</input>
|
||||||
@@ -227,7 +226,7 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
checked={quietActive[1]}
|
checked={quietActive[1]}
|
||||||
onChange={self.handleQuietToggle.bind(this, false)}
|
onChange={this.handleQuietToggle.bind(this, false)}
|
||||||
>
|
>
|
||||||
Off
|
Off
|
||||||
</input>
|
</input>
|
||||||
@@ -245,12 +244,12 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
handleUpdateSection = function updateSection(e) {
|
handleUpdateSection = function updateSection(e) {
|
||||||
self.updateSection('');
|
this.updateSection('');
|
||||||
self.onListenerChange();
|
this.onListenerChange();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
}.bind(this);
|
||||||
|
|
||||||
quietSection = (
|
return (
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
title='Quiet mode'
|
title='Quiet mode'
|
||||||
inputs={inputs}
|
inputs={inputs}
|
||||||
@@ -259,27 +258,38 @@ export default class ChannelNotifications extends React.Component {
|
|||||||
updateSection={handleUpdateSection}
|
updateSection={handleUpdateSection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
if (this.state.quietMode) {
|
|
||||||
describe = 'On';
|
|
||||||
} else {
|
|
||||||
describe = 'Off';
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUpdateSection = function updateSection(e) {
|
|
||||||
self.updateSection('quiet');
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
quietSection = (
|
|
||||||
<SettingItemMin
|
|
||||||
title='Quiet mode'
|
|
||||||
describe={describe}
|
|
||||||
updateSection={handleUpdateSection}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var describe;
|
||||||
|
if (this.state.quietMode) {
|
||||||
|
describe = 'On';
|
||||||
|
} else {
|
||||||
|
describe = 'Off';
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdateSection = function updateSection(e) {
|
||||||
|
this.updateSection('quiet');
|
||||||
|
e.preventDefault();
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingItemMin
|
||||||
|
title='Quiet mode'
|
||||||
|
describe={describe}
|
||||||
|
updateSection={handleUpdateSection}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
var serverError = null;
|
||||||
|
if (this.state.serverError) {
|
||||||
|
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
var desktopSection = this.createDesktopSection(serverError);
|
||||||
|
|
||||||
|
var quietSection = this.createQuietSection(serverError);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='modal fade'
|
className='modal fade'
|
||||||
|
|||||||
@@ -2,69 +2,114 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
var ZeroClipboardMixin = require('react-zeroclipboard-mixin');
|
|
||||||
|
|
||||||
ZeroClipboardMixin.ZeroClipboard.config({
|
export default class GetLinkModal extends React.Component {
|
||||||
swfPath: '../../static/flash/ZeroClipboard.swf'
|
constructor(props) {
|
||||||
});
|
super(props);
|
||||||
|
|
||||||
module.exports = React.createClass({
|
this.handleClick = this.handleClick.bind(this);
|
||||||
displayName: 'GetLinkModal',
|
|
||||||
zeroclipboardElementsSelector: '[data-copy-btn]',
|
this.state = {copiedLink: false};
|
||||||
mixins: [ZeroClipboardMixin],
|
}
|
||||||
componentDidMount: function() {
|
componentDidMount() {
|
||||||
var self = this;
|
|
||||||
if (this.refs.modal) {
|
if (this.refs.modal) {
|
||||||
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
|
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) {
|
||||||
var button = e.relatedTarget;
|
var button = e.relatedTarget;
|
||||||
self.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')});
|
this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')});
|
||||||
});
|
}.bind(this));
|
||||||
$(this.refs.modal.getDOMNode()).on('hide.bs.modal', function() {
|
$(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function hide() {
|
||||||
self.setState({copiedLink: false});
|
this.setState({copiedLink: false});
|
||||||
});
|
}.bind(this));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
handleClick() {
|
||||||
return {copiedLink: false};
|
var copyTextarea = $(React.findDOMNode(this.refs.textarea));
|
||||||
},
|
copyTextarea.select();
|
||||||
handleClick: function() {
|
|
||||||
this.setState({copiedLink: true});
|
try {
|
||||||
},
|
var successful = document.execCommand('copy');
|
||||||
render: function() {
|
if (successful) {
|
||||||
|
this.setState({copiedLink: true});
|
||||||
|
} else {
|
||||||
|
this.setState({copiedLink: false});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({copiedLink: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
var currentUser = UserStore.getCurrentUser();
|
var currentUser = UserStore.getCurrentUser();
|
||||||
var copyLinkConfirm = null;
|
var copyLinkConfirm = null;
|
||||||
|
|
||||||
if (this.state.copiedLink) {
|
if (this.state.copiedLink) {
|
||||||
copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className="fa fa-check"></i> Link copied to clipboard.</p>;
|
copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className='fa fa-check'></i> Link copied to clipboard.</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
return (
|
return (
|
||||||
<div className='modal fade' ref='modal' id='get_link' tabIndex='-1' role='dialog' aria-hidden='true'>
|
<div
|
||||||
<div className='modal-dialog'>
|
className='modal fade'
|
||||||
<div className='modal-content'>
|
ref='modal'
|
||||||
<div className='modal-header'>
|
id='get_link'
|
||||||
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>
|
tabIndex='-1'
|
||||||
<h4 className='modal-title' id='myModalLabel'>{this.state.title} Link</h4>
|
role='dialog'
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
|
<div className='modal-dialog'>
|
||||||
|
<div className='modal-content'>
|
||||||
|
<div className='modal-header'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='close'
|
||||||
|
data-dismiss='modal'
|
||||||
|
aria-label='Close'
|
||||||
|
>
|
||||||
|
<span aria-hidden='true'>×</span>
|
||||||
|
</button>
|
||||||
|
<h4
|
||||||
|
className='modal-title'
|
||||||
|
id='myModalLabel'
|
||||||
|
>
|
||||||
|
{this.state.title} Link
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div className='modal-body'>
|
||||||
|
<p>
|
||||||
|
Send {strings.Team + 'mates'} the link below for them to sign-up to this {strings.Team} site.
|
||||||
|
<br /><br />
|
||||||
|
Be careful not to share this link publicly, since anyone with the link can join your {strings.Team}.
|
||||||
|
</p>
|
||||||
|
<textarea
|
||||||
|
className='form-control no-resize'
|
||||||
|
readOnly='true'
|
||||||
|
ref='textarea'
|
||||||
|
value={this.state.value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='modal-footer'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='btn btn-default'
|
||||||
|
data-dismiss='modal'
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-copy-btn='true'
|
||||||
|
type='button'
|
||||||
|
className='btn btn-primary pull-left'
|
||||||
|
onClick={this.handleClick}
|
||||||
|
data-clipboard-text={this.state.value}
|
||||||
|
>
|
||||||
|
Copy Link
|
||||||
|
</button>
|
||||||
|
{copyLinkConfirm}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='modal-body'>
|
</div>
|
||||||
<p>
|
|
||||||
Send {strings.Team + 'mates'} the link below for them to sign-up to this {strings.Team} site.
|
|
||||||
<br /><br />
|
|
||||||
Be careful not to share this link publicly, since anyone with the link can join your {strings.Team}.
|
|
||||||
</p>
|
|
||||||
<textarea className='form-control no-resize' readOnly='true' value={this.state.value}></textarea>
|
|
||||||
</div>
|
|
||||||
<div className='modal-footer'>
|
|
||||||
<button type='button' className='btn btn-default' data-dismiss='modal'>Close</button>
|
|
||||||
<button data-copy-btn='true' type='button' className='btn btn-primary pull-left' onClick={this.handleClick} data-clipboard-text={this.state.value}>Copy Link</button>
|
|
||||||
{copyLinkConfirm}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <div/>;
|
return <div/>;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|||||||
@@ -1,24 +1,31 @@
|
|||||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class LoadingScreen extends React.Component {
|
||||||
displayName: "LoadingScreen",
|
constructor(props) {
|
||||||
propTypes: {
|
super(props);
|
||||||
position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
|
this.state = {};
|
||||||
},
|
}
|
||||||
getDefaultProps: function() {
|
render() {
|
||||||
return { position: 'relative' };
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
return (
|
return (
|
||||||
<div className="loading-screen" style={{position: this.props.position}}>
|
<div
|
||||||
<div className="loading__content">
|
className='loading-screen'
|
||||||
|
style={{position: this.props.position}}
|
||||||
|
>
|
||||||
|
<div className='loading__content'>
|
||||||
<h3>Loading</h3>
|
<h3>Loading</h3>
|
||||||
<div className="round round-1"></div>
|
<div className='round round-1'></div>
|
||||||
<div className="round round-2"></div>
|
<div className='round round-2'></div>
|
||||||
<div className="round round-3"></div>
|
<div className='round round-3'></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
LoadingScreen.defaultProps = {
|
||||||
|
position: 'relative'
|
||||||
|
};
|
||||||
|
LoadingScreen.propTypes = {
|
||||||
|
position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var client = require('../utils/client.jsx');
|
var Client = require('../utils/client.jsx');
|
||||||
var AsyncClient = require('../utils/async_client.jsx');
|
var AsyncClient = require('../utils/async_client.jsx');
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
var ChannelStore = require('../stores/channel_store.jsx');
|
var ChannelStore = require('../stores/channel_store.jsx');
|
||||||
@@ -13,22 +13,27 @@ var Constants = require('../utils/constants.jsx');
|
|||||||
var ActionTypes = Constants.ActionTypes;
|
var ActionTypes = Constants.ActionTypes;
|
||||||
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||||
|
|
||||||
function getStateFromStores() {
|
export default class Navbar extends React.Component {
|
||||||
return {
|
constructor(props) {
|
||||||
channel: ChannelStore.getCurrent(),
|
super(props);
|
||||||
member: ChannelStore.getCurrentMember(),
|
|
||||||
users: ChannelStore.getCurrentExtraInfo().members
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
this.onChange = this.onChange.bind(this);
|
||||||
displayName: 'Navbar',
|
this.handleLeave = this.handleLeave.bind(this);
|
||||||
propTypes: {
|
this.createCollapseButtons = this.createCollapseButtons.bind(this);
|
||||||
teamDisplayName: React.PropTypes.string
|
this.createDropdown = this.createDropdown.bind(this);
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
this.state = this.getStateFromStores();
|
||||||
ChannelStore.addChangeListener(this.onListenerChange);
|
}
|
||||||
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
|
getStateFromStores() {
|
||||||
|
return {
|
||||||
|
channel: ChannelStore.getCurrent(),
|
||||||
|
member: ChannelStore.getCurrentMember(),
|
||||||
|
users: ChannelStore.getCurrentExtraInfo().members
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
ChannelStore.addChangeListener(this.onChange);
|
||||||
|
ChannelStore.addExtraInfoChangeListener(this.onChange);
|
||||||
$('.inner__wrap').click(this.hideSidebars);
|
$('.inner__wrap').click(this.hideSidebars);
|
||||||
|
|
||||||
$('body').on('click.infopopover', function handlePopoverClick(e) {
|
$('body').on('click.infopopover', function handlePopoverClick(e) {
|
||||||
@@ -36,15 +41,15 @@ module.exports = React.createClass({
|
|||||||
$('.info-popover').popover('hide');
|
$('.info-popover').popover('hide');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
ChannelStore.removeChangeListener(this.onChange);
|
||||||
},
|
}
|
||||||
handleSubmit: function(e) {
|
handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
}
|
||||||
handleLeave: function() {
|
handleLeave() {
|
||||||
client.leaveChannel(this.state.channel.id,
|
Client.leaveChannel(this.state.channel.id,
|
||||||
function success() {
|
function success() {
|
||||||
AsyncClient.getChannels(true);
|
AsyncClient.getChannels(true);
|
||||||
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
|
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
|
||||||
@@ -53,8 +58,8 @@ module.exports = React.createClass({
|
|||||||
AsyncClient.dispatchError(err, 'handleLeave');
|
AsyncClient.dispatchError(err, 'handleLeave');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
hideSidebars: function(e) {
|
hideSidebars(e) {
|
||||||
var windowWidth = $(window).outerWidth();
|
var windowWidth = $(window).outerWidth();
|
||||||
if (windowWidth <= 768) {
|
if (windowWidth <= 768) {
|
||||||
AppDispatcher.handleServerAction({
|
AppDispatcher.handleServerAction({
|
||||||
@@ -74,32 +79,259 @@ module.exports = React.createClass({
|
|||||||
$('.sidebar--menu').removeClass('move--left');
|
$('.sidebar--menu').removeClass('move--left');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
toggleLeftSidebar: function() {
|
toggleLeftSidebar() {
|
||||||
$('.inner__wrap').toggleClass('move--right');
|
$('.inner__wrap').toggleClass('move--right');
|
||||||
$('.sidebar--left').toggleClass('move--right');
|
$('.sidebar--left').toggleClass('move--right');
|
||||||
},
|
}
|
||||||
toggleRightSidebar: function() {
|
toggleRightSidebar() {
|
||||||
$('.inner__wrap').toggleClass('move--left-small');
|
$('.inner__wrap').toggleClass('move--left-small');
|
||||||
$('.sidebar--menu').toggleClass('move--left');
|
$('.sidebar--menu').toggleClass('move--left');
|
||||||
},
|
}
|
||||||
onListenerChange: function() {
|
onChange() {
|
||||||
this.setState(getStateFromStores());
|
this.setState(this.getStateFromStores());
|
||||||
$('#navbar .navbar-brand .description').popover({placement: 'bottom', trigger: 'click', html: true});
|
$('#navbar .navbar-brand .description').popover({placement: 'bottom', trigger: 'click', html: true});
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent) {
|
||||||
return getStateFromStores();
|
if (channel) {
|
||||||
},
|
var viewInfoOption = (
|
||||||
render: function() {
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#channel_info'
|
||||||
|
data-channelid={channel.id}
|
||||||
|
href='#'
|
||||||
|
>
|
||||||
|
View Info
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
var setChannelDescriptionOption = (
|
||||||
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#edit_channel'
|
||||||
|
data-desc={channel.description}
|
||||||
|
data-title={channel.display_name}
|
||||||
|
data-channelid={channel.id}
|
||||||
|
>
|
||||||
|
Set Channel Description...
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
var addMembersOption;
|
||||||
|
var leaveChannelOption;
|
||||||
|
if (!isDirect && !ChannelStore.isDefault(channel)) {
|
||||||
|
addMembersOption = (
|
||||||
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#channel_invite'
|
||||||
|
href='#'
|
||||||
|
>
|
||||||
|
Add Members
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
leaveChannelOption = (
|
||||||
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
href='#'
|
||||||
|
onClick={this.handleLeave}
|
||||||
|
>
|
||||||
|
Leave Channel
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var manageMembersOption;
|
||||||
|
var renameChannelOption;
|
||||||
|
var deleteChannelOption;
|
||||||
|
if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
|
||||||
|
manageMembersOption = (
|
||||||
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#channel_members'
|
||||||
|
href='#'
|
||||||
|
>
|
||||||
|
Manage Members
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
renameChannelOption = (
|
||||||
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#rename_channel'
|
||||||
|
data-display={channel.display_name}
|
||||||
|
data-name={channel.name}
|
||||||
|
data-channelid={channel.id}
|
||||||
|
>
|
||||||
|
Rename Channel...
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
deleteChannelOption = (
|
||||||
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#delete_channel'
|
||||||
|
data-title={channel.display_name}
|
||||||
|
data-channelid={channel.id}
|
||||||
|
>
|
||||||
|
Delete Channel...
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var notificationPreferenceOption;
|
||||||
|
if (!isDirect) {
|
||||||
|
notificationPreferenceOption = (
|
||||||
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#channel_notifications'
|
||||||
|
data-title={channel.display_name}
|
||||||
|
data-channelid={channel.id}
|
||||||
|
>
|
||||||
|
Notification Preferences
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='navbar-brand'>
|
||||||
|
<div className='dropdown'>
|
||||||
|
<div
|
||||||
|
data-toggle='popover'
|
||||||
|
data-content={popoverContent}
|
||||||
|
className='description info-popover'
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
className='dropdown-toggle theme'
|
||||||
|
type='button'
|
||||||
|
id='channel_header_dropdown'
|
||||||
|
data-toggle='dropdown'
|
||||||
|
aria-expanded='true'
|
||||||
|
>
|
||||||
|
<span className='heading'>{channelTitle} </span>
|
||||||
|
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
|
||||||
|
</a>
|
||||||
|
<ul
|
||||||
|
className='dropdown-menu'
|
||||||
|
role='menu'
|
||||||
|
aria-labelledby='channel_header_dropdown'
|
||||||
|
>
|
||||||
|
{viewInfoOption}
|
||||||
|
{addMembersOption}
|
||||||
|
{manageMembersOption}
|
||||||
|
{setChannelDescriptionOption}
|
||||||
|
{notificationPreferenceOption}
|
||||||
|
{renameChannelOption}
|
||||||
|
{deleteChannelOption}
|
||||||
|
{leaveChannelOption}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='navbar-brand'>
|
||||||
|
<a
|
||||||
|
href='/'
|
||||||
|
className='heading'
|
||||||
|
>
|
||||||
|
{channelTitle}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
createCollapseButtons(currentId) {
|
||||||
|
var buttons = [];
|
||||||
|
if (currentId == null) {
|
||||||
|
buttons.push(
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='navbar-toggle'
|
||||||
|
data-toggle='collapse'
|
||||||
|
data-target='#navbar-collapse-1'
|
||||||
|
>
|
||||||
|
<span className='sr-only'>Toggle sidebar</span>
|
||||||
|
<span className='icon-bar'></span>
|
||||||
|
<span className='icon-bar'></span>
|
||||||
|
<span className='icon-bar'></span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
buttons.push(
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='navbar-toggle'
|
||||||
|
data-toggle='collapse'
|
||||||
|
data-target='#sidebar-nav'
|
||||||
|
onClick={this.toggleLeftSidebar}
|
||||||
|
>
|
||||||
|
<span className='sr-only'>Toggle sidebar</span>
|
||||||
|
<span className='icon-bar'></span>
|
||||||
|
<span className='icon-bar'></span>
|
||||||
|
<span className='icon-bar'></span>
|
||||||
|
<NotifyCounts />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
buttons.push(
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='navbar-toggle menu-toggle pull-right'
|
||||||
|
data-toggle='collapse'
|
||||||
|
data-target='#sidebar-nav'
|
||||||
|
onClick={this.toggleRightSidebar}
|
||||||
|
>
|
||||||
|
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
var currentId = UserStore.getCurrentId();
|
var currentId = UserStore.getCurrentId();
|
||||||
var popoverContent = '';
|
var channel = this.state.channel;
|
||||||
var channelTitle = this.props.teamDisplayName;
|
var channelTitle = this.props.teamDisplayName;
|
||||||
|
var popoverContent;
|
||||||
var isAdmin = false;
|
var isAdmin = false;
|
||||||
var isDirect = false;
|
var isDirect = false;
|
||||||
var channel = this.state.channel;
|
|
||||||
|
|
||||||
if (channel) {
|
if (channel) {
|
||||||
popoverContent = React.renderToString(<MessageWrapper message={channel.description} options={{singleline: true, noMentionHighlight: true}}/>);
|
popoverContent = React.renderToString(
|
||||||
|
<MessageWrapper
|
||||||
|
message={channel.description}
|
||||||
|
options={{singleline: true, noMentionHighlight: true}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
isAdmin = this.state.member.roles.indexOf('admin') > -1;
|
isAdmin = this.state.member.roles.indexOf('admin') > -1;
|
||||||
|
|
||||||
if (channel.type === 'O') {
|
if (channel.type === 'O') {
|
||||||
@@ -118,110 +350,46 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (channel.description.length === 0) {
|
if (channel.description.length === 0) {
|
||||||
popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>);
|
popoverContent = React.renderToString(
|
||||||
|
<div>
|
||||||
|
No channel description yet. <br/>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-desc={channel.description}
|
||||||
|
data-title={channel.display_name}
|
||||||
|
data-channelid={channel.id}
|
||||||
|
data-target='#edit_channel'
|
||||||
|
>
|
||||||
|
Click here
|
||||||
|
</a> to add one.</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var navbarCollapseButton = null;
|
var collapseButtons = this.createCollapseButtons(currentId);
|
||||||
if (currentId == null) {
|
|
||||||
navbarCollapseButton = (<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='#navbar-collapse-1'>
|
|
||||||
<span className='sr-only'>Toggle sidebar</span>
|
|
||||||
<span className='icon-bar'></span>
|
|
||||||
<span className='icon-bar'></span>
|
|
||||||
<span className='icon-bar'></span>
|
|
||||||
</button>);
|
|
||||||
}
|
|
||||||
|
|
||||||
var sidebarCollapseButton = null;
|
var channelMenuDropdown = this.createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent);
|
||||||
if (currentId != null) {
|
|
||||||
sidebarCollapseButton = (<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='#sidebar-nav' onClick={this.toggleLeftSidebar}>
|
|
||||||
<span className='sr-only'>Toggle sidebar</span>
|
|
||||||
<span className='icon-bar'></span>
|
|
||||||
<span className='icon-bar'></span>
|
|
||||||
<span className='icon-bar'></span>
|
|
||||||
<NotifyCounts />
|
|
||||||
</button>);
|
|
||||||
}
|
|
||||||
|
|
||||||
var rightSidebarCollapseButton = null;
|
|
||||||
if (currentId != null) {
|
|
||||||
rightSidebarCollapseButton = (<button type='button' className='navbar-toggle menu-toggle pull-right' data-toggle='collapse' data-target='#sidebar-nav' onClick={this.toggleRightSidebar}>
|
|
||||||
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
|
|
||||||
</button>);
|
|
||||||
}
|
|
||||||
|
|
||||||
var channelMenuDropdown = null;
|
|
||||||
if (channel) {
|
|
||||||
var viewInfoOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_info' data-channelid={channel.id} href='#'>View Info</a></li>
|
|
||||||
|
|
||||||
var addMembersOption = null;
|
|
||||||
if (!isDirect && !ChannelStore.isDefault(channel)) {
|
|
||||||
addMembersOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_invite' href='#'>Add Members</a></li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var manageMembersOption = null;
|
|
||||||
if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
|
|
||||||
manageMembersOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var setChannelDescriptionOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>;
|
|
||||||
|
|
||||||
var notificationPreferenceOption = null;
|
|
||||||
if (!isDirect) {
|
|
||||||
notificationPreferenceOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var renameChannelOption = null;
|
|
||||||
if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
|
|
||||||
renameChannelOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var deleteChannelOption = null;
|
|
||||||
if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
|
|
||||||
deleteChannelOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var leaveChannelOption = null;
|
|
||||||
if (!isDirect && !ChannelStore.isDefault(channel)) {
|
|
||||||
leaveChannelOption = <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave Channel</a></li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
channelMenuDropdown = (<div className='navbar-brand'>
|
|
||||||
<div className='dropdown'>
|
|
||||||
<div data-toggle='popover' data-content={popoverContent} className='description info-popover'></div>
|
|
||||||
<a href='#' className='dropdown-toggle theme' type='button' id='channel_header_dropdown' data-toggle='dropdown' aria-expanded='true'>
|
|
||||||
<span className='heading'>{channelTitle} </span>
|
|
||||||
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
|
|
||||||
</a>
|
|
||||||
<ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'>
|
|
||||||
{viewInfoOption}
|
|
||||||
{addMembersOption}
|
|
||||||
{manageMembersOption}
|
|
||||||
{setChannelDescriptionOption}
|
|
||||||
{notificationPreferenceOption}
|
|
||||||
{renameChannelOption}
|
|
||||||
{deleteChannelOption}
|
|
||||||
{leaveChannelOption}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>);
|
|
||||||
} else {
|
|
||||||
channelMenuDropdown = (<div className='navbar-brand'>
|
|
||||||
<a href='/' className='heading'>{channelTitle}</a>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className='navbar navbar-default navbar-fixed-top' role='navigation'>
|
<nav
|
||||||
|
className='navbar navbar-default navbar-fixed-top'
|
||||||
|
role='navigation'
|
||||||
|
>
|
||||||
<div className='container-fluid theme'>
|
<div className='container-fluid theme'>
|
||||||
<div className='navbar-header'>
|
<div className='navbar-header'>
|
||||||
{navbarCollapseButton}
|
{collapseButtons}
|
||||||
{sidebarCollapseButton}
|
|
||||||
{rightSidebarCollapseButton}
|
|
||||||
{channelMenuDropdown}
|
{channelMenuDropdown}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
Navbar.defaultProps = {
|
||||||
|
teamDisplayName: ''
|
||||||
|
};
|
||||||
|
Navbar.propTypes = {
|
||||||
|
teamDisplayName: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,23 +1,42 @@
|
|||||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var UserProfile = require( './user_profile.jsx' );
|
var UserProfile = require('./user_profile.jsx');
|
||||||
var PostInfo = require('./post_info.jsx');
|
var PostInfo = require('./post_info.jsx');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class PostHeader extends React.Component {
|
||||||
getInitialState: function() {
|
constructor(props) {
|
||||||
return { };
|
super(props);
|
||||||
},
|
this.state = {};
|
||||||
render: function() {
|
}
|
||||||
|
render() {
|
||||||
var post = this.props.post;
|
var post = this.props.post;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="post-header post-header-post">
|
<ul className='post-header post-header-post'>
|
||||||
<li className="post-header-col post-header__name"><strong><UserProfile userId={post.user_id} /></strong></li>
|
<li className='post-header-col post-header__name'><strong><UserProfile userId={post.user_id} /></strong></li>
|
||||||
<li className="post-info--hidden">
|
<li className='post-info--hidden'>
|
||||||
<PostInfo post={post} commentCount={this.props.commentCount} handleCommentClick={this.props.handleCommentClick} allowReply="true" isLastComment={this.props.isLastComment} />
|
<PostInfo
|
||||||
|
post={post}
|
||||||
|
commentCount={this.props.commentCount}
|
||||||
|
handleCommentClick={this.props.handleCommentClick}
|
||||||
|
allowReply='true'
|
||||||
|
isLastComment={this.props.isLastComment}
|
||||||
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
PostHeader.defaultProps = {
|
||||||
|
post: null,
|
||||||
|
commentCount: 0,
|
||||||
|
isLastComment: false
|
||||||
|
};
|
||||||
|
PostHeader.propTypes = {
|
||||||
|
post: React.PropTypes.object,
|
||||||
|
commentCount: React.PropTypes.number,
|
||||||
|
isLastComment: React.PropTypes.bool,
|
||||||
|
handleCommentClick: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,127 +8,137 @@ var SocketStore = require('../stores/socket_store.jsx');
|
|||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
var TeamStore = require('../stores/team_store.jsx');
|
var TeamStore = require('../stores/team_store.jsx');
|
||||||
var BrowserStore = require('../stores/browser_store.jsx');
|
var BrowserStore = require('../stores/browser_store.jsx');
|
||||||
var utils = require('../utils/utils.jsx');
|
var Utils = require('../utils/utils.jsx');
|
||||||
var SidebarHeader = require('./sidebar_header.jsx');
|
var SidebarHeader = require('./sidebar_header.jsx');
|
||||||
var SearchBox = require('./search_bar.jsx');
|
var SearchBox = require('./search_bar.jsx');
|
||||||
var Constants = require('../utils/constants.jsx');
|
var Constants = require('../utils/constants.jsx');
|
||||||
|
|
||||||
function getStateFromStores() {
|
export default class Sidebar extends React.Component {
|
||||||
var members = ChannelStore.getAllMembers();
|
constructor(props) {
|
||||||
var teamMemberMap = UserStore.getActiveOnlyProfiles();
|
super(props);
|
||||||
var currentId = ChannelStore.getCurrentId();
|
|
||||||
|
|
||||||
var teammates = [];
|
this.badgesActive = false;
|
||||||
for (var id in teamMemberMap) {
|
this.firstUnreadChannel = null;
|
||||||
if (id === UserStore.getCurrentId()) {
|
this.lastUnreadChannel = null;
|
||||||
continue;
|
|
||||||
}
|
this.onChange = this.onChange.bind(this);
|
||||||
teammates.push(teamMemberMap[id]);
|
this.onScroll = this.onScroll.bind(this);
|
||||||
|
this.onResize = this.onResize.bind(this);
|
||||||
|
this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this);
|
||||||
|
this.createChannelElement = this.createChannelElement.bind(this);
|
||||||
|
|
||||||
|
this.state = this.getStateFromStores();
|
||||||
|
this.state.loadingDMChannel = -1;
|
||||||
}
|
}
|
||||||
|
getStateFromStores() {
|
||||||
|
var members = ChannelStore.getAllMembers();
|
||||||
|
var teamMemberMap = UserStore.getActiveOnlyProfiles();
|
||||||
|
var currentId = ChannelStore.getCurrentId();
|
||||||
|
|
||||||
// Create lists of all read and unread direct channels
|
var teammates = [];
|
||||||
var showDirectChannels = [];
|
for (var id in teamMemberMap) {
|
||||||
var readDirectChannels = [];
|
if (id === UserStore.getCurrentId()) {
|
||||||
for (var i = 0; i < teammates.length; i++) {
|
continue;
|
||||||
var teammate = teammates[i];
|
}
|
||||||
|
teammates.push(teamMemberMap[id]);
|
||||||
if (teammate.id === UserStore.getCurrentId()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelName = '';
|
// Create lists of all read and unread direct channels
|
||||||
if (teammate.id > UserStore.getCurrentId()) {
|
var showDirectChannels = [];
|
||||||
channelName = UserStore.getCurrentId() + '__' + teammate.id;
|
var readDirectChannels = [];
|
||||||
} else {
|
for (var i = 0; i < teammates.length; i++) {
|
||||||
channelName = teammate.id + '__' + UserStore.getCurrentId();
|
var teammate = teammates[i];
|
||||||
}
|
|
||||||
|
|
||||||
var channel = ChannelStore.getByName(channelName);
|
if (teammate.id === UserStore.getCurrentId()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (channel != null) {
|
var channelName = '';
|
||||||
channel.display_name = teammate.username;
|
if (teammate.id > UserStore.getCurrentId()) {
|
||||||
channel.teammate_username = teammate.username;
|
channelName = UserStore.getCurrentId() + '__' + teammate.id;
|
||||||
|
|
||||||
channel.status = UserStore.getStatus(teammate.id);
|
|
||||||
|
|
||||||
var channelMember = members[channel.id];
|
|
||||||
var msgCount = channel.total_msg_count - channelMember.msg_count;
|
|
||||||
if (msgCount > 0) {
|
|
||||||
showDirectChannels.push(channel);
|
|
||||||
} else if (currentId === channel.id) {
|
|
||||||
showDirectChannels.push(channel);
|
|
||||||
} else {
|
} else {
|
||||||
readDirectChannels.push(channel);
|
channelName = teammate.id + '__' + UserStore.getCurrentId();
|
||||||
|
}
|
||||||
|
|
||||||
|
var channel = ChannelStore.getByName(channelName);
|
||||||
|
|
||||||
|
if (channel != null) {
|
||||||
|
channel.display_name = teammate.username;
|
||||||
|
channel.teammate_username = teammate.username;
|
||||||
|
|
||||||
|
channel.status = UserStore.getStatus(teammate.id);
|
||||||
|
|
||||||
|
var channelMember = members[channel.id];
|
||||||
|
var msgCount = channel.total_msg_count - channelMember.msg_count;
|
||||||
|
if (msgCount > 0) {
|
||||||
|
showDirectChannels.push(channel);
|
||||||
|
} else if (currentId === channel.id) {
|
||||||
|
showDirectChannels.push(channel);
|
||||||
|
} else {
|
||||||
|
readDirectChannels.push(channel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var tempChannel = {};
|
||||||
|
tempChannel.fake = true;
|
||||||
|
tempChannel.name = channelName;
|
||||||
|
tempChannel.display_name = teammate.username;
|
||||||
|
tempChannel.teammate_username = teammate.username;
|
||||||
|
tempChannel.status = UserStore.getStatus(teammate.id);
|
||||||
|
tempChannel.last_post_at = 0;
|
||||||
|
tempChannel.total_msg_count = 0;
|
||||||
|
tempChannel.type = 'D';
|
||||||
|
readDirectChannels.push(tempChannel);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
var tempChannel = {};
|
|
||||||
tempChannel.fake = true;
|
|
||||||
tempChannel.name = channelName;
|
|
||||||
tempChannel.display_name = teammate.username;
|
|
||||||
tempChannel.teammate_username = teammate.username;
|
|
||||||
tempChannel.status = UserStore.getStatus(teammate.id);
|
|
||||||
tempChannel.last_post_at = 0;
|
|
||||||
tempChannel.total_msg_count = 0;
|
|
||||||
tempChannel.type = 'D';
|
|
||||||
readDirectChannels.push(tempChannel);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have MAX_DMS unread channels, sort the read list by last_post_at
|
// If we don't have MAX_DMS unread channels, sort the read list by last_post_at
|
||||||
if (showDirectChannels.length < Constants.MAX_DMS) {
|
if (showDirectChannels.length < Constants.MAX_DMS) {
|
||||||
readDirectChannels.sort(function sortByLastPost(a, b) {
|
readDirectChannels.sort(function sortByLastPost(a, b) {
|
||||||
// sort by last_post_at first
|
// sort by last_post_at first
|
||||||
if (a.last_post_at > b.last_post_at) {
|
if (a.last_post_at > b.last_post_at) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (a.last_post_at < b.last_post_at) {
|
if (a.last_post_at < b.last_post_at) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if last_post_at is equal, sort by name
|
// if last_post_at is equal, sort by name
|
||||||
if (a.display_name < b.display_name) {
|
if (a.display_name < b.display_name) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (a.display_name > b.display_name) {
|
if (a.display_name > b.display_name) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
while (showDirectChannels.length < Constants.MAX_DMS && index < readDirectChannels.length) {
|
while (showDirectChannels.length < Constants.MAX_DMS && index < readDirectChannels.length) {
|
||||||
showDirectChannels.push(readDirectChannels[index]);
|
showDirectChannels.push(readDirectChannels[index]);
|
||||||
index++;
|
index++;
|
||||||
|
}
|
||||||
|
readDirectChannels = readDirectChannels.slice(index);
|
||||||
|
|
||||||
|
showDirectChannels.sort(function directSort(a, b) {
|
||||||
|
if (a.display_name < b.display_name) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.display_name > b.display_name) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
readDirectChannels = readDirectChannels.slice(index);
|
|
||||||
|
|
||||||
showDirectChannels.sort(function directSort(a, b) {
|
return {
|
||||||
if (a.display_name < b.display_name) {
|
activeId: currentId,
|
||||||
return -1;
|
channels: ChannelStore.getAll(),
|
||||||
}
|
members: members,
|
||||||
if (a.display_name > b.display_name) {
|
showDirectChannels: showDirectChannels,
|
||||||
return 1;
|
hideDirectChannels: readDirectChannels
|
||||||
}
|
};
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
componentDidMount() {
|
||||||
return {
|
|
||||||
activeId: currentId,
|
|
||||||
channels: ChannelStore.getAll(),
|
|
||||||
members: members,
|
|
||||||
showDirectChannels: showDirectChannels,
|
|
||||||
hideDirectChannels: readDirectChannels
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
displayName: 'Sidebar',
|
|
||||||
propTypes: {
|
|
||||||
teamType: React.PropTypes.string,
|
|
||||||
teamDisplayName: React.PropTypes.string
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
|
||||||
ChannelStore.addChangeListener(this.onChange);
|
ChannelStore.addChangeListener(this.onChange);
|
||||||
UserStore.addChangeListener(this.onChange);
|
UserStore.addChangeListener(this.onChange);
|
||||||
UserStore.addStatusesChangeListener(this.onChange);
|
UserStore.addStatusesChangeListener(this.onChange);
|
||||||
@@ -140,12 +150,12 @@ module.exports = React.createClass({
|
|||||||
this.updateUnreadIndicators();
|
this.updateUnreadIndicators();
|
||||||
|
|
||||||
$(window).on('resize', this.onResize);
|
$(window).on('resize', this.onResize);
|
||||||
},
|
}
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate() {
|
||||||
this.updateTitle();
|
this.updateTitle();
|
||||||
this.updateUnreadIndicators();
|
this.updateUnreadIndicators();
|
||||||
},
|
}
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount() {
|
||||||
$(window).off('resize', this.onResize);
|
$(window).off('resize', this.onResize);
|
||||||
|
|
||||||
ChannelStore.removeChangeListener(this.onChange);
|
ChannelStore.removeChangeListener(this.onChange);
|
||||||
@@ -153,14 +163,14 @@ module.exports = React.createClass({
|
|||||||
UserStore.removeStatusesChangeListener(this.onChange);
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
TeamStore.removeChangeListener(this.onChange);
|
TeamStore.removeChangeListener(this.onChange);
|
||||||
SocketStore.removeChangeListener(this.onSocketChange);
|
SocketStore.removeChangeListener(this.onSocketChange);
|
||||||
},
|
}
|
||||||
onChange: function() {
|
onChange() {
|
||||||
var newState = getStateFromStores();
|
var newState = this.getStateFromStores();
|
||||||
if (!utils.areStatesEqual(newState, this.state)) {
|
if (!Utils.areStatesEqual(newState, this.state)) {
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
onSocketChange: function(msg) {
|
onSocketChange(msg) {
|
||||||
if (msg.action === 'posted') {
|
if (msg.action === 'posted') {
|
||||||
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
if (ChannelStore.getCurrentId() === msg.channel_id) {
|
||||||
if (window.isActive) {
|
if (window.isActive) {
|
||||||
@@ -208,17 +218,17 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
if (notifyText.length === 0) {
|
if (notifyText.length === 0) {
|
||||||
if (msgProps.image) {
|
if (msgProps.image) {
|
||||||
utils.notifyMe(title, username + ' uploaded an image', channel);
|
Utils.notifyMe(title, username + ' uploaded an image', channel);
|
||||||
} else if (msgProps.otherFile) {
|
} else if (msgProps.otherFile) {
|
||||||
utils.notifyMe(title, username + ' uploaded a file', channel);
|
Utils.notifyMe(title, username + ' uploaded a file', channel);
|
||||||
} else {
|
} else {
|
||||||
utils.notifyMe(title, username + ' did something new', channel);
|
Utils.notifyMe(title, username + ' did something new', channel);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
|
Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
|
||||||
}
|
}
|
||||||
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
|
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
|
||||||
utils.ding();
|
Utils.ding();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (msg.action === 'viewed') {
|
} else if (msg.action === 'viewed') {
|
||||||
@@ -243,186 +253,196 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
updateTitle: function() {
|
updateTitle() {
|
||||||
var channel = ChannelStore.getCurrent();
|
var channel = ChannelStore.getCurrent();
|
||||||
if (channel) {
|
if (channel) {
|
||||||
if (channel.type === 'D') {
|
if (channel.type === 'D') {
|
||||||
var teammateUsername = utils.getDirectTeammate(channel.id).username;
|
var teammateUsername = Utils.getDirectTeammate(channel.id).username;
|
||||||
document.title = teammateUsername + ' ' + document.title.substring(document.title.lastIndexOf('-'));
|
document.title = teammateUsername + ' ' + document.title.substring(document.title.lastIndexOf('-'));
|
||||||
} else {
|
} else {
|
||||||
document.title = channel.display_name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
|
document.title = channel.display_name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
onScroll: function() {
|
onScroll() {
|
||||||
this.updateUnreadIndicators();
|
this.updateUnreadIndicators();
|
||||||
},
|
}
|
||||||
onResize: function() {
|
onResize() {
|
||||||
this.updateUnreadIndicators();
|
this.updateUnreadIndicators();
|
||||||
},
|
}
|
||||||
updateUnreadIndicators: function() {
|
updateUnreadIndicators() {
|
||||||
var container = $(this.refs.container.getDOMNode());
|
var container = $(React.findDOMNode(this.refs.container));
|
||||||
|
|
||||||
if (this.firstUnreadChannel) {
|
if (this.firstUnreadChannel) {
|
||||||
var firstUnreadElement = $(this.refs[this.firstUnreadChannel].getDOMNode());
|
var firstUnreadElement = $(React.findDOMNode(this.refs[this.firstUnreadChannel]));
|
||||||
|
|
||||||
if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) {
|
if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) {
|
||||||
$(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'initial');
|
$(React.findDOMNode(this.refs.topUnreadIndicator)).css('display', 'initial');
|
||||||
} else {
|
} else {
|
||||||
$(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'none');
|
$(React.findDOMNode(this.refs.topUnreadIndicator)).css('display', 'none');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.lastUnreadChannel) {
|
if (this.lastUnreadChannel) {
|
||||||
var lastUnreadElement = $(this.refs[this.lastUnreadChannel].getDOMNode());
|
var lastUnreadElement = $(React.findDOMNode(this.refs[this.lastUnreadChannel]));
|
||||||
|
|
||||||
if (lastUnreadElement.position().top > container.height()) {
|
if (lastUnreadElement.position().top > container.height()) {
|
||||||
$(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'initial');
|
$(React.findDOMNode(this.refs.bottomUnreadIndicator)).css('display', 'initial');
|
||||||
} else {
|
} else {
|
||||||
$(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'none');
|
$(React.findDOMNode(this.refs.bottomUnreadIndicator)).css('display', 'none');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
createChannelElement(channel, index) {
|
||||||
var newState = getStateFromStores();
|
|
||||||
newState.loadingDMChannel = -1;
|
|
||||||
|
|
||||||
return newState;
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var members = this.state.members;
|
var members = this.state.members;
|
||||||
var activeId = this.state.activeId;
|
var activeId = this.state.activeId;
|
||||||
var badgesActive = false;
|
var channelMember = members[channel.id];
|
||||||
|
var msgCount;
|
||||||
|
|
||||||
// keep track of the first and last unread channels so we can use them to set the unread indicators
|
var linkClass = '';
|
||||||
var self = this;
|
if (channel.id === activeId) {
|
||||||
this.firstUnreadChannel = null;
|
linkClass = 'active';
|
||||||
this.lastUnreadChannel = null;
|
}
|
||||||
|
|
||||||
function createChannelElement(channel, index) {
|
var unread = false;
|
||||||
var channelMember = members[channel.id];
|
if (channelMember) {
|
||||||
var msgCount;
|
msgCount = channel.total_msg_count - channelMember.msg_count;
|
||||||
|
unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
var linkClass = '';
|
var titleClass = '';
|
||||||
if (channel.id === activeId) {
|
if (unread) {
|
||||||
linkClass = 'active';
|
titleClass = 'unread-title';
|
||||||
|
|
||||||
|
if (!this.firstUnreadChannel) {
|
||||||
|
this.firstUnreadChannel = channel.name;
|
||||||
}
|
}
|
||||||
|
this.lastUnreadChannel = channel.name;
|
||||||
|
}
|
||||||
|
|
||||||
var unread = false;
|
var badge = null;
|
||||||
if (channelMember) {
|
if (channelMember) {
|
||||||
msgCount = channel.total_msg_count - channelMember.msg_count;
|
|
||||||
unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var titleClass = '';
|
|
||||||
if (unread) {
|
|
||||||
titleClass = 'unread-title';
|
|
||||||
|
|
||||||
if (!self.firstUnreadChannel) {
|
|
||||||
self.firstUnreadChannel = channel.name;
|
|
||||||
}
|
|
||||||
self.lastUnreadChannel = channel.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var badge = null;
|
|
||||||
if (channelMember) {
|
|
||||||
if (channel.type === 'D') {
|
|
||||||
// direct message channels show badges for any number of unread posts
|
|
||||||
msgCount = channel.total_msg_count - channelMember.msg_count;
|
|
||||||
if (msgCount > 0) {
|
|
||||||
badge = <span className='badge pull-right small'>{msgCount}</span>;
|
|
||||||
badgesActive = true;
|
|
||||||
}
|
|
||||||
} else if (channelMember.mention_count > 0) {
|
|
||||||
// public and private channels only show badges for mentions
|
|
||||||
badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
|
|
||||||
badgesActive = true;
|
|
||||||
}
|
|
||||||
} else if (self.state.loadingDMChannel === index && channel.type === 'D') {
|
|
||||||
badge = <img className='channel-loading-gif pull-right' src='/static/images/load.gif'/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up status icon for direct message channels
|
|
||||||
var status = null;
|
|
||||||
if (channel.type === 'D') {
|
if (channel.type === 'D') {
|
||||||
var statusIcon = '';
|
// direct message channels show badges for any number of unread posts
|
||||||
if (channel.status === 'online') {
|
msgCount = channel.total_msg_count - channelMember.msg_count;
|
||||||
statusIcon = Constants.ONLINE_ICON_SVG;
|
if (msgCount > 0) {
|
||||||
} else if (channel.status === 'away') {
|
badge = <span className='badge pull-right small'>{msgCount}</span>;
|
||||||
statusIcon = Constants.ONLINE_ICON_SVG;
|
this.badgesActive = true;
|
||||||
} else {
|
|
||||||
statusIcon = Constants.OFFLINE_ICON_SVG;
|
|
||||||
}
|
}
|
||||||
status = <span className='status' dangerouslySetInnerHTML={{__html: statusIcon}} />;
|
} else if (channelMember.mention_count > 0) {
|
||||||
|
// public and private channels only show badges for mentions
|
||||||
|
badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
|
||||||
|
this.badgesActive = true;
|
||||||
}
|
}
|
||||||
|
} else if (this.state.loadingDMChannel === index && channel.type === 'D') {
|
||||||
// set up click handler to switch channels (or create a new channel for non-existant ones)
|
badge = (
|
||||||
var handleClick = null;
|
<img
|
||||||
var href = '#';
|
className='channel-loading-gif pull-right'
|
||||||
var teamURL = TeamStore.getCurrentTeamUrl();
|
src='/static/images/load.gif'
|
||||||
|
/>
|
||||||
if (!channel.fake) {
|
|
||||||
handleClick = function clickHandler(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
utils.switchChannel(channel);
|
|
||||||
};
|
|
||||||
} else if (channel.fake && teamURL) {
|
|
||||||
// It's a direct message channel that doesn't exist yet so let's create it now
|
|
||||||
var otherUserId = utils.getUserIdFromChannelName(channel);
|
|
||||||
|
|
||||||
if (self.state.loadingDMChannel === -1) {
|
|
||||||
handleClick = function clickHandler(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
self.setState({loadingDMChannel: index});
|
|
||||||
|
|
||||||
Client.createDirectChannel(channel, otherUserId,
|
|
||||||
function success(data) {
|
|
||||||
self.setState({loadingDMChannel: -1});
|
|
||||||
AsyncClient.getChannel(data.id);
|
|
||||||
utils.switchChannel(data);
|
|
||||||
},
|
|
||||||
function error() {
|
|
||||||
self.setState({loadingDMChannel: -1});
|
|
||||||
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={channel.name} ref={channel.name} className={linkClass}>
|
|
||||||
<a className={'sidebar-channel ' + titleClass} href={href} onClick={handleClick}>
|
|
||||||
{status}
|
|
||||||
{channel.display_name}
|
|
||||||
{badge}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set up status icon for direct message channels
|
||||||
|
var status = null;
|
||||||
|
if (channel.type === 'D') {
|
||||||
|
var statusIcon = '';
|
||||||
|
if (channel.status === 'online') {
|
||||||
|
statusIcon = Constants.ONLINE_ICON_SVG;
|
||||||
|
} else if (channel.status === 'away') {
|
||||||
|
statusIcon = Constants.ONLINE_ICON_SVG;
|
||||||
|
} else {
|
||||||
|
statusIcon = Constants.OFFLINE_ICON_SVG;
|
||||||
|
}
|
||||||
|
status = (
|
||||||
|
<span
|
||||||
|
className='status'
|
||||||
|
dangerouslySetInnerHTML={{__html: statusIcon}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up click handler to switch channels (or create a new channel for non-existant ones)
|
||||||
|
var handleClick = null;
|
||||||
|
var href = '#';
|
||||||
|
var teamURL = TeamStore.getCurrentTeamUrl();
|
||||||
|
|
||||||
|
if (!channel.fake) {
|
||||||
|
handleClick = function clickHandler(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
Utils.switchChannel(channel);
|
||||||
|
};
|
||||||
|
} else if (channel.fake && teamURL) {
|
||||||
|
// It's a direct message channel that doesn't exist yet so let's create it now
|
||||||
|
var otherUserId = Utils.getUserIdFromChannelName(channel);
|
||||||
|
|
||||||
|
if (this.state.loadingDMChannel === -1) {
|
||||||
|
handleClick = function clickHandler(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState({loadingDMChannel: index});
|
||||||
|
|
||||||
|
Client.createDirectChannel(channel, otherUserId,
|
||||||
|
function success(data) {
|
||||||
|
this.setState({loadingDMChannel: -1});
|
||||||
|
AsyncClient.getChannel(data.id);
|
||||||
|
Utils.switchChannel(data);
|
||||||
|
},
|
||||||
|
function error() {
|
||||||
|
this.setState({loadingDMChannel: -1});
|
||||||
|
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={channel.name}
|
||||||
|
ref={channel.name}
|
||||||
|
className={linkClass}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className={'sidebar-channel ' + titleClass}
|
||||||
|
href={href}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
{channel.display_name}
|
||||||
|
{badge}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
this.badgesActive = false;
|
||||||
|
|
||||||
|
// keep track of the first and last unread channels so we can use them to set the unread indicators
|
||||||
|
this.firstUnreadChannel = null;
|
||||||
|
this.lastUnreadChannel = null;
|
||||||
|
|
||||||
// create elements for all 3 types of channels
|
// create elements for all 3 types of channels
|
||||||
var channelItems = this.state.channels.filter(
|
var channelItems = this.state.channels.filter(
|
||||||
function filterPublicChannels(channel) {
|
function filterPublicChannels(channel) {
|
||||||
return channel.type === 'O';
|
return channel.type === 'O';
|
||||||
}
|
}
|
||||||
).map(createChannelElement);
|
).map(this.createChannelElement);
|
||||||
|
|
||||||
var privateChannelItems = this.state.channels.filter(
|
var privateChannelItems = this.state.channels.filter(
|
||||||
function filterPrivateChannels(channel) {
|
function filterPrivateChannels(channel) {
|
||||||
return channel.type === 'P';
|
return channel.type === 'P';
|
||||||
}
|
}
|
||||||
).map(createChannelElement);
|
).map(this.createChannelElement);
|
||||||
|
|
||||||
var directMessageItems = this.state.showDirectChannels.map(createChannelElement);
|
var directMessageItems = this.state.showDirectChannels.map(this.createChannelElement);
|
||||||
|
|
||||||
// update the favicon to show if there are any notifications
|
// update the favicon to show if there are any notifications
|
||||||
var link = document.createElement('link');
|
var link = document.createElement('link');
|
||||||
link.type = 'image/x-icon';
|
link.type = 'image/x-icon';
|
||||||
link.rel = 'shortcut icon';
|
link.rel = 'shortcut icon';
|
||||||
link.id = 'favicon';
|
link.id = 'favicon';
|
||||||
if (badgesActive) {
|
if (this.badgesActive) {
|
||||||
link.href = '/static/images/redfavicon.ico';
|
link.href = '/static/images/redfavicon.ico';
|
||||||
} else {
|
} else {
|
||||||
link.href = '/static/images/favicon.ico';
|
link.href = '/static/images/favicon.ico';
|
||||||
@@ -438,7 +458,13 @@ module.exports = React.createClass({
|
|||||||
if (this.state.hideDirectChannels.length > 0) {
|
if (this.state.hideDirectChannels.length > 0) {
|
||||||
directMessageMore = (
|
directMessageMore = (
|
||||||
<li>
|
<li>
|
||||||
<a href='#' data-toggle='modal' className='nav-more' data-target='#more_direct_channels' data-channels={JSON.stringify(this.state.hideDirectChannels)}>
|
<a
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
className='nav-more'
|
||||||
|
data-target='#more_direct_channels'
|
||||||
|
data-channels={JSON.stringify(this.state.hideDirectChannels)}
|
||||||
|
>
|
||||||
{'More (' + this.state.hideDirectChannels.length + ')'}
|
{'More (' + this.state.hideDirectChannels.length + ')'}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -447,21 +473,76 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
|
<SidebarHeader
|
||||||
|
teamDisplayName={this.props.teamDisplayName}
|
||||||
|
teamType={this.props.teamType}
|
||||||
|
/>
|
||||||
<SearchBox />
|
<SearchBox />
|
||||||
|
|
||||||
<div ref='topUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-top' style={{display: 'none'}}>Unread post(s) above</div>
|
<div
|
||||||
<div ref='bottomUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom' style={{display: 'none'}}>Unread post(s) below</div>
|
ref='topUnreadIndicator'
|
||||||
|
className='nav-pills__unread-indicator nav-pills__unread-indicator-top'
|
||||||
|
style={{display: 'none'}}
|
||||||
|
>
|
||||||
|
Unread post(s) above
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref='bottomUnreadIndicator'
|
||||||
|
className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom'
|
||||||
|
style={{display: 'none'}}
|
||||||
|
>
|
||||||
|
Unread post(s) below
|
||||||
|
</div>
|
||||||
|
|
||||||
<div ref='container' className='nav-pills__container' onScroll={this.onScroll}>
|
<div
|
||||||
|
ref='container'
|
||||||
|
className='nav-pills__container'
|
||||||
|
onScroll={this.onScroll}
|
||||||
|
>
|
||||||
<ul className='nav nav-pills nav-stacked'>
|
<ul className='nav nav-pills nav-stacked'>
|
||||||
<li><h4>Channels<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='O'>+</a></h4></li>
|
<li>
|
||||||
|
<h4>
|
||||||
|
Channels
|
||||||
|
<a
|
||||||
|
className='add-channel-btn'
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#new_channel'
|
||||||
|
data-channeltype='O'
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
</li>
|
||||||
{channelItems}
|
{channelItems}
|
||||||
<li><a href='#' data-toggle='modal' className='nav-more' data-target='#more_channels' data-channeltype='O'>More...</a></li>
|
<li>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
className='nav-more'
|
||||||
|
data-target='#more_channels'
|
||||||
|
data-channeltype='O'
|
||||||
|
>
|
||||||
|
More...
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul className='nav nav-pills nav-stacked'>
|
<ul className='nav nav-pills nav-stacked'>
|
||||||
<li><h4>Private Groups<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='P'>+</a></h4></li>
|
<li>
|
||||||
|
<h4>
|
||||||
|
Private Groups
|
||||||
|
<a
|
||||||
|
className='add-channel-btn'
|
||||||
|
href='#'
|
||||||
|
data-toggle='modal'
|
||||||
|
data-target='#new_channel'
|
||||||
|
data-channeltype='P'
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
</li>
|
||||||
{privateChannelItems}
|
{privateChannelItems}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className='nav nav-pills nav-stacked'>
|
<ul className='nav nav-pills nav-stacked'>
|
||||||
@@ -473,4 +554,13 @@ module.exports = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
Sidebar.defaultProps = {
|
||||||
|
teamType: '',
|
||||||
|
teamDisplayName: ''
|
||||||
|
};
|
||||||
|
Sidebar.propTypes = {
|
||||||
|
teamType: React.PropTypes.string,
|
||||||
|
teamDisplayName: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,37 +4,27 @@
|
|||||||
var SettingItemMin = require('./setting_item_min.jsx');
|
var SettingItemMin = require('./setting_item_min.jsx');
|
||||||
var SettingItemMax = require('./setting_item_max.jsx');
|
var SettingItemMax = require('./setting_item_max.jsx');
|
||||||
|
|
||||||
var client = require('../utils/client.jsx');
|
var Client = require('../utils/client.jsx');
|
||||||
var AsyncClient = require('../utils/async_client.jsx');
|
var AsyncClient = require('../utils/async_client.jsx');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class FeatureTab extends React.Component {
|
||||||
displayName: 'Feature Tab',
|
constructor(props) {
|
||||||
propTypes: {
|
super(props);
|
||||||
updateSection: React.PropTypes.func.isRequired,
|
|
||||||
team: React.PropTypes.object.isRequired,
|
|
||||||
activeSection: React.PropTypes.string.isRequired
|
|
||||||
},
|
|
||||||
submitValetFeature: function() {
|
|
||||||
var data = {};
|
|
||||||
data.allow_valet = this.state.allowValet;
|
|
||||||
|
|
||||||
client.updateValetFeature(data,
|
this.submitValetFeature = this.submitValetFeature.bind(this);
|
||||||
function() {
|
this.handleValetRadio = this.handleValetRadio.bind(this);
|
||||||
this.props.updateSection('');
|
this.onUpdateSection = this.onUpdateSection.bind(this);
|
||||||
AsyncClient.getMyTeam();
|
|
||||||
}.bind(this),
|
this.state = {};
|
||||||
function(err) {
|
var team = this.props.team;
|
||||||
var state = this.getInitialState();
|
|
||||||
state.serverError = err;
|
if (team && team.allow_valet) {
|
||||||
this.setState(state);
|
this.state.allowValet = 'true';
|
||||||
}.bind(this)
|
} else {
|
||||||
);
|
this.state.allowValet = 'false';
|
||||||
},
|
}
|
||||||
handleValetRadio: function(val) {
|
}
|
||||||
this.setState({allowValet: val});
|
componentWillReceiveProps(newProps) {
|
||||||
this.refs.wrapper.getDOMNode().focus();
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
|
||||||
var team = newProps.team;
|
var team = newProps.team;
|
||||||
|
|
||||||
var allowValet = 'false';
|
var allowValet = 'false';
|
||||||
@@ -43,26 +33,36 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState({allowValet: allowValet});
|
this.setState({allowValet: allowValet});
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
submitValetFeature() {
|
||||||
var team = this.props.team;
|
var data = {};
|
||||||
|
data.allow_valet = this.state.allowValet;
|
||||||
|
|
||||||
var allowValet = 'false';
|
Client.updateValetFeature(data,
|
||||||
if (team && team.allow_valet) {
|
function success() {
|
||||||
allowValet = 'true';
|
this.props.updateSection('');
|
||||||
}
|
AsyncClient.getMyTeam();
|
||||||
|
}.bind(this),
|
||||||
return {allowValet: allowValet};
|
function fail(err) {
|
||||||
},
|
var state = this.getInitialState();
|
||||||
onUpdateSection: function(e) {
|
state.serverError = err;
|
||||||
|
this.setState(state);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
handleValetRadio(val) {
|
||||||
|
this.setState({allowValet: val});
|
||||||
|
React.findDOMNode(this.refs.wrapper).focus();
|
||||||
|
}
|
||||||
|
onUpdateSection(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.props.activeSection === 'valet') {
|
if (this.props.activeSection === 'valet') {
|
||||||
this.props.updateSection('');
|
this.props.updateSection('');
|
||||||
} else {
|
} else {
|
||||||
this.props.updateSection('valet');
|
this.props.updateSection('valet');
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
render: function() {
|
render() {
|
||||||
var clientError = null;
|
var clientError = null;
|
||||||
var serverError = null;
|
var serverError = null;
|
||||||
if (this.state.clientError) {
|
if (this.state.clientError) {
|
||||||
@@ -73,7 +73,6 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
var valetSection;
|
var valetSection;
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.props.activeSection === 'valet') {
|
if (this.props.activeSection === 'valet') {
|
||||||
var valetActive = [false, false];
|
var valetActive = [false, false];
|
||||||
@@ -92,7 +91,7 @@ module.exports = React.createClass({
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
checked={valetActive[0]}
|
checked={valetActive[0]}
|
||||||
onChange={self.handleValetRadio.bind(this, 'true')}
|
onChange={this.handleValetRadio.bind(this, 'true')}
|
||||||
>
|
>
|
||||||
On
|
On
|
||||||
</input>
|
</input>
|
||||||
@@ -104,7 +103,7 @@ module.exports = React.createClass({
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
checked={valetActive[1]}
|
checked={valetActive[1]}
|
||||||
onChange={self.handleValetRadio.bind(this, 'false')}
|
onChange={this.handleValetRadio.bind(this, 'false')}
|
||||||
>
|
>
|
||||||
Off
|
Off
|
||||||
</input>
|
</input>
|
||||||
@@ -145,10 +144,25 @@ module.exports = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>
|
<button
|
||||||
<h4 className='modal-title' ref='title'><i className='modal-back'></i>Advanced Features</h4>
|
type='button'
|
||||||
|
className='close'
|
||||||
|
data-dismiss='modal'
|
||||||
|
aria-label='Close'
|
||||||
|
>
|
||||||
|
<span aria-hidden='true'>×</span>
|
||||||
|
</button>
|
||||||
|
<h4
|
||||||
|
className='modal-title'
|
||||||
|
ref='title'
|
||||||
|
>
|
||||||
|
<i className='modal-back'></i>Advanced Features
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div ref='wrapper' className='user-settings'>
|
<div
|
||||||
|
ref='wrapper'
|
||||||
|
className='user-settings'
|
||||||
|
>
|
||||||
<h3 className='tab-header'>Advanced Features</h3>
|
<h3 className='tab-header'>Advanced Features</h3>
|
||||||
<div className='divider-dark first'/>
|
<div className='divider-dark first'/>
|
||||||
{valetSection}
|
{valetSection}
|
||||||
@@ -157,4 +171,14 @@ module.exports = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
FeatureTab.defaultProps = {
|
||||||
|
team: {},
|
||||||
|
activeSection: ''
|
||||||
|
};
|
||||||
|
FeatureTab.propTypes = {
|
||||||
|
updateSection: React.PropTypes.func.isRequired,
|
||||||
|
team: React.PropTypes.object.isRequired,
|
||||||
|
activeSection: React.PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,66 +5,83 @@ var TeamStore = require('../stores/team_store.jsx');
|
|||||||
var ImportTab = require('./team_import_tab.jsx');
|
var ImportTab = require('./team_import_tab.jsx');
|
||||||
var FeatureTab = require('./team_feature_tab.jsx');
|
var FeatureTab = require('./team_feature_tab.jsx');
|
||||||
var GeneralTab = require('./team_general_tab.jsx');
|
var GeneralTab = require('./team_general_tab.jsx');
|
||||||
var utils = require('../utils/utils.jsx');
|
var Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class TeamSettings extends React.Component {
|
||||||
displayName: 'Team Settings',
|
constructor(props) {
|
||||||
propTypes: {
|
super(props);
|
||||||
activeTab: React.PropTypes.string.isRequired,
|
|
||||||
activeSection: React.PropTypes.string.isRequired,
|
this.onChange = this.onChange.bind(this);
|
||||||
updateSection: React.PropTypes.func.isRequired,
|
|
||||||
teamDisplayName: React.PropTypes.string.isRequired
|
this.state = {team: TeamStore.getCurrent()};
|
||||||
},
|
}
|
||||||
componentDidMount: function() {
|
componentDidMount() {
|
||||||
TeamStore.addChangeListener(this.onChange);
|
TeamStore.addChangeListener(this.onChange);
|
||||||
},
|
}
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount() {
|
||||||
TeamStore.removeChangeListener(this.onChange);
|
TeamStore.removeChangeListener(this.onChange);
|
||||||
},
|
}
|
||||||
onChange: function() {
|
onChange() {
|
||||||
var team = TeamStore.getCurrent();
|
var team = TeamStore.getCurrent();
|
||||||
if (!utils.areStatesEqual(this.state.team, team)) {
|
if (!Utils.areStatesEqual(this.state.team, team)) {
|
||||||
this.setState({team: team});
|
this.setState({team: team});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
render() {
|
||||||
return {team: TeamStore.getCurrent()};
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var result;
|
var result;
|
||||||
switch (this.props.activeTab) {
|
switch (this.props.activeTab) {
|
||||||
case 'general':
|
case 'general':
|
||||||
result = (
|
result = (
|
||||||
<div>
|
<div>
|
||||||
<GeneralTab
|
<GeneralTab
|
||||||
team={this.state.team}
|
team={this.state.team}
|
||||||
activeSection={this.props.activeSection}
|
activeSection={this.props.activeSection}
|
||||||
updateSection={this.props.updateSection}
|
updateSection={this.props.updateSection}
|
||||||
teamDisplayName={this.props.teamDisplayName}
|
teamDisplayName={this.props.teamDisplayName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'feature':
|
case 'feature':
|
||||||
result = (
|
result = (
|
||||||
<div>
|
<div>
|
||||||
<FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
|
<FeatureTab
|
||||||
</div>
|
team={this.state.team}
|
||||||
);
|
activeSection={this.props.activeSection}
|
||||||
break;
|
updateSection={this.props.updateSection}
|
||||||
case 'import':
|
/>
|
||||||
result = (
|
</div>
|
||||||
<div>
|
);
|
||||||
<ImportTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
|
break;
|
||||||
</div>
|
case 'import':
|
||||||
);
|
result = (
|
||||||
break;
|
<div>
|
||||||
default:
|
<ImportTab
|
||||||
result = (
|
team={this.state.team}
|
||||||
<div/>
|
activeSection={this.props.activeSection}
|
||||||
);
|
updateSection={this.props.updateSection}
|
||||||
break;
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = (
|
||||||
|
<div/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
TeamSettings.defaultProps = {
|
||||||
|
activeTab: '',
|
||||||
|
activeSection: '',
|
||||||
|
teamDisplayName: ''
|
||||||
|
};
|
||||||
|
TeamSettings.propTypes = {
|
||||||
|
activeTab: React.PropTypes.string.isRequired,
|
||||||
|
activeSection: React.PropTypes.string.isRequired,
|
||||||
|
updateSection: React.PropTypes.func.isRequired,
|
||||||
|
teamDisplayName: React.PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var client = require('../utils/client.jsx');
|
var Client = require('../utils/client.jsx');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class TeamSignupAllowedDomainsPage extends React.Component {
|
||||||
displayName: 'TeamSignupAllowedDomainsPage',
|
constructor(props) {
|
||||||
propTypes: {
|
super(props);
|
||||||
state: React.PropTypes.object,
|
|
||||||
updateParent: React.PropTypes.func
|
this.submitBack = this.submitBack.bind(this);
|
||||||
},
|
this.submitNext = this.submitNext.bind(this);
|
||||||
submitBack: function(e) {
|
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
submitBack(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.state.wizard = 'team_url';
|
this.props.state.wizard = 'team_url';
|
||||||
this.props.updateParent(this.props.state);
|
this.props.updateParent(this.props.state);
|
||||||
},
|
}
|
||||||
submitNext: function(e) {
|
submitNext(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.refs.open_network.getDOMNode().checked) {
|
if (React.findDOMNode(this.refs.open_network).checked) {
|
||||||
this.props.state.wizard = 'send_invites';
|
this.props.state.wizard = 'send_invites';
|
||||||
this.props.state.team.type = 'O';
|
this.props.state.team.type = 'O';
|
||||||
this.props.updateParent(this.props.state);
|
this.props.updateParent(this.props.state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.refs.allow.getDOMNode().checked) {
|
if (React.findDOMNode(this.refs.allow).checked) {
|
||||||
var name = this.refs.name.getDOMNode().value.trim();
|
var name = React.findDOMNode(this.refs.name).value.trim();
|
||||||
var domainRegex = /^\w+\.\w+$/;
|
var domainRegex = /^\w+\.\w+$/;
|
||||||
if (!name) {
|
if (!name) {
|
||||||
this.setState({nameError: 'This field is required'});
|
this.setState({nameError: 'This field is required'});
|
||||||
@@ -46,12 +49,9 @@ module.exports = React.createClass({
|
|||||||
this.props.state.team.type = 'I';
|
this.props.state.team.type = 'I';
|
||||||
this.props.updateParent(this.props.state);
|
this.props.updateParent(this.props.state);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
render() {
|
||||||
return {};
|
Client.track('signup', 'signup_team_04_allow_domains');
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
client.track('signup', 'signup_team_04_allow_domains');
|
|
||||||
|
|
||||||
var nameError = null;
|
var nameError = null;
|
||||||
var nameDivClass = 'form-group';
|
var nameDivClass = 'form-group';
|
||||||
@@ -63,11 +63,21 @@ module.exports = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form>
|
<form>
|
||||||
<img className='signup-team-logo' src='/static/images/logo.png' />
|
<img
|
||||||
|
className='signup-team-logo'
|
||||||
|
src='/static/images/logo.png'
|
||||||
|
/>
|
||||||
<h2>Email Domain</h2>
|
<h2>Email Domain</h2>
|
||||||
<p>
|
<p>
|
||||||
<div className='checkbox'>
|
<div className='checkbox'>
|
||||||
<label><input type='checkbox' ref='allow' defaultChecked={true} />{' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'}</label>
|
<label>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
ref='allow'
|
||||||
|
defaultChecked={true}
|
||||||
|
/>
|
||||||
|
{' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</p>
|
||||||
<p>{'Check this box to allow your ' + strings.Team + ' members to sign up using their ' + strings.Company + ' email addresses if you share the same domain--otherwise, you need to invite everyone yourself.'}</p>
|
<p>{'Check this box to allow your ' + strings.Team + ' members to sign up using their ' + strings.Company + ' email addresses if you share the same domain--otherwise, you need to invite everyone yourself.'}</p>
|
||||||
@@ -77,7 +87,16 @@ module.exports = React.createClass({
|
|||||||
<div className='col-sm-9'>
|
<div className='col-sm-9'>
|
||||||
<div className='input-group'>
|
<div className='input-group'>
|
||||||
<span className='input-group-addon'>@</span>
|
<span className='input-group-addon'>@</span>
|
||||||
<input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.allowed_domains} autoFocus={true} onFocus={this.handleFocus}/>
|
<input
|
||||||
|
type='text'
|
||||||
|
ref='name'
|
||||||
|
className='form-control'
|
||||||
|
placeholder=''
|
||||||
|
maxLength='128'
|
||||||
|
defaultValue={this.props.state.team.allowed_domains}
|
||||||
|
autoFocus={true}
|
||||||
|
onFocus={this.handleFocus}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,13 +105,38 @@ module.exports = React.createClass({
|
|||||||
<p>To allow signups from multiple domains, separate each with a comma.</p>
|
<p>To allow signups from multiple domains, separate each with a comma.</p>
|
||||||
<p>
|
<p>
|
||||||
<div className='checkbox'>
|
<div className='checkbox'>
|
||||||
<label><input type='checkbox' ref='open_network' defaultChecked={this.props.state.team.type === 'O'} /> Allow anyone to signup to this domain without an invitation.</label>
|
<label>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
ref='open_network'
|
||||||
|
defaultChecked={this.props.state.team.type === 'O'}
|
||||||
|
/> Allow anyone to signup to this domain without an invitation.</label>
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</p>
|
||||||
<button type='button' className='btn btn-default' onClick={this.submitBack}><i className='glyphicon glyphicon-chevron-left'></i> Back</button>
|
<button
|
||||||
<button type='submit' className='btn-primary btn' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
|
type='button'
|
||||||
|
className='btn btn-default'
|
||||||
|
onClick={this.submitBack}
|
||||||
|
>
|
||||||
|
<i className='glyphicon glyphicon-chevron-left'></i> Back
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='btn-primary btn'
|
||||||
|
onClick={this.submitNext}
|
||||||
|
>
|
||||||
|
Next<i className='glyphicon glyphicon-chevron-right'></i>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
TeamSignupAllowedDomainsPage.defaultProps = {
|
||||||
|
state: {}
|
||||||
|
};
|
||||||
|
TeamSignupAllowedDomainsPage.propTypes = {
|
||||||
|
state: React.PropTypes.object,
|
||||||
|
updateParent: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var utils = require('../utils/utils.jsx');
|
var Client = require('../utils/client.jsx');
|
||||||
var client = require('../utils/client.jsx');
|
var BrowserStore = require('../stores/browser_store.jsx');
|
||||||
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class TeamSignupPasswordPage extends React.Component {
|
||||||
displayName: 'TeamSignupPasswordPage',
|
constructor(props) {
|
||||||
propTypes: {
|
super(props);
|
||||||
state: React.PropTypes.object,
|
|
||||||
updateParent: React.PropTypes.func
|
this.submitBack = this.submitBack.bind(this);
|
||||||
},
|
this.submitNext = this.submitNext.bind(this);
|
||||||
submitBack: function(e) {
|
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
submitBack(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.state.wizard = 'username';
|
this.props.state.wizard = 'username';
|
||||||
this.props.updateParent(this.props.state);
|
this.props.updateParent(this.props.state);
|
||||||
},
|
}
|
||||||
submitNext: function(e) {
|
submitNext(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var password = this.refs.password.getDOMNode().value.trim();
|
var password = React.findDOMNode(this.refs.password).value.trim();
|
||||||
if (!password || password.length < 5) {
|
if (!password || password.length < 5) {
|
||||||
this.setState({passwordError: 'Please enter at least 5 characters'});
|
this.setState({passwordError: 'Please enter at least 5 characters'});
|
||||||
return;
|
return;
|
||||||
@@ -31,15 +35,14 @@ module.exports = React.createClass({
|
|||||||
teamSignup.user.allow_marketing = true;
|
teamSignup.user.allow_marketing = true;
|
||||||
delete teamSignup.wizard;
|
delete teamSignup.wizard;
|
||||||
|
|
||||||
client.createTeamFromSignup(teamSignup,
|
Client.createTeamFromSignup(teamSignup,
|
||||||
function success() {
|
function success() {
|
||||||
client.track('signup', 'signup_team_08_complete');
|
Client.track('signup', 'signup_team_08_complete');
|
||||||
|
|
||||||
var props = this.props;
|
var props = this.props;
|
||||||
|
|
||||||
|
Client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password,
|
||||||
client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password,
|
function loginSuccess() {
|
||||||
function(data) {
|
|
||||||
UserStore.setLastEmail(teamSignup.team.email);
|
UserStore.setLastEmail(teamSignup.team.email);
|
||||||
UserStore.setCurrentUser(data);
|
UserStore.setCurrentUser(data);
|
||||||
if (this.props.hash > 0) {
|
if (this.props.hash > 0) {
|
||||||
@@ -52,7 +55,7 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
}.bind(this),
|
}.bind(this),
|
||||||
function(err) {
|
function loginFail(err) {
|
||||||
if (err.message === 'Login failed because email address has not been verified') {
|
if (err.message === 'Login failed because email address has not been verified') {
|
||||||
window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name);
|
window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name);
|
||||||
} else {
|
} else {
|
||||||
@@ -67,12 +70,9 @@ module.exports = React.createClass({
|
|||||||
$('#finish-button').button('reset');
|
$('#finish-button').button('reset');
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
render() {
|
||||||
return {};
|
Client.track('signup', 'signup_team_07_password');
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
client.track('signup', 'signup_team_07_password');
|
|
||||||
|
|
||||||
var passwordError = null;
|
var passwordError = null;
|
||||||
var passwordDivStyle = 'form-group';
|
var passwordDivStyle = 'form-group';
|
||||||
@@ -89,7 +89,10 @@ module.exports = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form>
|
<form>
|
||||||
<img className='signup-team-logo' src='/static/images/logo.png' />
|
<img
|
||||||
|
className='signup-team-logo'
|
||||||
|
src='/static/images/logo.png'
|
||||||
|
/>
|
||||||
<h2 className='margin--less'>Your password</h2>
|
<h2 className='margin--less'>Your password</h2>
|
||||||
<h5 className='color--light'>Select a password that you'll use to login with your email address:</h5>
|
<h5 className='color--light'>Select a password that you'll use to login with your email address:</h5>
|
||||||
<div className='inner__content margin--extra'>
|
<div className='inner__content margin--extra'>
|
||||||
@@ -99,7 +102,14 @@ module.exports = React.createClass({
|
|||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-sm-11'>
|
<div className='col-sm-11'>
|
||||||
<h5><strong>Choose your password</strong></h5>
|
<h5><strong>Choose your password</strong></h5>
|
||||||
<input autoFocus={true} type='password' ref='password' className='form-control' placeholder='' maxLength='128' />
|
<input
|
||||||
|
autoFocus={true}
|
||||||
|
type='password'
|
||||||
|
ref='password'
|
||||||
|
className='form-control'
|
||||||
|
placeholder=''
|
||||||
|
maxLength='128'
|
||||||
|
/>
|
||||||
<div className='color--light form__hint'>Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</div>
|
<div className='color--light form__hint'>Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,14 +118,37 @@ module.exports = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='form-group'>
|
<div className='form-group'>
|
||||||
<button type='submit' className='btn btn-primary margin--extra' id='finish-button' data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'} onClick={this.submitNext}>Finish</button>
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='btn btn-primary margin--extra'
|
||||||
|
id='finish-button'
|
||||||
|
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'}
|
||||||
|
onClick={this.submitNext}
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p>By proceeding to create your account and use {config.SiteName}, you agree to our <a href={config.TermsLink}>Terms of Service</a> and <a href={config.PrivacyLink}>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
|
<p>By proceeding to create your account and use {config.SiteName}, you agree to our <a href={config.TermsLink}>Terms of Service</a> and <a href={config.PrivacyLink}>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
|
||||||
<div className='margin--extra'>
|
<div className='margin--extra'>
|
||||||
<a href='#' onClick={this.submitBack}>Back to previous step</a>
|
<a
|
||||||
|
href='#'
|
||||||
|
onClick={this.submitBack}
|
||||||
|
>
|
||||||
|
Back to previous step
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
TeamSignupPasswordPage.defaultProps = {
|
||||||
|
state: {},
|
||||||
|
hash: ''
|
||||||
|
};
|
||||||
|
TeamSignupPasswordPage.propTypes = {
|
||||||
|
state: React.PropTypes.object,
|
||||||
|
hash: React.PropTypes.string,
|
||||||
|
updateParent: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,20 +1,9 @@
|
|||||||
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
var Utils = require('../utils/utils.jsx');
|
||||||
var utils = require('../utils/utils.jsx');
|
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
|
|
||||||
function getStateFromStores(userId) {
|
|
||||||
var profile = UserStore.getProfile(userId);
|
|
||||||
|
|
||||||
if (profile == null) {
|
|
||||||
return { profile: { id: "0", username: "..."} };
|
|
||||||
} else {
|
|
||||||
return { profile: profile };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = 0;
|
var id = 0;
|
||||||
|
|
||||||
function nextId() {
|
function nextId() {
|
||||||
@@ -22,49 +11,76 @@ function nextId() {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default class UserProfile extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
module.exports = React.createClass({
|
this.uniqueId = nextId();
|
||||||
uniqueId: null,
|
|
||||||
componentDidMount: function() {
|
this.state = this.getStateFromStores(this.props.userId);
|
||||||
UserStore.addChangeListener(this._onChange);
|
}
|
||||||
$("#profile_" + this.uniqueId).popover({placement : 'right', container: 'body', trigger: 'hover', html: true, delay: { "show": 200, "hide": 100 }});
|
getStateFromStores(userId) {
|
||||||
$('body').tooltip( {selector: '[data-toggle=tooltip]', trigger: 'hover click'} );
|
var profile = UserStore.getProfile(userId);
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
if (profile == null) {
|
||||||
UserStore.removeChangeListener(this._onChange);
|
return {profile: {id: '0', username: '...'}};
|
||||||
},
|
}
|
||||||
_onChange: function(id) {
|
|
||||||
if (id == this.props.userId) {
|
return {profile: profile};
|
||||||
var newState = getStateFromStores(this.props.userId);
|
}
|
||||||
if (!utils.areStatesEqual(newState, this.state)) {
|
componentDidMount() {
|
||||||
|
UserStore.addChangeListener(this.onChange);
|
||||||
|
$('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'hover', html: true, delay: {show: 200, hide: 100}});
|
||||||
|
$('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
UserStore.removeChangeListener(this.onChange);
|
||||||
|
}
|
||||||
|
onChange(userId) {
|
||||||
|
if (userId === this.props.userId) {
|
||||||
|
var newState = this.getStateFromStores(this.props.userId);
|
||||||
|
if (!Utils.areStatesEqual(newState, this.state)) {
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.userId != nextProps.userId) {
|
if (this.props.userId !== nextProps.userId) {
|
||||||
this.setState(getStateFromStores(nextProps.userId));
|
this.setState(this.getStateFromStores(nextProps.userId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
var name = this.state.profile.username;
|
||||||
|
if (this.props.overwriteName) {
|
||||||
|
name = this.props.overwriteName;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
this.uniqueId = nextId();
|
|
||||||
return getStateFromStores(this.props.userId);
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username;
|
|
||||||
|
|
||||||
|
var dataContent = '<img class="user-popover__image" src="/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '" height="128" width="128" />';
|
||||||
var data_content = "<img class='user-popover__image' src='/api/v1/users/" + this.state.profile.id + "/image?time=" + this.state.profile.update_at + "' height='128' width='128' />";
|
|
||||||
if (!config.ShowEmail) {
|
if (!config.ShowEmail) {
|
||||||
data_content += "<div class='text-nowrap'>Email not shared</div>";
|
dataContent += '<div class="text-nowrap">Email not shared</div>';
|
||||||
} else {
|
} else {
|
||||||
data_content += "<div data-toggle='tooltip' title= '" + this.state.profile.email + "'><a href='mailto:" + this.state.profile.email + "' class='text-nowrap text-lowercase user-popover__email'>" + this.state.profile.email + "</a></div>";
|
dataContent += '<div data-toggle="tooltip" title="' + this.state.profile.email + '"><a href="mailto:' + this.state.profile.email + '" class="text-nowrap text-lowercase user-popover__email">' + this.state.profile.email + '</a></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="user-popover" id={"profile_" + this.uniqueId} data-toggle="popover" data-content={data_content} data-original-title={this.state.profile.username} >
|
<div
|
||||||
{ name }
|
className='user-popover'
|
||||||
|
id={'profile_' + this.uniqueId}
|
||||||
|
data-toggle='popover'
|
||||||
|
data-content={dataContent}
|
||||||
|
data-original-title={this.state.profile.username}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
UserProfile.defaultProps = {
|
||||||
|
userId: '',
|
||||||
|
overwriteName: ''
|
||||||
|
};
|
||||||
|
UserProfile.propTypes = {
|
||||||
|
userId: React.PropTypes.string,
|
||||||
|
overwriteName: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,100 +4,133 @@
|
|||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
var SettingItemMin = require('./setting_item_min.jsx');
|
var SettingItemMin = require('./setting_item_min.jsx');
|
||||||
var SettingItemMax = require('./setting_item_max.jsx');
|
var SettingItemMax = require('./setting_item_max.jsx');
|
||||||
var client = require('../utils/client.jsx');
|
var Client = require('../utils/client.jsx');
|
||||||
var utils = require('../utils/utils.jsx');
|
var Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class UserSettingsAppearance extends React.Component {
|
||||||
submitTheme: function(e) {
|
constructor(props) {
|
||||||
e.preventDefault();
|
super(props);
|
||||||
var user = UserStore.getCurrentUser();
|
|
||||||
if (!user.props) user.props = {};
|
|
||||||
user.props.theme = this.state.theme;
|
|
||||||
|
|
||||||
client.updateUser(user,
|
this.submitTheme = this.submitTheme.bind(this);
|
||||||
function(data) {
|
this.updateTheme = this.updateTheme.bind(this);
|
||||||
this.props.updateSection("");
|
this.handleClose = this.handleClose.bind(this);
|
||||||
window.location.reload();
|
|
||||||
}.bind(this),
|
this.state = this.getStateFromStores();
|
||||||
function(err) {
|
}
|
||||||
state = this.getInitialState();
|
getStateFromStores() {
|
||||||
state.server_error = err;
|
|
||||||
this.setState(state);
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
updateTheme: function(e) {
|
|
||||||
var hex = utils.rgb2hex(e.target.style.backgroundColor);
|
|
||||||
this.setState({ theme: hex.toLowerCase() });
|
|
||||||
},
|
|
||||||
handleClose: function() {
|
|
||||||
this.setState({server_error: null});
|
|
||||||
this.props.updateTab('general');
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
|
||||||
if (this.props.activeSection === "theme") {
|
|
||||||
$(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
|
|
||||||
}
|
|
||||||
$('#user_settings').on('hidden.bs.modal', this.handleClose);
|
|
||||||
},
|
|
||||||
componentDidUpdate: function() {
|
|
||||||
if (this.props.activeSection === "theme") {
|
|
||||||
$('.color-btn').removeClass('active-border');
|
|
||||||
$(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
$('#user_settings').off('hidden.bs.modal', this.handleClose);
|
|
||||||
this.props.updateSection('');
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
var user = UserStore.getCurrentUser();
|
var user = UserStore.getCurrentUser();
|
||||||
var theme = config.ThemeColors != null ? config.ThemeColors[0] : "#2389d7";
|
var theme = '#2389d7';
|
||||||
|
if (config.ThemeColors != null) {
|
||||||
|
theme = config.ThemeColors[0];
|
||||||
|
}
|
||||||
if (user.props && user.props.theme) {
|
if (user.props && user.props.theme) {
|
||||||
theme = user.props.theme;
|
theme = user.props.theme;
|
||||||
}
|
}
|
||||||
return { theme: theme.toLowerCase() };
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var server_error = this.state.server_error ? this.state.server_error : null;
|
|
||||||
|
|
||||||
|
return {theme: theme.toLowerCase()};
|
||||||
|
}
|
||||||
|
submitTheme(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var user = UserStore.getCurrentUser();
|
||||||
|
if (!user.props) {
|
||||||
|
user.props = {};
|
||||||
|
}
|
||||||
|
user.props.theme = this.state.theme;
|
||||||
|
|
||||||
|
Client.updateUser(user,
|
||||||
|
function success() {
|
||||||
|
this.props.updateSection('');
|
||||||
|
window.location.reload();
|
||||||
|
}.bind(this),
|
||||||
|
function fail(err) {
|
||||||
|
var state = this.getStateFromStores();
|
||||||
|
state.serverError = err;
|
||||||
|
this.setState(state);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
updateTheme(e) {
|
||||||
|
var hex = Utils.rgb2hex(e.target.style.backgroundColor);
|
||||||
|
this.setState({theme: hex.toLowerCase()});
|
||||||
|
}
|
||||||
|
handleClose() {
|
||||||
|
this.setState({serverError: null});
|
||||||
|
this.props.updateTab('general');
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.activeSection === 'theme') {
|
||||||
|
$(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
|
||||||
|
}
|
||||||
|
$('#user_settings').on('hidden.bs.modal', this.handleClose);
|
||||||
|
}
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (this.props.activeSection === 'theme') {
|
||||||
|
$('.color-btn').removeClass('active-border');
|
||||||
|
$(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
$('#user_settings').off('hidden.bs.modal', this.handleClose);
|
||||||
|
this.props.updateSection('');
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
var serverError;
|
||||||
|
if (this.state.serverError) {
|
||||||
|
serverError = this.state.serverError;
|
||||||
|
}
|
||||||
|
|
||||||
var themeSection;
|
var themeSection;
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (config.ThemeColors != null) {
|
if (config.ThemeColors != null) {
|
||||||
if (this.props.activeSection === 'theme') {
|
if (this.props.activeSection === 'theme') {
|
||||||
var theme_buttons = [];
|
var themeButtons = [];
|
||||||
|
|
||||||
for (var i = 0; i < config.ThemeColors.length; i++) {
|
for (var i = 0; i < config.ThemeColors.length; i++) {
|
||||||
theme_buttons.push(<button ref={config.ThemeColors[i]} type="button" className="btn btn-lg color-btn" style={{backgroundColor: config.ThemeColors[i]}} onClick={this.updateTheme} />);
|
themeButtons.push(
|
||||||
|
<button
|
||||||
|
ref={config.ThemeColors[i]}
|
||||||
|
type='button'
|
||||||
|
className='btn btn-lg color-btn'
|
||||||
|
style={{backgroundColor: config.ThemeColors[i]}}
|
||||||
|
onClick={this.updateTheme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputs = [];
|
var inputs = [];
|
||||||
|
|
||||||
inputs.push(
|
inputs.push(
|
||||||
<li className="setting-list-item">
|
<li className='setting-list-item'>
|
||||||
<div className="btn-group" data-toggle="buttons-radio">
|
<div
|
||||||
{ theme_buttons }
|
className='btn-group'
|
||||||
|
data-toggle='buttons-radio'
|
||||||
|
>
|
||||||
|
{themeButtons}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
themeSection = (
|
themeSection = (
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
title="Theme Color"
|
title='Theme Color'
|
||||||
inputs={inputs}
|
inputs={inputs}
|
||||||
submit={this.submitTheme}
|
submit={this.submitTheme}
|
||||||
server_error={server_error}
|
serverError={serverError}
|
||||||
updateSection={function(e){self.props.updateSection("");e.preventDefault;}}
|
updateSection={function updateSection(e) {
|
||||||
|
self.props.updateSection('');
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
themeSection = (
|
themeSection = (
|
||||||
<SettingItemMin
|
<SettingItemMin
|
||||||
title="Theme Color"
|
title='Theme Color'
|
||||||
describe={this.state.theme}
|
describe={this.state.theme}
|
||||||
updateSection={function(){self.props.updateSection("theme");}}
|
updateSection={function updateSection() {
|
||||||
|
self.props.updateSection('theme');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -105,17 +138,38 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="modal-header">
|
<div className='modal-header'>
|
||||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
<button
|
||||||
<h4 className="modal-title" ref="title"><i className="modal-back"></i>Appearance Settings</h4>
|
type='button'
|
||||||
|
className='close'
|
||||||
|
data-dismiss='modal'
|
||||||
|
aria-label='Close'
|
||||||
|
>
|
||||||
|
<span aria-hidden='true'>×</span>
|
||||||
|
</button>
|
||||||
|
<h4
|
||||||
|
className='modal-title'
|
||||||
|
ref='title'
|
||||||
|
>
|
||||||
|
<i className='modal-back'></i>Appearance Settings
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className="user-settings">
|
<div className='user-settings'>
|
||||||
<h3 className="tab-header">Appearance Settings</h3>
|
<h3 className='tab-header'>Appearance Settings</h3>
|
||||||
<div className="divider-dark first"/>
|
<div className='divider-dark first'/>
|
||||||
{themeSection}
|
{themeSection}
|
||||||
<div className="divider-dark"/>
|
<div className='divider-dark'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
UserSettingsAppearance.defaultProps = {
|
||||||
|
activeSection: ''
|
||||||
|
};
|
||||||
|
UserSettingsAppearance.propTypes = {
|
||||||
|
activeSection: React.PropTypes.string,
|
||||||
|
updateSection: React.PropTypes.func,
|
||||||
|
updateTab: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,13 +3,23 @@
|
|||||||
|
|
||||||
var SettingItemMin = require('./setting_item_min.jsx');
|
var SettingItemMin = require('./setting_item_min.jsx');
|
||||||
var SettingItemMax = require('./setting_item_max.jsx');
|
var SettingItemMax = require('./setting_item_max.jsx');
|
||||||
var client = require('../utils/client.jsx');
|
var Client = require('../utils/client.jsx');
|
||||||
var AsyncClient = require('../utils/async_client.jsx');
|
var AsyncClient = require('../utils/async_client.jsx');
|
||||||
var Constants = require('../utils/constants.jsx');
|
var Constants = require('../utils/constants.jsx');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class SecurityTab extends React.Component {
|
||||||
displayName: 'SecurityTab',
|
constructor(props) {
|
||||||
submitPassword: function(e) {
|
super(props);
|
||||||
|
|
||||||
|
this.submitPassword = this.submitPassword.bind(this);
|
||||||
|
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
|
||||||
|
this.updateNewPassword = this.updateNewPassword.bind(this);
|
||||||
|
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
|
||||||
|
this.handleClose = this.handleClose.bind(this);
|
||||||
|
|
||||||
|
this.state = {currentPassword: '', newPassword: '', confirmPassword: ''};
|
||||||
|
}
|
||||||
|
submitPassword(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var user = this.props.user;
|
var user = this.props.user;
|
||||||
@@ -37,13 +47,13 @@ module.exports = React.createClass({
|
|||||||
data.current_password = currentPassword;
|
data.current_password = currentPassword;
|
||||||
data.new_password = newPassword;
|
data.new_password = newPassword;
|
||||||
|
|
||||||
client.updatePassword(data,
|
Client.updatePassword(data,
|
||||||
function() {
|
function success() {
|
||||||
this.props.updateSection('');
|
this.props.updateSection('');
|
||||||
AsyncClient.getMe();
|
AsyncClient.getMe();
|
||||||
this.setState({currentPassword: '', newPassword: '', confirmPassword: ''});
|
this.setState({currentPassword: '', newPassword: '', confirmPassword: ''});
|
||||||
}.bind(this),
|
}.bind(this),
|
||||||
function(err) {
|
function fail(err) {
|
||||||
var state = this.getInitialState();
|
var state = this.getInitialState();
|
||||||
if (err.message) {
|
if (err.message) {
|
||||||
state.serverError = err.message;
|
state.serverError = err.message;
|
||||||
@@ -54,47 +64,49 @@ module.exports = React.createClass({
|
|||||||
this.setState(state);
|
this.setState(state);
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
updateCurrentPassword: function(e) {
|
updateCurrentPassword(e) {
|
||||||
this.setState({currentPassword: e.target.value});
|
this.setState({currentPassword: e.target.value});
|
||||||
},
|
}
|
||||||
updateNewPassword: function(e) {
|
updateNewPassword(e) {
|
||||||
this.setState({newPassword: e.target.value});
|
this.setState({newPassword: e.target.value});
|
||||||
},
|
}
|
||||||
updateConfirmPassword: function(e) {
|
updateConfirmPassword(e) {
|
||||||
this.setState({confirmPassword: e.target.value});
|
this.setState({confirmPassword: e.target.value});
|
||||||
},
|
}
|
||||||
handleHistoryOpen: function() {
|
handleHistoryOpen() {
|
||||||
$("#user_settings").modal('hide');
|
$('#user_settings').modal('hide');
|
||||||
},
|
}
|
||||||
handleDevicesOpen: function() {
|
handleDevicesOpen() {
|
||||||
$("#user_settings").modal('hide');
|
$('#user_settings').modal('hide');
|
||||||
},
|
}
|
||||||
handleClose: function() {
|
handleClose() {
|
||||||
$(this.getDOMNode()).find('.form-control').each(function() {
|
$(React.findDOMNode(this)).find('.form-control').each(function resetValue() {
|
||||||
this.value = '';
|
this.value = '';
|
||||||
});
|
});
|
||||||
this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
|
this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
|
||||||
|
|
||||||
this.props.updateTab('general');
|
this.props.updateTab('general');
|
||||||
},
|
}
|
||||||
componentDidMount: function() {
|
componentDidMount() {
|
||||||
$('#user_settings').on('hidden.bs.modal', this.handleClose);
|
$('#user_settings').on('hidden.bs.modal', this.handleClose);
|
||||||
},
|
}
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount() {
|
||||||
$('#user_settings').off('hidden.bs.modal', this.handleClose);
|
$('#user_settings').off('hidden.bs.modal', this.handleClose);
|
||||||
this.props.updateSection('');
|
this.props.updateSection('');
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
render() {
|
||||||
return {currentPassword: '', newPassword: '', confirmPassword: ''};
|
var serverError;
|
||||||
},
|
if (this.state.serverError) {
|
||||||
render: function() {
|
serverError = this.state.serverError;
|
||||||
var serverError = this.state.serverError ? this.state.serverError : null;
|
}
|
||||||
var passwordError = this.state.passwordError ? this.state.passwordError : null;
|
var passwordError;
|
||||||
|
if (this.state.passwordError) {
|
||||||
|
passwordError = this.state.passwordError;
|
||||||
|
}
|
||||||
|
|
||||||
var updateSectionStatus;
|
var updateSectionStatus;
|
||||||
var passwordSection;
|
var passwordSection;
|
||||||
var self = this;
|
|
||||||
if (this.props.activeSection === 'password') {
|
if (this.props.activeSection === 'password') {
|
||||||
var inputs = [];
|
var inputs = [];
|
||||||
var submit = null;
|
var submit = null;
|
||||||
@@ -104,7 +116,12 @@ module.exports = React.createClass({
|
|||||||
<div className='form-group'>
|
<div className='form-group'>
|
||||||
<label className='col-sm-5 control-label'>Current Password</label>
|
<label className='col-sm-5 control-label'>Current Password</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input className='form-control' type='password' onChange={this.updateCurrentPassword} value={this.state.currentPassword}/>
|
<input
|
||||||
|
className='form-control'
|
||||||
|
type='password'
|
||||||
|
onChange={this.updateCurrentPassword}
|
||||||
|
value={this.state.currentPassword}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -112,7 +129,12 @@ module.exports = React.createClass({
|
|||||||
<div className='form-group'>
|
<div className='form-group'>
|
||||||
<label className='col-sm-5 control-label'>New Password</label>
|
<label className='col-sm-5 control-label'>New Password</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input className='form-control' type='password' onChange={this.updateNewPassword} value={this.state.newPassword}/>
|
<input
|
||||||
|
className='form-control'
|
||||||
|
type='password'
|
||||||
|
onChange={this.updateNewPassword}
|
||||||
|
value={this.state.newPassword}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -120,7 +142,12 @@ module.exports = React.createClass({
|
|||||||
<div className='form-group'>
|
<div className='form-group'>
|
||||||
<label className='col-sm-5 control-label'>Retype New Password</label>
|
<label className='col-sm-5 control-label'>Retype New Password</label>
|
||||||
<div className='col-sm-7'>
|
<div className='col-sm-7'>
|
||||||
<input className='form-control' type='password' onChange={this.updateConfirmPassword} value={this.state.confirmPassword}/>
|
<input
|
||||||
|
className='form-control'
|
||||||
|
type='password'
|
||||||
|
onChange={this.updateConfirmPassword}
|
||||||
|
value={this.state.confirmPassword}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -134,11 +161,11 @@ module.exports = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSectionStatus = function(e) {
|
updateSectionStatus = function resetSection(e) {
|
||||||
self.props.updateSection('');
|
this.props.updateSection('');
|
||||||
self.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
|
this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
}.bind(this);
|
||||||
|
|
||||||
passwordSection = (
|
passwordSection = (
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
@@ -154,17 +181,27 @@ module.exports = React.createClass({
|
|||||||
var describe;
|
var describe;
|
||||||
if (this.props.user.auth_service === '') {
|
if (this.props.user.auth_service === '') {
|
||||||
var d = new Date(this.props.user.last_password_update);
|
var d = new Date(this.props.user.last_password_update);
|
||||||
var hour = d.getHours() % 12 ? String(d.getHours() % 12) : '12';
|
var hour = '12';
|
||||||
var min = d.getMinutes() < 10 ? '0' + d.getMinutes() : String(d.getMinutes());
|
if (d.getHours() % 12) {
|
||||||
var timeOfDay = d.getHours() >= 12 ? ' pm' : ' am';
|
hour = String(d.getHours() % 12);
|
||||||
|
}
|
||||||
|
var min = String(d.getMinutes());
|
||||||
|
if (d.getMinutes() < 10) {
|
||||||
|
min = '0' + d.getMinutes();
|
||||||
|
}
|
||||||
|
var timeOfDay = ' am';
|
||||||
|
if (d.getHours() >= 12) {
|
||||||
|
timeOfDay = ' pm';
|
||||||
|
}
|
||||||
|
|
||||||
describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
|
describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
|
||||||
} else {
|
} else {
|
||||||
describe = 'Log in done through GitLab';
|
describe = 'Log in done through GitLab';
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSectionStatus = function() {
|
updateSectionStatus = function updateSection() {
|
||||||
self.props.updateSection('password');
|
this.props.updateSection('password');
|
||||||
};
|
}.bind(this);
|
||||||
|
|
||||||
passwordSection = (
|
passwordSection = (
|
||||||
<SettingItemMin
|
<SettingItemMin
|
||||||
@@ -178,8 +215,20 @@ module.exports = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>
|
<button
|
||||||
<h4 className='modal-title' ref='title'><i className='modal-back'></i>Security Settings</h4>
|
type='button'
|
||||||
|
className='close'
|
||||||
|
data-dismiss='modal'
|
||||||
|
aria-label='Close'
|
||||||
|
>
|
||||||
|
<span aria-hidden='true'>×</span>
|
||||||
|
</button>
|
||||||
|
<h4
|
||||||
|
className='modal-title'
|
||||||
|
ref='title'
|
||||||
|
>
|
||||||
|
<i className='modal-back'></i>Security Settings
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className='user-settings'>
|
<div className='user-settings'>
|
||||||
<h3 className='tab-header'>Security Settings</h3>
|
<h3 className='tab-header'>Security Settings</h3>
|
||||||
@@ -187,11 +236,38 @@ module.exports = React.createClass({
|
|||||||
{passwordSection}
|
{passwordSection}
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
<br></br>
|
<br></br>
|
||||||
<a data-toggle='modal' className='security-links theme' data-target='#access-history' href='#' onClick={this.handleHistoryOpen}><i className='fa fa-clock-o'></i>View Access History</a>
|
<a
|
||||||
|
data-toggle='modal'
|
||||||
|
className='security-links theme'
|
||||||
|
data-target='#access-history'
|
||||||
|
href='#'
|
||||||
|
onClick={this.handleHistoryOpen}
|
||||||
|
>
|
||||||
|
<i className='fa fa-clock-o'></i>View Access History
|
||||||
|
</a>
|
||||||
<b> </b>
|
<b> </b>
|
||||||
<a data-toggle='modal' className='security-links theme' data-target='#activity-log' href='#' onClick={this.handleDevicesOpen}><i className='fa fa-globe'></i>View and Logout of Active Sessions</a>
|
<a
|
||||||
|
data-toggle='modal'
|
||||||
|
className='security-links theme'
|
||||||
|
data-target='#activity-log'
|
||||||
|
href='#'
|
||||||
|
onClick={this.handleDevicesOpen}
|
||||||
|
>
|
||||||
|
<i className='fa fa-globe'></i>View and Logout of Active Sessions
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
SecurityTab.defaultProps = {
|
||||||
|
user: {},
|
||||||
|
activeSection: ''
|
||||||
|
};
|
||||||
|
SecurityTab.propTypes = {
|
||||||
|
user: React.PropTypes.object,
|
||||||
|
activeSection: React.PropTypes.string,
|
||||||
|
updateSection: React.PropTypes.func,
|
||||||
|
updateTab: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,35 +2,46 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var Client = require('../utils/client.jsx');
|
var Client = require('../utils/client.jsx');
|
||||||
var utils = require('../utils/utils.jsx');
|
var Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class ViewImageModal extends React.Component {
|
||||||
displayName: 'ViewImageModal',
|
constructor(props) {
|
||||||
propTypes: {
|
super(props);
|
||||||
filenames: React.PropTypes.array,
|
|
||||||
modalId: React.PropTypes.string,
|
this.canSetState = false;
|
||||||
channelId: React.PropTypes.string,
|
|
||||||
userId: React.PropTypes.string,
|
this.loadImage = this.loadImage.bind(this);
|
||||||
startId: React.PropTypes.number
|
this.handleNext = this.handleNext.bind(this);
|
||||||
},
|
this.handlePrev = this.handlePrev.bind(this);
|
||||||
canSetState: false,
|
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||||
handleNext: function() {
|
this.getPublicLink = this.getPublicLink.bind(this);
|
||||||
|
this.getPreviewImagePath = this.getPreviewImagePath.bind(this);
|
||||||
|
|
||||||
|
var loaded = [];
|
||||||
|
var progress = [];
|
||||||
|
for (var i = 0; i < this.props.filenames.length; i++) {
|
||||||
|
loaded.push(false);
|
||||||
|
progress.push(0);
|
||||||
|
}
|
||||||
|
this.state = {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}};
|
||||||
|
}
|
||||||
|
handleNext() {
|
||||||
var id = this.state.imgId + 1;
|
var id = this.state.imgId + 1;
|
||||||
if (id > this.props.filenames.length - 1) {
|
if (id > this.props.filenames.length - 1) {
|
||||||
id = 0;
|
id = 0;
|
||||||
}
|
}
|
||||||
this.setState({imgId: id});
|
this.setState({imgId: id});
|
||||||
this.loadImage(id);
|
this.loadImage(id);
|
||||||
},
|
}
|
||||||
handlePrev: function() {
|
handlePrev() {
|
||||||
var id = this.state.imgId - 1;
|
var id = this.state.imgId - 1;
|
||||||
if (id < 0) {
|
if (id < 0) {
|
||||||
id = this.props.filenames.length - 1;
|
id = this.props.filenames.length - 1;
|
||||||
}
|
}
|
||||||
this.setState({imgId: id});
|
this.setState({imgId: id});
|
||||||
this.loadImage(id);
|
this.loadImage(id);
|
||||||
},
|
}
|
||||||
handleKeyPress: function handleKeyPress(e) {
|
handleKeyPress(e) {
|
||||||
if (!e) {
|
if (!e) {
|
||||||
return;
|
return;
|
||||||
} else if (e.keyCode === 39) {
|
} else if (e.keyCode === 39) {
|
||||||
@@ -38,11 +49,11 @@ module.exports = React.createClass({
|
|||||||
} else if (e.keyCode === 37) {
|
} else if (e.keyCode === 37) {
|
||||||
this.handlePrev();
|
this.handlePrev();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.setState({imgId: nextProps.startId});
|
this.setState({imgId: nextProps.startId});
|
||||||
},
|
}
|
||||||
loadImage: function(id) {
|
loadImage(id) {
|
||||||
var imgHeight = $(window).height() - 100;
|
var imgHeight = $(window).height() - 100;
|
||||||
if (this.state.loaded[id] || this.state.images[id]) {
|
if (this.state.loaded[id] || this.state.images[id]) {
|
||||||
$('.modal .modal-image .image-wrapper img').css('max-height', imgHeight);
|
$('.modal .modal-image .image-wrapper img').css('max-height', imgHeight);
|
||||||
@@ -51,26 +62,25 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
var filename = this.props.filenames[id];
|
var filename = this.props.filenames[id];
|
||||||
|
|
||||||
var fileInfo = utils.splitFileLocation(filename);
|
var fileInfo = Utils.splitFileLocation(filename);
|
||||||
var fileType = utils.getFileType(fileInfo.ext);
|
var fileType = Utils.getFileType(fileInfo.ext);
|
||||||
|
|
||||||
if (fileType === 'image') {
|
if (fileType === 'image') {
|
||||||
var self = this;
|
|
||||||
var img = new Image();
|
var img = new Image();
|
||||||
img.load(this.getPreviewImagePath(filename),
|
img.load(this.getPreviewImagePath(filename),
|
||||||
function() {
|
function load() {
|
||||||
var progress = self.state.progress;
|
var progress = this.state.progress;
|
||||||
progress[id] = img.completedPercentage;
|
progress[id] = img.completedPercentage;
|
||||||
self.setState({progress: progress});
|
this.setState({progress: progress});
|
||||||
});
|
}.bind(this));
|
||||||
img.onload = function onload(imgid) {
|
img.onload = (function onload(imgid) {
|
||||||
return function onloadReturn() {
|
return function onloadReturn() {
|
||||||
var loaded = self.state.loaded;
|
var loaded = this.state.loaded;
|
||||||
loaded[imgid] = true;
|
loaded[imgid] = true;
|
||||||
self.setState({loaded: loaded});
|
this.setState({loaded: loaded});
|
||||||
$(self.refs.image.getDOMNode()).css('max-height', imgHeight);
|
$(React.findDOMNode(this.refs.image)).css('max-height', imgHeight);
|
||||||
};
|
}.bind(this);
|
||||||
}(id);
|
}.bind(this)(id));
|
||||||
var images = this.state.images;
|
var images = this.state.images;
|
||||||
images[id] = img;
|
images[id] = img;
|
||||||
this.setState({images: images});
|
this.setState({images: images});
|
||||||
@@ -80,52 +90,51 @@ module.exports = React.createClass({
|
|||||||
loaded[id] = true;
|
loaded[id] = true;
|
||||||
this.setState({loaded: loaded});
|
this.setState({loaded: loaded});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate() {
|
||||||
if (this.state.loaded[this.state.imgId]) {
|
if (this.state.loaded[this.state.imgId]) {
|
||||||
if (this.refs.imageWrap) {
|
if (this.refs.imageWrap) {
|
||||||
$(this.refs.imageWrap.getDOMNode()).removeClass('default');
|
$(React.findDOMNode(this.refs.imageWrap)).removeClass('default');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
componentDidMount: function() {
|
componentDidMount() {
|
||||||
var self = this;
|
|
||||||
$('#' + this.props.modalId).on('shown.bs.modal', function onModalShow() {
|
$('#' + this.props.modalId).on('shown.bs.modal', function onModalShow() {
|
||||||
self.setState({viewed: true});
|
this.setState({viewed: true});
|
||||||
self.loadImage(self.state.imgId);
|
this.loadImage(this.state.imgId);
|
||||||
});
|
}.bind(this));
|
||||||
|
|
||||||
$(this.refs.modal.getDOMNode()).click(function onModalClick(e) {
|
$(React.findDOMNode(this.refs.modal)).click(function onModalClick(e) {
|
||||||
if (e.target === this || e.target === self.refs.imageBody.getDOMNode()) {
|
if (e.target === this || e.target === React.findDOMNode(this.refs.imageBody)) {
|
||||||
$('.image_modal').modal('hide');
|
$('.image_modal').modal('hide');
|
||||||
}
|
}
|
||||||
});
|
}.bind(this));
|
||||||
|
|
||||||
$(this.refs.imageWrap.getDOMNode()).hover(
|
$(React.findDOMNode(this.refs.imageWrap)).hover(
|
||||||
function onModalHover() {
|
function onModalHover() {
|
||||||
$(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
|
$(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
|
||||||
}, function offModalHover() {
|
}.bind(this), function offModalHover() {
|
||||||
$(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
|
$(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
|
||||||
}
|
}.bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.refs.previewArrowLeft) {
|
if (this.refs.previewArrowLeft) {
|
||||||
$(this.refs.previewArrowLeft.getDOMNode()).hover(
|
$(React.findDOMNode(this.refs.previewArrowLeft)).hover(
|
||||||
function onModalHover() {
|
function onModalHover() {
|
||||||
$(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
|
$(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
|
||||||
}, function offModalHover() {
|
}.bind(this), function offModalHover() {
|
||||||
$(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
|
$(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
|
||||||
}
|
}.bind(this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.refs.previewArrowRight) {
|
if (this.refs.previewArrowRight) {
|
||||||
$(this.refs.previewArrowRight.getDOMNode()).hover(
|
$(React.findDOMNode(this.refs.previewArrowRight)).hover(
|
||||||
function onModalHover() {
|
function onModalHover() {
|
||||||
$(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
|
$(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
|
||||||
}, function offModalHover() {
|
}.bind(this), function offModalHover() {
|
||||||
$(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
|
$(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
|
||||||
}
|
}.bind(this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,90 +142,93 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
// keep track of whether or not this component is mounted so we can safely set the state asynchronously
|
// keep track of whether or not this component is mounted so we can safely set the state asynchronously
|
||||||
this.canSetState = true;
|
this.canSetState = true;
|
||||||
},
|
}
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount() {
|
||||||
this.canSetState = false;
|
this.canSetState = false;
|
||||||
$(window).off('keyup', this.handleKeyPress);
|
$(window).off('keyup', this.handleKeyPress);
|
||||||
},
|
}
|
||||||
getPublicLink: function() {
|
getPublicLink() {
|
||||||
var data = {};
|
var data = {};
|
||||||
data.channel_id = this.props.channelId;
|
data.channel_id = this.props.channelId;
|
||||||
data.user_id = this.props.userId;
|
data.user_id = this.props.userId;
|
||||||
data.filename = this.props.filenames[this.state.imgId];
|
data.filename = this.props.filenames[this.state.imgId];
|
||||||
Client.getPublicLink(data,
|
Client.getPublicLink(data,
|
||||||
function sucess(serverData) {
|
function sucess(serverData) {
|
||||||
if (utils.isMobile()) {
|
if (Utils.isMobile()) {
|
||||||
window.location.href = serverData.public_link;
|
window.location.href = serverData.public_link;
|
||||||
} else {
|
} else {
|
||||||
window.open(serverData.public_link);
|
window.open(serverData.public_link);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function error() {
|
function error() {}
|
||||||
}
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
getPreviewImagePath: function(filename) {
|
getPreviewImagePath(filename) {
|
||||||
// Returns the path to a preview image that can be used to represent a file.
|
// Returns the path to a preview image that can be used to represent a file.
|
||||||
var fileInfo = utils.splitFileLocation(filename);
|
var fileInfo = Utils.splitFileLocation(filename);
|
||||||
var fileType = utils.getFileType(fileInfo.ext);
|
var fileType = Utils.getFileType(fileInfo.ext);
|
||||||
|
|
||||||
if (fileType === 'image') {
|
if (fileType === 'image') {
|
||||||
// This is a temporary patch to fix issue with old files using absolute paths
|
// This is a temporary patch to fix issue with old files using absolute paths
|
||||||
if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) {
|
if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) {
|
||||||
fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1];
|
fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1];
|
||||||
}
|
}
|
||||||
fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
|
fileInfo.path = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
|
||||||
|
|
||||||
return fileInfo.path + '_preview.jpg';
|
return fileInfo.path + '_preview.jpg';
|
||||||
}
|
}
|
||||||
|
|
||||||
// only images have proper previews, so just use a placeholder icon for non-images
|
// only images have proper previews, so just use a placeholder icon for non-images
|
||||||
return utils.getPreviewImagePathForFileType(fileType);
|
return Utils.getPreviewImagePathForFileType(fileType);
|
||||||
},
|
}
|
||||||
getInitialState: function() {
|
render() {
|
||||||
var loaded = [];
|
|
||||||
var progress = [];
|
|
||||||
for (var i = 0; i < this.props.filenames.length; i++) {
|
|
||||||
loaded.push(false);
|
|
||||||
progress.push(0);
|
|
||||||
}
|
|
||||||
return {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}};
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) {
|
if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) {
|
||||||
return <div/>;
|
return <div/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = this.props.filenames[this.state.imgId];
|
var filename = this.props.filenames[this.state.imgId];
|
||||||
var fileUrl = utils.getFileUrl(filename);
|
var fileUrl = Utils.getFileUrl(filename);
|
||||||
|
|
||||||
var name = decodeURIComponent(utils.getFileName(filename));
|
var name = decodeURIComponent(Utils.getFileName(filename));
|
||||||
|
|
||||||
var content;
|
var content;
|
||||||
var bgClass = '';
|
var bgClass = '';
|
||||||
if (this.state.loaded[this.state.imgId]) {
|
if (this.state.loaded[this.state.imgId]) {
|
||||||
var fileInfo = utils.splitFileLocation(filename);
|
var fileInfo = Utils.splitFileLocation(filename);
|
||||||
var fileType = utils.getFileType(fileInfo.ext);
|
var fileType = Utils.getFileType(fileInfo.ext);
|
||||||
|
|
||||||
if (fileType === 'image') {
|
if (fileType === 'image') {
|
||||||
// image files just show a preview of the file
|
// image files just show a preview of the file
|
||||||
content = (
|
content = (
|
||||||
<a href={fileUrl} target='_blank'>
|
<a
|
||||||
<img ref='image' src={this.getPreviewImagePath(filename)}/>
|
href={fileUrl}
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
ref='image'
|
||||||
|
src={this.getPreviewImagePath(filename)}
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// non-image files include a section providing details about the file
|
// non-image files include a section providing details about the file
|
||||||
var infoString = 'File type ' + fileInfo.ext.toUpperCase();
|
var infoString = 'File type ' + fileInfo.ext.toUpperCase();
|
||||||
if (this.state.fileSizes[filename] && this.state.fileSizes[filename] >= 0) {
|
if (this.state.fileSizes[filename] && this.state.fileSizes[filename] >= 0) {
|
||||||
infoString += ', Size ' + utils.fileSizeToString(this.state.fileSizes[filename]);
|
infoString += ', Size ' + Utils.fileSizeToString(this.state.fileSizes[filename]);
|
||||||
}
|
}
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<div className='file-details__container'>
|
<div className='file-details__container'>
|
||||||
<a className={'file-details__preview'} href={fileUrl} target='_blank'>
|
<a
|
||||||
|
className={'file-details__preview'}
|
||||||
|
href={fileUrl}
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
<span className='file-details__preview-helper' />
|
<span className='file-details__preview-helper' />
|
||||||
<img ref='image' src={this.getPreviewImagePath(filename)} />
|
<img
|
||||||
|
ref='image'
|
||||||
|
src={this.getPreviewImagePath(filename)}
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<div className='file-details'>
|
<div className='file-details'>
|
||||||
<div className='file-details__name'>{name}</div>
|
<div className='file-details__name'>{name}</div>
|
||||||
@@ -228,19 +240,16 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
// asynchronously request the actual size of this file
|
// asynchronously request the actual size of this file
|
||||||
if (!(filename in this.state.fileSizes)) {
|
if (!(filename in this.state.fileSizes)) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
Client.getFileInfo(
|
Client.getFileInfo(
|
||||||
filename,
|
filename,
|
||||||
function(data) {
|
function success(data) {
|
||||||
if (self.canSetState) {
|
if (this.canSetState) {
|
||||||
var fileSizes = self.state.fileSizes;
|
var fileSizes = this.state.fileSizes;
|
||||||
fileSizes[filename] = parseInt(data["size"], 10);
|
fileSizes[filename] = parseInt(data.size, 10);
|
||||||
self.setState(fileSizes);
|
this.setState(fileSizes);
|
||||||
}
|
}
|
||||||
},
|
}.bind(this),
|
||||||
function(err) {
|
function fail() {}
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,14 +259,22 @@ module.exports = React.createClass({
|
|||||||
if (percentage) {
|
if (percentage) {
|
||||||
content = (
|
content = (
|
||||||
<div>
|
<div>
|
||||||
<img className='loader-image' src='/static/images/load.gif' />
|
<img
|
||||||
<span className='loader-percent' >{'Previewing ' + percentage + '%'}</span>
|
className='loader-image'
|
||||||
|
src='/static/images/load.gif'
|
||||||
|
/>
|
||||||
|
<span className='loader-percent'>
|
||||||
|
{'Previewing ' + percentage + '%'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<div>
|
<div>
|
||||||
<img className='loader-image' src='/static/images/load.gif' />
|
<img
|
||||||
|
className='loader-image'
|
||||||
|
src='/static/images/load.gif'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -268,7 +285,14 @@ module.exports = React.createClass({
|
|||||||
if (config.AllowPublicLink) {
|
if (config.AllowPublicLink) {
|
||||||
publicLink = (
|
publicLink = (
|
||||||
<div>
|
<div>
|
||||||
<a href='#' className='public-link text' data-title='Public Image' onClick={this.getPublicLink}>Get Public Link</a>
|
<a
|
||||||
|
href='#'
|
||||||
|
className='public-link text'
|
||||||
|
data-title='Public Image'
|
||||||
|
onClick={this.getPublicLink}
|
||||||
|
>
|
||||||
|
Get Public Link
|
||||||
|
</a>
|
||||||
<span className='text'> | </span>
|
<span className='text'> | </span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -299,18 +323,43 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal fade image_modal' ref='modal' id={this.props.modalId} tabIndex='-1' role='dialog' aria-hidden='true'>
|
<div
|
||||||
|
className='modal fade image_modal'
|
||||||
|
ref='modal'
|
||||||
|
id={this.props.modalId}
|
||||||
|
tabIndex='-1'
|
||||||
|
role='dialog'
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
<div className='modal-dialog modal-image'>
|
<div className='modal-dialog modal-image'>
|
||||||
<div className='modal-content image-content'>
|
<div className='modal-content image-content'>
|
||||||
<div ref='imageBody' className='modal-body image-body'>
|
<div
|
||||||
<div ref='imageWrap' className={'image-wrapper default ' + bgClass}>
|
ref='imageBody'
|
||||||
<div className='modal-close' data-dismiss='modal'></div>
|
className='modal-body image-body'
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref='imageWrap'
|
||||||
|
className={'image-wrapper default ' + bgClass}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='modal-close'
|
||||||
|
data-dismiss='modal'
|
||||||
|
/>
|
||||||
{content}
|
{content}
|
||||||
<div ref='imageFooter' className='modal-button-bar'>
|
<div
|
||||||
|
ref='imageFooter'
|
||||||
|
className='modal-button-bar'
|
||||||
|
>
|
||||||
<span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
|
<span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
|
||||||
<div className='image-links'>
|
<div className='image-links'>
|
||||||
{publicLink}
|
{publicLink}
|
||||||
<a href={fileUrl} download={name} className='text'>Download</a>
|
<a
|
||||||
|
href={fileUrl}
|
||||||
|
download={name}
|
||||||
|
className='text'
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -322,4 +371,19 @@ module.exports = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
ViewImageModal.defaultProps = {
|
||||||
|
filenames: [],
|
||||||
|
modalId: '',
|
||||||
|
channelId: '',
|
||||||
|
userId: '',
|
||||||
|
startId: 0
|
||||||
|
};
|
||||||
|
ViewImageModal.propTypes = {
|
||||||
|
filenames: React.PropTypes.array,
|
||||||
|
modalId: React.PropTypes.string,
|
||||||
|
channelId: React.PropTypes.string,
|
||||||
|
userId: React.PropTypes.string,
|
||||||
|
startId: React.PropTypes.number
|
||||||
|
};
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user