diff options
Diffstat (limited to 'web/react')
22 files changed, 567 insertions, 83 deletions
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 006c168ba..ade58a10a 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -201,7 +201,7 @@ module.exports = React.createClass({ </a> <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown"> <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_info" data-channelid={this.state.channel.id} href="#">View Info</a></li> - <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Invite Members</a></li> + <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li> { isAdmin ? <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li> : "" diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 5252f275c..537a41d03 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -18,6 +18,7 @@ module.exports = React.createClass({ AsyncClient.getChannelExtraInfo(true); AsyncClient.findTeams(); AsyncClient.getStatuses(); + AsyncClient.getMyTeam(); /* End of async loads */ diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx new file mode 100644 index 000000000..3be13cf9b --- /dev/null +++ b/web/react/components/confirm_modal.jsx @@ -0,0 +1,31 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +module.exports = React.createClass({ + handleConfirm: function() { + $('#'+this.props.parent_id).attr('data-confirm', 'true'); + $('#'+this.props.parent_id).modal('hide'); + $('#'+this.props.id).modal('hide'); + }, + render: function() { + return ( + <div className="modal fade" id={this.props.id} tabIndex="-1" role="dialog" aria-hidden="true"> + <div className="modal-dialog"> + <div className="modal-content"> + <div className="modal-header"> + <h4 className="modal-title">{this.props.title}</h4> + </div> + <div className="modal-body"> + {this.props.message} + </div> + <div className="modal-footer"> + <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> + <button onClick={this.handleConfirm} type="button" className="btn btn-primary">{this.props.confirm_button}</button> + </div> + </div> + </div> + </div> + ); + } +}); + diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 9bcbad079..cb7aa371c 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -145,7 +145,7 @@ module.exports = React.createClass({ onUserInput={this.handleUserInput} onKeyPress={this.commentMsgKeyPress} messageText={this.state.messageText} - createMessage="Create a comment..." + createMessage="Add a comment..." initialText="" id="reply_textbox" ref="textbox" /> diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 191be9bf8..5a0b6f85f 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -31,9 +31,7 @@ module.exports = React.createClass({ post.message = this.state.messageText; - var repRegex = new RegExp("<br>", "g"); - if (post.message.replace(repRegex, " ").trim().length === 0 - && this.state.previews.length === 0) { + if (post.message.trim().length === 0 && this.state.previews.length === 0) { return; } @@ -250,7 +248,7 @@ module.exports = React.createClass({ onUserInput={this.handleUserInput} onKeyPress={this.postMsgKeyPress} messageText={this.state.messageText} - createMessage="Create a post..." + createMessage="Write a message..." channelId={this.state.channel_id} id="post_textbox" ref="textbox" /> diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index 1d2bbed84..5980664de 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -4,8 +4,37 @@ var utils = require('../utils/utils.jsx'); var Client =require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); +var ConfirmModal = require('./confirm_modal.jsx'); module.exports = React.createClass({ + componentDidMount: function() { + var self = this; + $('#invite_member').on('hide.bs.modal', function(e) { + if ($('#invite_member').attr('data-confirm') === 'true') { + $('#invite_member').attr('data-confirm', 'false'); + return; + } + + var not_empty = false; + for (var i = 0; i < self.state.invite_ids.length; i++) { + var index = self.state.invite_ids[i]; + if (self.refs["email"+index].getDOMNode().value.trim() !== '') { + not_empty = true; + break; + } + } + + if (not_empty) { + $('#confirm_invite_modal').modal('show'); + e.preventDefault(); + } + + }); + + $('#invite_member').on('hidden.bs.modal', function() { + self.clearFields(); + }); + }, handleSubmit: function(e) { var invite_ids = this.state.invite_ids; var count = invite_ids.length; @@ -56,22 +85,8 @@ module.exports = React.createClass({ Client.inviteMembers(data, function() { + $(this.refs.modal.getDOMNode()).attr('data-confirm', 'true'); $(this.refs.modal.getDOMNode()).modal('hide'); - for (var i = 0; i < invite_ids.length; i++) { - var index = invite_ids[i]; - this.refs["email"+index].getDOMNode().value = ""; - if (config.AllowInviteNames) { - this.refs["first_name"+index].getDOMNode().value = ""; - this.refs["last_name"+index].getDOMNode().value = ""; - } - } - this.setState({ - invite_ids: [0], - id_count: 0, - email_errors: {}, - first_name_errors: {}, - last_name_errors: {} - }); }.bind(this), function(err) { this.setState({ server_error: err }); @@ -89,6 +104,26 @@ module.exports = React.createClass({ invite_ids.push(count); this.setState({ invite_ids: invite_ids, id_count: count }); }, + clearFields: function() { + var invite_ids = this.state.invite_ids; + + for (var i = 0; i < invite_ids.length; i++) { + var index = invite_ids[i]; + this.refs["email"+index].getDOMNode().value = ""; + if (config.AllowInviteNames) { + this.refs["first_name"+index].getDOMNode().value = ""; + this.refs["last_name"+index].getDOMNode().value = ""; + } + } + + this.setState({ + invite_ids: [0], + id_count: 0, + email_errors: {}, + first_name_errors: {}, + last_name_errors: {} + }); + }, removeInviteFields: function(index) { var invite_ids = this.state.invite_ids; var i = invite_ids.indexOf(index); @@ -147,29 +182,38 @@ module.exports = React.createClass({ var server_error = this.state.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null; return ( - <div className="modal fade" ref="modal" id="invite_member" tabIndex="-1" 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" data-reactid=".5.0.0.0.0"><span aria-hidden="true" data-reactid=".5.0.0.0.0.0">×</span></button> - <h4 className="modal-title" id="myModalLabel">Invite New Member</h4> - </div> - <div ref="modalBody" className="modal-body"> - <form role="form"> - { invite_sections } - </form> - { server_error } - <button type="button" className="btn btn-default" onClick={this.addInviteFields}>Add another</button> - <br/> - <br/> - <label className='control-label'>People invited automatically join Town Square channel.</label> - </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> - <button onClick={this.handleSubmit} type="button" className="btn btn-primary">Send Invitations</button> - </div> - </div> - </div> + <div> + <div className="modal fade" ref="modal" id="invite_member" tabIndex="-1" 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" data-reactid=".5.0.0.0.0"><span aria-hidden="true" data-reactid=".5.0.0.0.0.0">×</span></button> + <h4 className="modal-title" id="myModalLabel">Invite New Member</h4> + </div> + <div ref="modalBody" className="modal-body"> + <form role="form"> + { invite_sections } + </form> + { server_error } + <button type="button" className="btn btn-default" onClick={this.addInviteFields}>Add another</button> + <br/> + <br/> + <label className='control-label'>People invited automatically join Town Square channel.</label> + </div> + <div className="modal-footer"> + <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <button onClick={this.handleSubmit} type="button" className="btn btn-primary">Send Invitations</button> + </div> + </div> + </div> + </div> + <ConfirmModal + id="confirm_invite_modal" + parent_id="invite_member" + title="Discard Invitations?" + message="You have unsent invitations, are you sure you want to discard them?" + confirm_button="Yes, Discard" + /> </div> ); } else { diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index 3821c2772..35f7d9044 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -301,7 +301,7 @@ module.exports = React.createClass({ <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span> </a> <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown"> - <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Invite Members</a></li> + <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li> { isAdmin ? <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li> : "" diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 55fc32c33..3079917ec 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -85,20 +85,22 @@ module.exports = React.createClass({ var postFiles = []; var images = []; if (filenames) { - for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { + for (var i = 0; i < filenames.length; i++) { var fileInfo = utils.splitFileLocation(filenames[i]); if (Object.keys(fileInfo).length === 0) continue; var type = utils.getFileType(fileInfo.ext); if (type === "image") { - postFiles.push( - <div className="post-image__column" key={filenames[i]}> - <a href="#" onClick={this.handleImageClick} data-img-id={images.length.toString()} data-toggle="modal" data-target={"#" + postImageModalId }><div ref={filenames[i]} className="post__load" style={{backgroundImage: 'url(/static/images/load.gif)'}}></div></a> - </div> - ); + if (i < Constants.MAX_DISPLAY_FILES) { + postFiles.push( + <div className="post-image__column" key={filenames[i]}> + <a href="#" onClick={this.handleImageClick} data-img-id={images.length.toString()} data-toggle="modal" data-target={"#" + postImageModalId }><div ref={filenames[i]} className="post__load" style={{backgroundImage: 'url(/static/images/load.gif)'}}></div></a> + </div> + ); + } images.push(filenames[i]); - } else { + } else if (i < Constants.MAX_DISPLAY_FILES) { postFiles.push( <div className="post-image__column custom-file" key={fileInfo.name}> <a href={fileInfo.path+"."+fileInfo.ext} download={fileInfo.name+"."+fileInfo.ext}> diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx index a1546890f..ae8510cf2 100644 --- a/web/react/components/settings_sidebar.jsx +++ b/web/react/components/settings_sidebar.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var utils = require('../utils/utils.jsx'); + module.exports = React.createClass({ updateTab: function(tab) { this.props.updateTab(tab); @@ -11,16 +13,11 @@ module.exports = React.createClass({ return ( <div className=""> <ul className="nav nav-pills nav-stacked"> - <li className={this.props.activeTab == 'general' ? 'active' : ''}><a href="#" onClick={function(){self.updateTab("general");}}><i className="glyphicon glyphicon-cog"></i>General</a></li> - <li className={this.props.activeTab == 'security' ? 'active' : ''}><a href="#" onClick={function(){self.updateTab("security");}}><i className="glyphicon glyphicon-lock"></i>Security</a></li> - <li className={this.props.activeTab == 'notifications' ? 'active' : ''}><a href="#" onClick={function(){self.updateTab("notifications");}}><i className="glyphicon glyphicon-exclamation-sign"></i>Notifications</a></li> - <li className={this.props.activeTab == 'appearance' ? 'active' : ''}><a href="#" onClick={function(){self.updateTab("appearance");}}><i className="glyphicon glyphicon-wrench"></i>Appearance</a></li> + {this.props.tabs.map(function(tab) { + return <li className={self.props.activeTab == tab.name ? 'active' : ''}><a href="#" onClick={function(){self.updateTab(tab.name);}}><i className={tab.icon}></i>{tab.ui_name}</a></li> + })} </ul> </div> ); - /* Temporarily removing sessions and activity logs - <li className={this.props.activeTab == 'sessions' ? 'active' : ''}><a href="#" onClick={function(){self.updateTab("sessions");}}><i className="glyphicon glyphicon-globe"></i>Sessions</a></li> - <li className={this.props.activeTab == 'activity_log' ? 'active' : ''}><a href="#" onClick={function(){self.updateTab("activity_log");}}><i className="glyphicon glyphicon-time"></i>Activity Log</a></li> - */ } }); diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 5a872b7a0..0b59d2036 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -94,7 +94,8 @@ var NavbarDropdown = React.createClass({ <i className="dropdown__icon"></i> </a> <ul className="dropdown-menu" role="menu"> - <li><a href="#" data-toggle="modal" data-target="#settings_modal">Account Settings</a></li> + <li><a href="#" data-toggle="modal" data-target="#user_settings1">Account Settings</a></li> + { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings">Team Settings</a></li> : "" } { invite_link } { team_link } { manage_link } diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx index d0c139d1a..c523ce554 100644 --- a/web/react/components/sidebar_right_menu.jsx +++ b/web/react/components/sidebar_right_menu.jsx @@ -59,7 +59,7 @@ module.exports = React.createClass({ <div className="nav-pills__container"> <ul className="nav nav-pills nav-stacked"> - <li><a href="#" data-toggle="modal" data-target="#settings_modal"><i className="glyphicon glyphicon-cog"></i>Account Settings</a></li> + <li><a href="#" data-toggle="modal" data-target="#user_settings1"><i className="glyphicon glyphicon-cog"></i>Account Settings</a></li> { invite_link } { team_link } { manage_link } diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx new file mode 100644 index 000000000..0cec30f3e --- /dev/null +++ b/web/react/components/team_settings.jsx @@ -0,0 +1,161 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); +var SettingItemMin = require('./setting_item_min.jsx'); +var SettingItemMax = require('./setting_item_max.jsx'); +var SettingPicture = require('./setting_picture.jsx'); +var utils = require('../utils/utils.jsx'); + +var client = require('../utils/client.jsx'); +var AsyncClient = require('../utils/async_client.jsx'); +var Constants = require('../utils/constants.jsx'); + +var FeatureTab = React.createClass({ + submitValetFeature: function() { + data = {}; + data['allow_valet'] = this.state.allow_valet; + + client.updateValetFeature(data, + function(data) { + this.props.updateSection(""); + AsyncClient.getMyTeam(); + }.bind(this), + function(err) { + state = this.getInitialState(); + state.server_error = err; + this.setState(state); + }.bind(this) + ); + }, + handleValetRadio: function(val) { + this.setState({ allow_valet: val }); + this.refs.wrapper.getDOMNode().focus(); + }, + componentWillReceiveProps: function(newProps) { + var team = newProps.team; + + var allow_valet = "false"; + if (team && team.allow_valet) { + allow_valet = "true"; + } + + this.setState({ allow_valet: allow_valet }); + }, + getInitialState: function() { + var team = this.props.team; + + var allow_valet = "false"; + if (team && team.allow_valet) { + allow_valet = "true"; + } + + return { allow_valet: allow_valet }; + }, + render: function() { + var team = this.props.team; + + var client_error = this.state.client_error ? this.state.client_error : null; + var server_error = this.state.server_error ? this.state.server_error : null; + + var valetSection; + var self = this; + + if (this.props.activeSection === 'valet') { + var valetActive = ["",""]; + if (this.state.allow_valet === "false") { + valetActive[1] = "active"; + } else { + valetActive[0] = "active"; + } + + var inputs = []; + + inputs.push( + <div className="col-sm-12"> + <div className="btn-group" data-toggle="buttons-radio"> + <button className={"btn btn-default "+valetActive[0]} onClick={function(){self.handleValetRadio("true")}}>On</button> + <button className={"btn btn-default "+valetActive[1]} onClick={function(){self.handleValetRadio("false")}}>Off</button> + </div> + <div><br/>Warning: Turning on the Valet feature and using it with any third party software increases the risk of a security breach.</div> + </div> + ); + + valetSection = ( + <SettingItemMax + title="Valet" + inputs={inputs} + submit={this.submitValetFeature} + server_error={server_error} + client_error={client_error} + updateSection={function(e){self.props.updateSection("");e.preventDefault();}} + /> + ); + } else { + var describe = ""; + if (this.state.allow_valet === "false") { + describe = "Off"; + } else { + describe = "On"; + } + + valetSection = ( + <SettingItemMin + title="Valet" + describe={describe} + updateSection={function(){self.props.updateSection("valet");}} + /> + ); + } + + return ( + <div> + <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" ref="title"><i className="modal-back"></i>General Settings</h4> + </div> + <div ref="wrapper" className="user-settings"> + <h3 className="tab-header">Feature Settings</h3> + <div className="divider-dark first"/> + {valetSection} + <div className="divider-dark"/> + </div> + </div> + ); + } +}); + +module.exports = React.createClass({ + componentDidMount: function() { + TeamStore.addChangeListener(this._onChange); + }, + componentWillUnmount: function() { + TeamStore.removeChangeListener(this._onChange); + }, + _onChange: function () { + var team = TeamStore.getCurrent(); + if (!utils.areStatesEqual(this.state.team, team)) { + this.setState({ team: team }); + } + }, + getInitialState: function() { + return { team: TeamStore.getCurrent() }; + }, + render: function() { + if (this.props.activeTab === 'general') { + return ( + <div> + </div> + ); + } else if (this.props.activeTab === 'feature') { + return ( + <div> + <FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} /> + </div> + ); + } else { + return <div/>; + } + } +}); diff --git a/web/react/components/settings_modal.jsx b/web/react/components/team_settings_modal.jsx index 57a869f93..08a952d2e 100644 --- a/web/react/components/settings_modal.jsx +++ b/web/react/components/team_settings_modal.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. var SettingsSidebar = require('./settings_sidebar.jsx'); -var UserSettings = require('./user_settings.jsx'); +var TeamSettings = require('./team_settings.jsx'); module.exports = React.createClass({ componentDidMount: function() { @@ -22,27 +22,31 @@ module.exports = React.createClass({ this.setState({ active_section: section }); }, getInitialState: function() { - return { active_tab: "general", active_section: "" }; + return { active_tab: "feature", active_section: "" }; }, render: function() { + var tabs = []; + tabs.push({name: "feature", ui_name: "Features", icon: "glyphicon glyphicon-wrench"}); + return ( - <div className="modal fade" ref="modal" id="settings_modal" role="dialog" aria-hidden="true"> + <div className="modal fade" ref="modal" id="team_settings" role="dialog" aria-hidden="true"> <div className="modal-dialog settings-modal"> <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" ref="title">Account Settings</h4> + <h4 className="modal-title" ref="title">Team Settings</h4> </div> <div className="modal-body"> <div className="settings-table"> <div className="settings-links"> <SettingsSidebar + tabs={tabs} activeTab={this.state.active_tab} updateTab={this.updateTab} /> </div> <div className="settings-content"> - <UserSettings + <TeamSettings activeTab={this.state.active_tab} activeSection={this.state.active_section} updateSection={this.updateSection} diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index a9c2433f2..7d542a8b7 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -658,9 +658,10 @@ var SecurityTab = React.createClass({ ); } else { var d = new Date(this.props.user.last_password_update); - var hour = d.getHours() < 10 ? "0" + d.getHours() : String(d.getHours()); + var hour = d.getHours() % 12 ? String(d.getHours() % 12) : "12"; var min = d.getMinutes() < 10 ? "0" + d.getMinutes() : String(d.getMinutes()); - var dateStr = "Last updated " + Constants.MONTHS[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear() + " at " + hour + ":" + min; + var timeOfDay = d.getHours() >= 12 ? " pm" : " am"; + var dateStr = "Last updated " + Constants.MONTHS[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear() + " at " + hour + ":" + min + timeOfDay; passwordSection = ( <SettingItemMin @@ -1056,7 +1057,7 @@ var AppearanceTab = React.createClass({ themeSection = ( <SettingItemMax - title="Theme" + title="Theme Color" inputs={inputs} submit={this.submitTheme} server_error={server_error} @@ -1066,7 +1067,7 @@ var AppearanceTab = React.createClass({ } else { themeSection = ( <SettingItemMin - title="Theme" + title="Theme Color" describe={this.state.theme} updateSection={function(){self.props.updateSection("theme");}} /> diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx new file mode 100644 index 000000000..ff001611d --- /dev/null +++ b/web/react/components/user_settings_modal.jsx @@ -0,0 +1,68 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var SettingsSidebar = require('./settings_sidebar.jsx'); +var UserSettings = require('./user_settings.jsx'); + +module.exports = React.createClass({ + componentDidMount: function() { + $('body').on('click', '.modal-back', function(){ + $(this).closest('.modal-dialog').removeClass('display--content'); + }); + $('body').on('click', '.modal-header .close', function(){ + setTimeout(function() { + $('.modal-dialog.display--content').removeClass('display--content'); + }, 500); + }); + }, + updateTab: function(tab) { + this.setState({ active_tab: tab }); + }, + updateSection: function(section) { + this.setState({ active_section: section }); + }, + getInitialState: function() { + return { active_tab: "general", active_section: "" }; + }, + render: function() { + var tabs = []; + tabs.push({name: "general", ui_name: "General", icon: "glyphicon glyphicon-cog"}); + tabs.push({name: "security", ui_name: "Security", icon: "glyphicon glyphicon-lock"}); + tabs.push({name: "notifications", ui_name: "Notifications", icon: "glyphicon glyphicon-exclamation-sign"}); + tabs.push({name: "appearance", ui_name: "Appearance", icon: "glyphicon glyphicon-wrench"}); + //tabs.push({name: "sessions", ui_name: "Sessions", icon: "glyphicon glyphicon-globe"}); + //tabs.push({name: "activity_log", ui_name: "Activity Log", icon: "glyphicon glyphicon-time"}); + + return ( + <div className="modal fade" ref="modal" id="user_settings1" role="dialog" aria-hidden="true"> + <div className="modal-dialog settings-modal"> + <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" ref="title">Account Settings</h4> + </div> + <div className="modal-body"> + <div className="settings-table"> + <div className="settings-links"> + <SettingsSidebar + tabs={tabs} + activeTab={this.state.active_tab} + updateTab={this.updateTab} + /> + </div> + <div className="settings-content"> + <UserSettings + activeTab={this.state.active_tab} + activeSection={this.state.active_section} + updateSection={this.updateSection} + /> + </div> + </div> + </div> + </div> + </div> + </div> + ); + } +}); + diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 4cb30e1d3..c573e9dbb 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -124,7 +124,7 @@ module.exports = React.createClass({ <div key={name+"_loading"}> <img ref="placeholder" className="loader-image" src="/static/images/load.gif" /> { percentage > 0 ? - <span className="loader-percent" >{"Downloading " + percentage + "%"}</span> + <span className="loader-percent" >{"Previewing " + percentage + "%"}</span> : ""} </div> ); diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index df67d4360..3aa985863 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -22,7 +22,8 @@ var MoreChannelsModal = require('../components/more_channels.jsx'); var NewChannelModal = require('../components/new_channel.jsx'); var PostDeletedModal = require('../components/post_deleted_modal.jsx'); var ChannelNotificationsModal = require('../components/channel_notifications.jsx'); -var UserSettingsModal = require('../components/settings_modal.jsx'); +var UserSettingsModal = require('../components/user_settings_modal.jsx'); +var TeamSettingsModal = require('../components/team_settings_modal.jsx'); var ChannelMembersModal = require('../components/channel_members.jsx'); var ChannelInviteModal = require('../components/channel_invite_modal.jsx'); var TeamMembersModal = require('../components/team_members.jsx'); @@ -36,7 +37,7 @@ var ChannelInfoModal = require('../components/channel_info_modal.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -global.window.setup_channel_page = function(team_name, team_type, channel_name, channel_id) { +global.window.setup_channel_page = function(team_name, team_type, team_id, channel_name, channel_id) { AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_CHANNEL, @@ -44,6 +45,11 @@ global.window.setup_channel_page = function(team_name, team_type, channel_name, id: channel_id }); + AppDispatcher.handleViewAction({ + type: ActionTypes.CLICK_TEAM, + id: team_id + }); + React.render( <ErrorBar/>, document.getElementById('error_bar') @@ -80,6 +86,11 @@ global.window.setup_channel_page = function(team_name, team_type, channel_name, ); React.render( + <TeamSettingsModal />, + document.getElementById('team_settings_modal') + ); + + React.render( <TeamMembersModal teamName={team_name} />, document.getElementById('team_members_modal') ); diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx new file mode 100644 index 000000000..e95daeeba --- /dev/null +++ b/web/react/stores/team_store.jsx @@ -0,0 +1,100 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +var EventEmitter = require('events').EventEmitter; +var assign = require('object-assign'); + +var Constants = require('../utils/constants.jsx'); +var ActionTypes = Constants.ActionTypes; + + +var CHANGE_EVENT = 'change'; + +var TeamStore = assign({}, EventEmitter.prototype, { + emitChange: function() { + this.emit(CHANGE_EVENT); + }, + addChangeListener: function(callback) { + this.on(CHANGE_EVENT, callback); + }, + removeChangeListener: function(callback) { + this.removeListener(CHANGE_EVENT, callback); + }, + get: function(id) { + var c = this._getTeams(); + return c[id]; + }, + getByName: function(name) { + var current = null; + var t = this._getTeams(); + + for (id in t) { + if (t[id].name == name) { + return t[id]; + } + } + + return null; + }, + getAll: function() { + return this._getTeams(); + }, + setCurrentId: function(id) { + if (id == null) + sessionStorage.removeItem("current_team_id"); + else + sessionStorage.setItem("current_team_id", id); + }, + getCurrentId: function() { + return sessionStorage.getItem("current_team_id"); + }, + getCurrent: function() { + var currentId = TeamStore.getCurrentId(); + + if (currentId != null) + return this.get(currentId); + else + return null; + }, + storeTeam: function(team) { + var teams = this._getTeams(); + teams[team.id] = team; + this._storeTeams(teams); + }, + _storeTeams: function(teams) { + sessionStorage.setItem("user_teams", JSON.stringify(teams)); + }, + _getTeams: function() { + var teams = {}; + + try { + teams = JSON.parse(sessionStorage.user_teams); + } + catch (err) { + } + + return teams; + } +}); + +TeamStore.dispatchToken = AppDispatcher.register(function(payload) { + var action = payload.action; + + switch(action.type) { + + case ActionTypes.CLICK_TEAM: + TeamStore.setCurrentId(action.id); + TeamStore.emitChange(); + break; + + case ActionTypes.RECIEVED_TEAM: + TeamStore.storeTeam(action.team); + TeamStore.emitChange(); + break; + + default: + } +}); + +module.exports = TeamStore; diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index bb7ca458f..9383057c3 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -15,6 +15,8 @@ var ActionTypes = Constants.ActionTypes; var callTracker = {}; var dispatchError = function(err, method) { + if (err.message === "There appears to be a problem with your internet connection") return; + AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_ERROR, err: err, @@ -355,3 +357,25 @@ module.exports.getStatuses = function() { } ); } + +module.exports.getMyTeam = function() { + if (isCallInProgress("getMyTeam")) return; + + callTracker["getMyTeam"] = utils.getTimestamp(); + client.getMyTeam( + function(data, textStatus, xhr) { + callTracker["getMyTeam"] = 0; + + if (xhr.status === 304 || !data) return; + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_TEAM, + team: data + }); + }, + function(err) { + callTracker["getMyTeam"] = 0; + dispatchError(err, "getMyTeam"); + } + ); +} diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 786e6dcea..15b6ace91 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -811,3 +811,34 @@ module.exports.getStatuses = function(success, error) { } }); }; + +module.exports.getMyTeam = function(success, error) { + $.ajax({ + url: "/api/v1/teams/me", + dataType: 'json', + type: 'GET', + success: success, + ifModified: true, + error: function(xhr, status, err) { + e = handleError("getMyTeam", xhr, status, err); + error(e); + } + }); +}; + +module.exports.updateValetFeature = function(data, success, error) { + $.ajax({ + url: "/api/v1/teams/update_valet_feature", + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(data), + success: success, + error: function(xhr, status, err) { + e = handleError("updateValetFeature", xhr, status, err); + error(e); + } + }); + + module.exports.track('api', 'api_teams_update_valet_feature'); +}; diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 0a3b1db3d..4a0d243e2 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -27,6 +27,9 @@ module.exports = { RECIEVED_STATUSES: null, RECIEVED_MSG: null, + + CLICK_TEAM: null, + RECIEVED_TEAM: null, }), PayloadSources: keyMirror({ @@ -61,6 +64,7 @@ module.exports = { "channel", "internal", "localhost", + "dockerhost", "stag", "post", "cluster", diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 628d92342..a5d1e8895 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -28,6 +28,9 @@ module.exports.isTestDomain = function() { if ((/^localhost/).test(window.location.hostname)) return true; + if ((/^dockerhost/).test(window.location.hostname)) + return true; + if ((/^test/).test(window.location.hostname)) return true; @@ -75,8 +78,14 @@ module.exports.getDomainWithOutSub = function() { var parts = window.location.host.split("."); - if (parts.length == 1) - return "localhost:8065"; + if (parts.length == 1) { + if (parts[0].indexOf("dockerhost") > -1) { + return "dockerhost:8065"; + } + else { + return "localhost:8065"; + } + } return parts[1] + "." + parts[2]; } @@ -357,9 +366,6 @@ module.exports.textToJsx = function(text, options) { if (options && options['singleline']) { var repRegex = new RegExp("\n", "g"); text = text.replace(repRegex, " "); - } else { - var repRegex = new RegExp("\n", "g"); - text = text.replace(repRegex, "<br>"); } var searchTerm = "" @@ -383,7 +389,7 @@ module.exports.textToJsx = function(text, options) { implicitKeywords[keywordArray[i]] = true; } - var lines = text.split("<br>"); + var lines = text.split("\n"); var urlMatcher = new LinkifyIt(); for (var i = 0; i < lines.length; i++) { var line = lines[i]; @@ -412,7 +418,7 @@ module.exports.textToJsx = function(text, options) { highlightSearchClass = " search-highlight"; } - inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function() {module.exports.searchForTerm(name);}}>@{name}</a>{suffix} </span>); + inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>); } else if (urlMatcher.test(word)) { var match = urlMatcher.match(word)[0]; var link = match.url; @@ -425,7 +431,7 @@ module.exports.textToJsx = function(text, options) { } else if (trimWord.match(hashRegex)) { var suffix = word.match(puncEndRegex); var prefix = word.match(puncStartRegex); - var mClass = trimWord in implicitKeywords ? mentionClass : ""; + var mClass = trimWord in implicitKeywords || trimWord.toLowerCase() in implicitKeywords ? mentionClass : ""; if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) { highlightSearchClass = " search-highlight"; @@ -433,7 +439,7 @@ module.exports.textToJsx = function(text, options) { inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>); - } else if (trimWord in implicitKeywords) { + } else if (trimWord in implicitKeywords || trimWord.toLowerCase() in implicitKeywords) { var suffix = word.match(puncEndRegex); var prefix = word.match(puncStartRegex); |