summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/access_history_modal.jsx57
-rw-r--r--web/react/components/activity_log_modal.jsx113
-rw-r--r--web/react/components/channel_header.jsx2
-rw-r--r--web/react/components/create_comment.jsx29
-rw-r--r--web/react/components/create_post.jsx29
-rw-r--r--web/react/components/file_attachment.jsx10
-rw-r--r--web/react/components/file_upload.jsx101
-rw-r--r--web/react/components/file_upload_overlay.jsx26
-rw-r--r--web/react/components/login.jsx2
-rw-r--r--web/react/components/post.jsx6
-rw-r--r--web/react/components/post_body.jsx78
-rw-r--r--web/react/components/post_list.jsx17
-rw-r--r--web/react/components/post_right.jsx34
-rw-r--r--web/react/components/search_results.jsx12
-rw-r--r--web/react/components/setting_item_min.jsx19
-rw-r--r--web/react/components/sidebar.jsx17
-rw-r--r--web/react/components/sidebar_header.jsx2
-rw-r--r--web/react/components/sidebar_right_menu.jsx2
-rw-r--r--web/react/components/user_settings.jsx41
-rw-r--r--web/react/components/user_settings_modal.jsx3
-rw-r--r--web/react/pages/channel.jsx7
-rw-r--r--web/react/stores/team_store.jsx143
-rw-r--r--web/react/utils/utils.jsx318
23 files changed, 519 insertions, 549 deletions
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index 16768a119..a19e5c16e 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -13,21 +13,23 @@ function getStateFromStoresForAudits() {
}
module.exports = React.createClass({
+ displayName: 'AccessHistoryModal',
componentDidMount: function() {
- UserStore.addAuditsChangeListener(this._onChange);
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) {
+ UserStore.addAuditsChangeListener(this.onListenerChange);
+ $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function() {
AsyncClient.getAudits();
});
var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
+ $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function() {
+ $('#user_settings').modal('show');
self.setState({moreInfo: []});
});
},
componentWillUnmount: function() {
- UserStore.removeAuditsChangeListener(this._onChange);
+ UserStore.removeAuditsChangeListener(this.onListenerChange);
},
- _onChange: function() {
+ onListenerChange: function() {
var newState = getStateFromStoresForAudits();
if (!utils.areStatesEqual(newState.audits, this.state.audits)) {
this.setState(newState);
@@ -61,6 +63,21 @@ module.exports = React.createClass({
currentAudit.session_id = 'N/A (Login attempt)';
}
+ var moreInfo = (<a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
+ if (this.state.moreInfo[i]) {
+ moreInfo = (
+ <div>
+ <div>{'Session ID: ' + currentAudit.session_id}</div>
+ <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div>
+ </div>
+ );
+ }
+
+ var divider = null;
+ if (i < this.state.audits.length - 1) {
+ divider = (<div className='divider-light'></div>)
+ }
+
accessList[i] = (
<div className='access-history__table'>
<div className='access__date'>{newDate}</div>
@@ -68,25 +85,21 @@ module.exports = React.createClass({
<div className='report__time'>{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'})}</div>
<div className='report__info'>
<div>{'IP: ' + currentAudit.ip_address}</div>
- {this.state.moreInfo[i] ?
- <div>
- <div>{'Session ID: ' + currentAudit.session_id}</div>
- <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div>
- </div>
- :
- <a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>
- }
+ {moreInfo}
</div>
- {i < this.state.audits.length - 1 ?
- <div className='divider-light'/>
- :
- null
- }
+ {divider}
</div>
</div>
);
}
+ var content;
+ if (this.state.audits.loading) {
+ content = (<LoadingScreen />);
+ } else {
+ content = (<form role='form'>{accessList}</form>);
+ }
+
return (
<div>
<div className='modal fade' ref='modal' id='access-history' tabIndex='-1' role='dialog' aria-hidden='true'>
@@ -97,13 +110,7 @@ module.exports = React.createClass({
<h4 className='modal-title' id='myModalLabel'>Access History</h4>
</div>
<div ref='modalBody' className='modal-body'>
- {!this.state.audits.loading ?
- <form role='form'>
- {accessList}
- </form>
- :
- <LoadingScreen />
- }
+ {content}
</div>
</div>
</div>
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index f28f0d5f1..1192a72bc 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -10,40 +10,41 @@ var utils = require('../utils/utils.jsx');
function getStateFromStoresForSessions() {
return {
sessions: UserStore.getSessions(),
- server_error: null,
- client_error: null
+ serverError: null,
+ clientError: null
};
}
module.exports = React.createClass({
+ displayName: 'ActivityLogModal',
submitRevoke: function(altId) {
- var self = this;
Client.revokeSession(altId,
function(data) {
AsyncClient.getSessions();
}.bind(this),
function(err) {
- state = getStateFromStoresForSessions();
- state.server_error = err;
+ var state = getStateFromStoresForSessions();
+ state.serverError = err;
this.setState(state);
}.bind(this)
);
},
componentDidMount: function() {
- UserStore.addSessionsChangeListener(this._onChange);
+ UserStore.addSessionsChangeListener(this.onListenerChange);
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) {
AsyncClient.getSessions();
});
var self = this;
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({ moreInfo: [] });
+ $('#user_settings').modal('show');
+ self.setState({moreInfo: []});
});
},
componentWillUnmount: function() {
- UserStore.removeSessionsChangeListener(this._onChange);
+ UserStore.removeSessionsChangeListener(this.onListenerChange);
},
- _onChange: function() {
+ onListenerChange: function() {
var newState = getStateFromStoresForSessions();
if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) {
this.setState(newState);
@@ -52,7 +53,7 @@ module.exports = React.createClass({
handleMoreInfo: function(index) {
var newMoreInfo = this.state.moreInfo;
newMoreInfo[index] = true;
- this.setState({ moreInfo: newMoreInfo });
+ this.setState({moreInfo: newMoreInfo});
},
getInitialState: function() {
var initialState = getStateFromStoresForSessions();
@@ -61,68 +62,76 @@ module.exports = React.createClass({
},
render: function() {
var activityList = [];
- var server_error = this.state.server_error ? this.state.server_error : null;
+ var serverError = this.state.serverError;
+
+ // Squash any false-y value for server error into null
+ if (!serverError) {
+ serverError = null;
+ }
for (var i = 0; i < this.state.sessions.length; i++) {
var currentSession = this.state.sessions[i];
var lastAccessTime = new Date(currentSession.last_activity_at);
var firstAccessTime = new Date(currentSession.create_at);
- var devicePicture = "";
+ var devicePicture = '';
- if (currentSession.props.platform === "Windows") {
- devicePicture = "fa fa-windows";
+ if (currentSession.props.platform === 'Windows') {
+ devicePicture = 'fa fa-windows';
}
- else if (currentSession.props.platform === "Macintosh" || currentSession.props.platform === "iPhone") {
- devicePicture = "fa fa-apple";
+ else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
+ devicePicture = 'fa fa-apple';
}
- else if (currentSession.props.platform === "Linux") {
- devicePicture = "fa fa-linux";
+ else if (currentSession.props.platform === 'Linux') {
+ devicePicture = 'fa fa-linux';
+ }
+
+ var moreInfo;
+ if (this.state.moreInfo[i]) {
+ moreInfo = (
+ <div>
+ <div>{'First time active: ' + firstAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
+ <div>{'OS: ' + currentSession.props.os}</div>
+ <div>{'Browser: ' + currentSession.props.browser}</div>
+ <div>{'Session ID: ' + currentSession.alt_id}</div>
+ </div>
+ );
+ } else {
+ moreInfo = (<a className='theme' href='#' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
}
activityList[i] = (
- <div className="activity-log__table">
- <div className="activity-log__report">
- <div className="report__platform"><i className={devicePicture} />{currentSession.props.platform}</div>
- <div className="report__info">
- <div>{"Last activity: " + lastAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}</div>
- { this.state.moreInfo[i] ?
- <div>
- <div>{"First time active: " + firstAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}</div>
- <div>{"OS: " + currentSession.props.os}</div>
- <div>{"Browser: " + currentSession.props.browser}</div>
- <div>{"Session ID: " + currentSession.alt_id}</div>
- </div>
- :
- <a className="theme" href="#" onClick={this.handleMoreInfo.bind(this, i)}>More info</a>
- }
+ <div className='activity-log__table'>
+ <div className='activity-log__report'>
+ <div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
+ <div className='report__info'>
+ <div>{'Last activity: ' + lastAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
+ {moreInfo}
</div>
</div>
- <div className="activity-log__action"><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className="btn btn-primary">Logout</button></div>
+ <div className='activity-log__action'><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className='btn btn-primary'>Logout</button></div>
</div>
);
}
+ var content;
+ if (this.state.sessions.loading) {
+ content = (<LoadingScreen />);
+ } else {
+ content = (<form role='form'>{activityList}</form>);
+ }
+
return (
<div>
- <div className="modal fade" ref="modal" id="activity-log" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog modal-lg">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" id="myModalLabel">Active Sessions</h4>
+ <div className='modal fade' ref='modal' id='activity-log' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div className='modal-dialog modal-lg'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
+ <h4 className='modal-title' id='myModalLabel'>Active Sessions</h4>
</div>
- <p className="session-help-text">Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the "Logout" button below to end a session.</p>
- <div ref="modalBody" className="modal-body">
- { !this.state.sessions.loading ?
- <div>
- <form role="form">
- { activityList }
- </form>
- { server_error }
- </div>
- :
- <LoadingScreen />
- }
+ <p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p>
+ <div ref='modalBody' className='modal-body'>
+ {content}
</div>
</div>
</div>
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 4d64e2b94..90a776791 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -156,7 +156,7 @@ module.exports = React.createClass({
}
var channel = this.state.channel;
- var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true, noTextFormatting: true});
+ var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true});
var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
var channelTitle = channel.display_name;
var currentId = UserStore.getCurrentId();
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index a0a018025..885efab7a 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -122,16 +122,20 @@ module.exports = React.createClass({
this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
},
handleUploadError: function(err, clientId) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ if (clientId !== -1) {
+ var draft = PostStore.getCommentDraft(this.props.rootId);
- var index = draft['uploadsInProgress'].indexOf(clientId);
- if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
- }
+ var index = draft['uploadsInProgress'].indexOf(clientId);
+ if (index !== -1) {
+ draft['uploadsInProgress'].splice(index, 1);
+ }
- PostStore.storeCommentDraft(this.props.rootId, draft);
+ PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ } else {
+ this.setState({serverError: err});
+ }
},
clearPreviews: function() {
this.setState({previews: []});
@@ -184,7 +188,6 @@ module.exports = React.createClass({
</div>
);
}
- var allowTextFormatting = config.AllowTextFormatting;
var postError = null;
if (this.state.postError) {
@@ -205,10 +208,6 @@ module.exports = React.createClass({
if (postError) {
postFooterClassName += ' has-error';
}
- var extraInfo = <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} />;
- if (this.state.messageText.split(' ').length > 1 && allowTextFormatting) {
- extraInfo = <span className='msg-format-help'>_<em>italics</em>_ *<strong>bold</strong>* `<code className='code-info'>code</code>`</span>;
- }
return (
<form onSubmit={this.handleSubmit}>
@@ -227,9 +226,11 @@ module.exports = React.createClass({
getFileCount={this.getFileCount}
onUploadStart={this.handleUploadStart}
onFileUpload={this.handleFileUploadComplete}
- onUploadError={this.handleUploadError} />
+ onUploadError={this.handleUploadError}
+ postType='comment'
+ channelId={this.props.channelId} />
</div>
- {extraInfo}
+ <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} />
<div className={postFooterClassName}>
<input type='button' className='btn btn-primary comment-btn pull-right' value='Add Comment' onClick={this.handleSubmit} />
{postError}
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 3e1faba7d..377e7bd34 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -145,16 +145,20 @@ module.exports = React.createClass({
this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
},
handleUploadError: function(err, clientId) {
- var draft = PostStore.getDraft(this.state.channelId);
+ if (clientId !== -1) {
+ var draft = PostStore.getDraft(this.state.channelId);
- var index = draft['uploadsInProgress'].indexOf(clientId);
- if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
- }
+ var index = draft['uploadsInProgress'].indexOf(clientId);
+ if (index !== -1) {
+ draft['uploadsInProgress'].splice(index, 1);
+ }
- PostStore.storeDraft(this.state.channelId, draft);
+ PostStore.storeDraft(this.state.channelId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ } else {
+ this.setState({serverError: err});
+ }
},
removePreview: function(id) {
var previews = this.state.previews;
@@ -224,7 +228,6 @@ module.exports = React.createClass({
</div>
);
}
- var allowTextFormatting = config.AllowTextFormatting;
var postError = null;
if (this.state.postError) {
@@ -245,10 +248,6 @@ module.exports = React.createClass({
if (postError) {
postFooterClassName += ' has-error';
}
- var extraInfo = <MsgTyping channelId={this.state.channel_id} parentId='' />;
- if (this.state.messageText.split(' ').length > 1 && allowTextFormatting) {
- extraInfo = <span className='msg-typing'>_<em>italics</em>_ *<strong>bold</strong>* `<code className='code-info'>code</code>`</span>;
- }
return (
<form id='create_post' ref='topDiv' role='form' onSubmit={this.handleSubmit}>
@@ -267,13 +266,15 @@ module.exports = React.createClass({
getFileCount={this.getFileCount}
onUploadStart={this.handleUploadStart}
onFileUpload={this.handleFileUploadComplete}
- onUploadError={this.handleUploadError} />
+ onUploadError={this.handleUploadError}
+ postType='post'
+ channelId='' />
</div>
<div className={postFooterClassName}>
{postError}
{serverError}
{preview}
- {extraInfo}
+ <MsgTyping channelId={this.state.channelId} parentId=''/>
</div>
</div>
</form>
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index b7ea5734f..c36c908d2 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -113,6 +113,14 @@ module.exports = React.createClass({
fileSizeString = utils.fileSizeToString(this.state.fileSize);
}
+ var filenameString = decodeURIComponent(utils.getFileName(filename));
+ var trimmedFilename;
+ if (filenameString.length > 35) {
+ trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + "...";
+ } else {
+ trimmedFilename = filenameString;
+ }
+
return (
<div className="post-image__column" key={filename}>
<a className="post-image__thumbnail" href="#" onClick={this.props.handleImageClick}
@@ -120,7 +128,7 @@ module.exports = React.createClass({
{thumbnail}
</a>
<div className="post-image__details">
- <div className="post-image__name">{decodeURIComponent(utils.getFileName(filename))}</div>
+ <div data-toggle="tooltip" title={filenameString} className="post-image__name">{trimmedFilename}</div>
<div>
<span className="post-image__type">{fileInfo.ext.toUpperCase()}</span>
<span className="post-image__size">{fileSizeString}</span>
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index c1fab669c..7497ec330 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -12,7 +12,9 @@ module.exports = React.createClass({
onUploadError: React.PropTypes.func,
getFileCount: React.PropTypes.func,
onFileUpload: React.PropTypes.func,
- onUploadStart: React.PropTypes.func
+ onUploadStart: React.PropTypes.func,
+ channelId: React.PropTypes.string,
+ postType: React.PropTypes.string
},
getInitialState: function() {
return {requests: {}};
@@ -21,7 +23,7 @@ module.exports = React.createClass({
var element = $(this.refs.fileInput.getDOMNode());
var files = element.prop('files');
- var channelId = ChannelStore.getCurrentId();
+ var channelId = this.props.channelId || ChannelStore.getCurrentId();
this.props.onUploadError(null);
@@ -61,8 +63,8 @@ module.exports = React.createClass({
this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
var requests = this.state.requests;
- for (var i = 0; i < parsedData.client_ids.length; i++) {
- delete requests[parsedData.client_ids[i]];
+ for (var j = 0; j < parsedData.client_ids.length; j++) {
+ delete requests[parsedData.client_ids[j]];
}
this.setState({requests: requests});
}.bind(this),
@@ -87,10 +89,94 @@ module.exports = React.createClass({
}
} catch(e) {}
},
+ handleDrop: function(e) {
+ this.props.onUploadError(null);
+
+ var files = e.originalEvent.dataTransfer.files;
+ var channelId = this.props.channelId || ChannelStore.getCurrentId();
+
+ if (typeof files !== 'string' && files.length) {
+ var numFiles = files.length;
+
+ var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId), numFiles);
+
+ if (numFiles > numToUpload) {
+ this.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.');
+ }
+
+ for (var i = 0; i < files.length && i < numToUpload; i++) {
+ if (files[i].size > Constants.MAX_FILE_SIZE) {
+ this.props.onUploadError('Files must be no more than ' + Constants.MAX_FILE_SIZE / 1000000 + ' MB');
+ continue;
+ }
+
+ // generate a unique id that can be used by other components to refer back to this file upload
+ var clientId = utils.generateId();
+
+ // Prepare data to be uploaded.
+ var formData = new FormData();
+ formData.append('channel_id', channelId);
+ formData.append('files', files[i], files[i].name);
+ formData.append('client_ids', clientId);
+
+ var request = client.uploadFile(formData,
+ function(data) {
+ var parsedData = $.parseJSON(data);
+ this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
+
+ var requests = this.state.requests;
+ for (var j = 0; j < parsedData.client_ids.length; j++) {
+ delete requests[parsedData.client_ids[j]];
+ }
+ this.setState({requests: requests});
+ }.bind(this),
+ function(err) {
+ this.props.onUploadError(err, clientId);
+ }.bind(this)
+ );
+
+ var requests = this.state.requests;
+ requests[clientId] = request;
+ this.setState({requests: requests});
+
+ this.props.onUploadStart([clientId], channelId);
+ }
+ } else {
+ this.props.onUploadError('Invalid file upload', -1);
+ }
+ },
componentDidMount: function() {
var inputDiv = this.refs.input.getDOMNode();
var self = this;
+ if (this.props.postType === 'post') {
+ $('.row.main').dragster({
+ enter: function() {
+ $('.center-file-overlay').removeClass('hidden');
+ },
+ leave: function() {
+ $('.center-file-overlay').addClass('hidden');
+ },
+ drop: function(dragsterEvent, e) {
+ $('.center-file-overlay').addClass('hidden');
+ self.handleDrop(e);
+ }
+ });
+ } else if (this.props.postType === 'comment') {
+ $('.post-right__container').dragster({
+ enter: function() {
+ $('.right-file-overlay').removeClass('hidden');
+ },
+ leave: function() {
+ $('.right-file-overlay').addClass('hidden');
+ },
+ drop: function(dragsterEvent, e) {
+ $('.right-file-overlay').addClass('hidden');
+ self.handleDrop(e);
+ }
+ });
+ }
+
document.addEventListener('paste', function(e) {
var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0];
@@ -133,14 +219,13 @@ module.exports = React.createClass({
continue;
}
- var channelId = ChannelStore.getCurrentId();
+ var channelId = this.props.channelId || ChannelStore.getCurrentId();
// generate a unique id that can be used by other components to refer back to this file upload
var clientId = utils.generateId();
var formData = new FormData();
formData.append('channel_id', channelId);
-
var d = new Date();
var hour;
if (d.getHours() < 10) {
@@ -165,8 +250,8 @@ module.exports = React.createClass({
self.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
var requests = self.state.requests;
- for (var i = 0; i < parsedData.client_ids.length; i++) {
- delete requests[parsedData.client_ids[i]];
+ for (var j = 0; j < parsedData.client_ids.length; j++) {
+ delete requests[parsedData.client_ids[j]];
}
self.setState({requests: requests});
},
diff --git a/web/react/components/file_upload_overlay.jsx b/web/react/components/file_upload_overlay.jsx
new file mode 100644
index 000000000..f35556371
--- /dev/null
+++ b/web/react/components/file_upload_overlay.jsx
@@ -0,0 +1,26 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+module.exports = React.createClass({
+ displayName: 'FileUploadOverlay',
+ propTypes: {
+ overlayType: React.PropTypes.string
+ },
+ render: function() {
+ var overlayClass = 'file-overlay hidden';
+ if (this.props.overlayType === 'right') {
+ overlayClass += ' right-file-overlay';
+ } else if (this.props.overlayType === 'center') {
+ overlayClass += ' center-file-overlay';
+ }
+
+ return (
+ <div className={overlayClass}>
+ <div>
+ <i className='fa fa-upload'></i>
+ <span>Drop a file to upload it.</span>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index c395bb500..f9eacf094 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -49,7 +49,7 @@ module.exports = React.createClass({
var redirect = utils.getUrlParameter('redirect');
if (redirect) {
- window.location.pathname = decodeURI(redirect);
+ window.location.pathname = decodeURIComponent(redirect);
} else {
window.location.pathname = '/' + name + '/channels/town-square';
}
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index e72a2d001..f099c67ab 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -11,12 +11,6 @@ var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({
displayName: "Post",
- componentDidMount: function() {
- $('.modal').on('show.bs.modal', function () {
- $('.modal-body').css('overflow-y', 'auto');
- $('.modal-body').css('max-height', $(window).height() * 0.7);
- });
- },
handleCommentClick: function(e) {
e.preventDefault();
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index fab6833e6..860c96d84 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -4,69 +4,59 @@
var FileAttachmentList = require('./file_attachment_list.jsx');
var UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx');
-var formatText = require('../../static/js/marked/lib/marked.js');
module.exports = React.createClass({
componentWillReceiveProps: function(nextProps) {
var linkData = utils.extractLinks(nextProps.post.message);
- this.setState({links: linkData.links, message: linkData.text});
+ this.setState({ links: linkData["links"], message: linkData["text"] });
},
getInitialState: function() {
var linkData = utils.extractLinks(this.props.post.message);
- return {links: linkData.links, message: linkData.text};
+ return { links: linkData["links"], message: linkData["text"] };
},
render: function() {
var post = this.props.post;
var filenames = this.props.post.filenames;
var parentPost = this.props.parentPost;
var inner = utils.textToJsx(this.state.message);
- var allowTextFormatting = config.AllowTextFormatting;
- var comment = '';
- var postClass = '';
+ var comment = "";
+ var reply = "";
+ var postClass = "";
if (parentPost) {
var profile = UserStore.getProfile(parentPost.user_id);
- var apostrophe = '';
- var name = '...';
+ var apostrophe = "";
+ var name = "...";
if (profile != null) {
if (profile.username.slice(-1) === 's') {
apostrophe = "'";
} else {
apostrophe = "'s";
}
- name = <a className='theme' onClick={utils.searchForTerm.bind(this, profile.username)}>{profile.username}</a>;
+ name = <a className="theme" onClick={function(){ utils.searchForTerm(profile.username); }}>{profile.username}</a>;
}
- var message = '';
- if (parentPost.message) {
- message = utils.replaceHtmlEntities(parentPost.message);
+ var message = ""
+ if(parentPost.message) {
+ message = utils.replaceHtmlEntities(parentPost.message)
} else if (parentPost.filenames.length) {
message = parentPost.filenames[0].split('/').pop();
if (parentPost.filenames.length === 2) {
- message += ' plus 1 other file';
+ message += " plus 1 other file";
} else if (parentPost.filenames.length > 2) {
- message += ' plus ' + (parentPost.filenames.length - 1) + ' other files';
+ message += " plus " + (parentPost.filenames.length - 1) + " other files";
}
}
- if (allowTextFormatting) {
- message = formatText(message, {sanitize: true, mangle: false, gfm: true, breaks: true, tables: false, smartypants: true, renderer: utils.customMarkedRenderer({disable: true})});
- comment = (
- <p className='post-link'>
- <span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick} dangerouslySetInnerHTML={{__html: message}} /></span>
- </p>
- );
- } else {
- comment = (
- <p className='post-link'>
- <span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick}>{message}</a></span>
- </p>
- );
- }
+ comment = (
+ <p className="post-link">
+ <span>Commented on {name}{apostrophe} message: <a className="theme" onClick={this.props.handleCommentClick}>{message}</a></span>
+ </p>
+ );
- postClass += ' post-comment';
+ postClass += " post-comment";
}
var embed;
@@ -74,26 +64,18 @@ module.exports = React.createClass({
embed = utils.getEmbed(this.state.links[0]);
}
- var innerHolder = <p key={post.id + '_message'} className={postClass}><span>{inner}</span></p>;
- if (allowTextFormatting) {
- innerHolder = <div key={post.id + '_message'} className={postClass}><span>{inner}</span></div>;
- }
-
- var fileAttachmentHolder = '';
- if (filenames && filenames.length > 0) {
- fileAttachmentHolder = (<FileAttachmentList
- filenames={filenames}
- modalId={'view_image_modal_' + post.id}
- channelId={post.channel_id}
- userId={post.user_id} />);
- }
-
return (
- <div className='post-body'>
- {comment}
- {innerHolder}
- {fileAttachmentHolder}
- {embed}
+ <div className="post-body">
+ { comment }
+ <p key={post.id+"_message"} className={postClass}><span>{inner}</span></p>
+ { filenames && filenames.length > 0 ?
+ <FileAttachmentList
+ filenames={filenames}
+ modalId={"view_image_modal_" + post.id}
+ channelId={post.channel_id}
+ userId={post.user_id} />
+ : "" }
+ { embed }
</div>
);
}
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index ad7f4a8bf..fa74d4295 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -69,6 +69,12 @@ module.exports = React.createClass({
this.oldScrollHeight = post_holder.scrollHeight;
this.oldZoom = (window.outerWidth - 8) / window.innerWidth;
+ $('.modal').on('show.bs.modal', function () {
+ $('.modal-body').css('overflow-y', 'auto');
+ $('.modal-body').css('max-height', $(window).height() * 0.7);
+ });
+
+ // Timeout exists for the DOM to fully render before making changes
var self = this;
$(window).resize(function(){
$(post_holder).perfectScrollbar('update');
@@ -140,6 +146,7 @@ module.exports = React.createClass({
UserStore.removeStatusesChangeListener(this._onTimeChange);
SocketStore.removeChangeListener(this._onSocketChange);
$('body').off('click.userpopover');
+ $('.modal').off('show.bs.modal')
},
resize: function() {
var post_holder = $(".post-list-holder-by-time")[0];
@@ -197,10 +204,7 @@ module.exports = React.createClass({
var post = post_list.posts[msg.props.post_id];
post.message = msg.props.message;
- post.lastEditDate = Date.now();
-
post_list.posts[post.id] = post;
-
this.setState({ post_list: post_list });
PostStore.storePosts(msg.channel_id, post_list);
@@ -433,13 +437,8 @@ module.exports = React.createClass({
// it is the last comment if it is last post in the channel or the next post has a different root post
var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i-1]].root_id != post.root_id);
- var postKey = post.id;
- if (post.lastEditDate) {
- postKey += post.lastEditDate;
- }
-
var postCtl = (
- <Post ref={post.id} sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={postKey}
+ <Post ref={post.id} sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id}
posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment}
/>
);
diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx
index 19e4cf67a..e46979ff7 100644
--- a/web/react/components/post_right.jsx
+++ b/web/react/components/post_right.jsx
@@ -11,6 +11,7 @@ var SearchBox =require('./search_bar.jsx');
var CreateComment = require( './create_comment.jsx' );
var Constants = require('../utils/constants.jsx');
var FileAttachmentList = require('./file_attachment_list.jsx');
+var FileUploadOverlay = require('./file_upload_overlay.jsx');
var ActionTypes = Constants.ActionTypes;
RhsHeaderPost = React.createClass({
@@ -56,7 +57,6 @@ RhsHeaderPost = React.createClass({
RootPost = React.createClass({
render: function() {
- var allowTextFormatting = config.AllowTextFormatting;
var post = this.props.post;
var message = utils.textToJsx(post.message);
var isOwner = UserStore.getCurrentId() == post.user_id;
@@ -77,11 +77,6 @@ RootPost = React.createClass({
channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
}
- var messageHolder = <p>{message}</p>;
- if (allowTextFormatting) {
- messageHolder = <div>{message}</div>;
- }
-
return (
<div className={"post post--root " + currentUserCss}>
<div className="post-right-channel__name">{ channelName }</div>
@@ -107,7 +102,7 @@ RootPost = React.createClass({
</li>
</ul>
<div className="post-body">
- {messageHolder}
+ <p>{message}</p>
{ post.filenames && post.filenames.length > 0 ?
<FileAttachmentList
filenames={post.filenames}
@@ -125,7 +120,6 @@ RootPost = React.createClass({
CommentPost = React.createClass({
render: function() {
- var allowTextFormatting = config.AllowTextFormatting;
var post = this.props.post;
var commentClass = "post";
@@ -145,11 +139,6 @@ CommentPost = React.createClass({
var message = utils.textToJsx(post.message);
var timestamp = UserStore.getCurrentUser().update_at;
- var messageHolder = <p>{message}</p>;
- if (allowTextFormatting) {
- messageHolder = <div>{message}</div>;
- }
-
return (
<div className={commentClass + " " + currentUserCss}>
<div className="post-profile-img__container">
@@ -172,7 +161,7 @@ CommentPost = React.createClass({
</li>
</ul>
<div className="post-body">
- {messageHolder}
+ <p>{message}</p>
{ post.filenames && post.filenames.length > 0 ?
<FileAttachmentList
filenames={post.filenames}
@@ -285,22 +274,11 @@ module.exports = React.createClass({
root_post = post_list.posts[selected_post.root_id];
}
- var rootPostKey = root_post.id
- if (root_post.lastEditDate) {
- rootPostKey += root_post.lastEditDate;
- }
-
var posts_array = [];
for (var postId in post_list.posts) {
var cpost = post_list.posts[postId];
if (cpost.root_id == root_post.id) {
- var cpostKey = cpost.id
- if (cpost.lastEditDate) {
- cpostKey += cpost.lastEditDate;
- }
-
- cpost.cpostKey = cpostKey;
posts_array.push(cpost);
}
}
@@ -319,14 +297,16 @@ module.exports = React.createClass({
return (
<div className="post-right__container">
+ <FileUploadOverlay
+ overlayType='right' />
<div className="search-bar__container sidebar--right__search-header">{searchForm}</div>
<div className="sidebar-right__body">
<RhsHeaderPost fromSearch={this.props.fromSearch} isMentionSearch={this.props.isMentionSearch} />
<div className="post-right__scroll">
- <RootPost key={rootPostKey} post={root_post} commentCount={posts_array.length}/>
+ <RootPost post={root_post} commentCount={posts_array.length}/>
<div className="post-right-comments-container">
{ posts_array.map(function(cpost) {
- return <CommentPost ref={cpost.id} key={cpost.cpostKey} post={cpost} selected={ (cpost.id == selected_post.id) } />
+ return <CommentPost ref={cpost.id} key={cpost.id} post={cpost} selected={ (cpost.id == selected_post.id) } />
})}
</div>
<div className="post-create__container">
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 8f6bd861a..643ad112b 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -84,8 +84,6 @@ var SearchItem = React.createClass({
channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
}
- var searchItemKey = Date.now().toString();
-
return (
<div className="search-item-container post" onClick={this.handleClick}>
<div className="search-channel__name">{ channelName }</div>
@@ -101,7 +99,7 @@ var SearchItem = React.createClass({
</time>
</li>
</ul>
- <div key={this.props.key + searchItemKey} className="search-item-snippet"><span>{message}</span></div>
+ <div className="search-item-snippet"><span>{message}</span></div>
</div>
</div>
);
@@ -133,7 +131,6 @@ module.exports = React.createClass({
if (this.isMounted()) {
var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
- newState.last_edit_time = Date.now();
this.setState(newState);
}
}
@@ -155,11 +152,6 @@ module.exports = React.createClass({
var noResults = (!results || !results.order || !results.order.length);
var searchTerm = PostStore.getSearchTerm();
- var searchItemKey = "";
- if (this.state.last_edit_time) {
- searchItemKey += this.state.last_edit_time.toString();
- }
-
return (
<div className="sidebar--right__content">
<div className="search-bar__container sidebar--right__search-header">{searchForm}</div>
@@ -170,7 +162,7 @@ module.exports = React.createClass({
{ noResults ? <div className="sidebar--right__subheader">No results</div>
: results.order.map(function(id) {
var post = results.posts[id];
- return <SearchItem key={searchItemKey + post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
+ return <SearchItem key={post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
}, this)
}
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index 2209c74d1..3c87e416e 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -2,12 +2,23 @@
// See License.txt for license information.
module.exports = React.createClass({
+ displayName: 'SettingsItemMin',
+ propTypes: {
+ title: React.PropTypes.string,
+ disableOpen: React.PropTypes.bool,
+ updateSection: React.PropTypes.func,
+ describe: React.PropTypes.string
+ },
render: function() {
+ var editButton = '';
+ if (!this.props.disableOpen) {
+ editButton = <li className='col-sm-2 section-edit'><a className='section-edit theme' href='#' onClick={this.props.updateSection}>Edit</a></li>;
+ }
return (
- <ul className="section-min">
- <li className="col-sm-10 section-title">{this.props.title}</li>
- <li className="col-sm-2 section-edit"><a className="section-edit theme" href="#" onClick={this.props.updateSection}>Edit</a></li>
- <li className="col-sm-7 section-describe">{this.props.describe}</li>
+ <ul className='section-min'>
+ <li className='col-sm-10 section-title'>{this.props.title}</li>
+ {editButton}
+ <li className='col-sm-7 section-describe'>{this.props.describe}</li>
</ul>
);
}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 988ef4a9c..a8496b385 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -11,7 +11,6 @@ var BrowserStore = require('../stores/browser_store.jsx');
var utils = require('../utils/utils.jsx');
var SidebarHeader = require('./sidebar_header.jsx');
var SearchBox = require('./search_bar.jsx');
-var formatText = require('../../static/js/marked/lib/marked.js');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -129,6 +128,7 @@ module.exports = React.createClass({
ChannelStore.addChangeListener(this.onChange);
UserStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
+ TeamStore.addChangeListener(this.onChange);
SocketStore.addChangeListener(this.onSocketChange);
$('.nav-pills__container').perfectScrollbar();
@@ -147,6 +147,7 @@ module.exports = React.createClass({
ChannelStore.removeChangeListener(this.onChange);
UserStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
+ TeamStore.removeChangeListener(this.onChange);
SocketStore.removeChangeListener(this.onSocketChange);
},
onChange: function() {
@@ -210,11 +211,6 @@ module.exports = React.createClass({
utils.notifyMe(title, username + ' did something new', channel);
}
} else {
- var allowTextFormatting = config.AllowTextFormatting;
- if (allowTextFormatting) {
- notifyText = formatText(notifyText, {sanitize: false, mangle: false, gfm: true, breaks: true, tables: false, smartypants: true, renderer: utils.customMarkedRenderer({disable: true})});
- }
- notifyText = utils.replaceHtmlEntities(notifyText);
utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
}
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
@@ -354,15 +350,16 @@ module.exports = React.createClass({
// set up click handler to switch channels (or create a new channel for non-existant ones)
var clickHandler = null;
- var href;
+ var href = '#';
+ var teamURL = TeamStore.getCurrentTeamUrl();
if (!channel.fake) {
clickHandler = function(e) {
e.preventDefault();
utils.switchChannel(channel);
};
- href = '#';
- } else {
- href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
+ }
+ if (channel.fake && teamURL){
+ href = teamURL + '/channels/' + channel.name;
}
return (
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index cc3f255ee..761c06e74 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -96,7 +96,7 @@ var NavbarDropdown = React.createClass({
<span className='dropdown__icon' dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
</a>
<ul className='dropdown-menu' role='menu'>
- <li><a href='#' data-toggle='modal' data-target='#user_settings1'>Account Settings</a></li>
+ <li><a href='#' data-toggle='modal' data-target='#user_settings'>Account Settings</a></li>
{teamSettings}
{inviteLink}
{teamLink}
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 2439719a1..d221ca840 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -72,7 +72,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='#user_settings1'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
+ <li><a href='#' data-toggle='modal' data-target='#user_settings'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
{teamSettingsLink}
{inviteLink}
{teamLink}
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index 1a0c313d3..8f29bbe57 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -13,6 +13,7 @@ var assign = require('object-assign');
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
+ var soundNeeded = !utils.isBrowserFirefox();
var sound = (!user.notify_props || user.notify_props.desktop_sound == undefined) ? "true" : user.notify_props.desktop_sound;
var desktop = (!user.notify_props || user.notify_props.desktop == undefined) ? "all" : user.notify_props.desktop;
var email = (!user.notify_props || user.notify_props.email == undefined) ? "true" : user.notify_props.email;
@@ -58,7 +59,7 @@ function getNotificationsStateFromStores() {
}
}
- return { notify_level: desktop, enable_email: email, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key };
+ return { notify_level: desktop, enable_email: email, soundNeeded: soundNeeded, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key };
}
@@ -105,11 +106,11 @@ var NotificationsTab = React.createClass({
},
componentDidMount: function() {
UserStore.addChangeListener(this._onChange);
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
UserStore.removeChangeListener(this._onChange);
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
_onChange: function() {
@@ -235,7 +236,7 @@ var NotificationsTab = React.createClass({
}
var soundSection;
- if (this.props.activeSection === 'sound') {
+ if (this.props.activeSection === 'sound' && this.state.soundNeeded) {
var soundActive = ["",""];
if (this.state.enable_sound === "false") {
soundActive[1] = "active";
@@ -265,7 +266,9 @@ var NotificationsTab = React.createClass({
);
} else {
var describe = "";
- if (this.state.enable_sound === "false") {
+ if (!this.state.soundNeeded) {
+ describe = "Please configure notification sounds in your browser settings"
+ } else if (this.state.enable_sound === "false") {
describe = "Off";
} else {
describe = "On";
@@ -276,6 +279,7 @@ var NotificationsTab = React.createClass({
title="Desktop notification sounds"
describe={describe}
updateSection={function(){self.props.updateSection("sound");}}
+ disableOpen = {!this.state.soundNeeded}
/>
);
}
@@ -513,27 +517,34 @@ var SecurityTab = React.createClass({
this.setState({confirmPassword: e.target.value});
},
handleHistoryOpen: function() {
- $('#user_settings1').modal('hide');
+ this.setState({willReturn: true});
+ $("#user_settings").modal('hide');
},
handleDevicesOpen: function() {
- $('#user_settings1').modal('hide');
+ this.setState({willReturn: true});
+ $("#user_settings").modal('hide');
},
handleClose: function() {
$(this.getDOMNode()).find('.form-control').each(function() {
this.value = '';
});
this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
- this.props.updateTab('general');
+
+ if (!this.state.willReturn) {
+ this.props.updateTab('general');
+ } else {
+ this.setState({willReturn: false});
+ }
},
componentDidMount: function() {
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
getInitialState: function() {
- return {currentPassword: '', newPassword: '', confirmPassword: ''};
+ return {currentPassword: '', newPassword: '', confirmPassword: '', willReturn: false};
},
render: function() {
var serverError = this.state.serverError ? this.state.serverError : null;
@@ -811,10 +822,10 @@ var GeneralTab = React.createClass({
this.props.updateSection('');
},
componentDidMount: function() {
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
},
getInitialState: function() {
var user = this.props.user;
@@ -1093,7 +1104,7 @@ var AppearanceTab = React.createClass({
if (this.props.activeSection === "theme") {
$(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
}
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentDidUpdate: function() {
if (this.props.activeSection === "theme") {
@@ -1102,7 +1113,7 @@ var AppearanceTab = React.createClass({
}
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
getInitialState: function() {
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx
index 702e7ad7a..7181c4020 100644
--- a/web/react/components/user_settings_modal.jsx
+++ b/web/react/components/user_settings_modal.jsx
@@ -32,7 +32,7 @@ module.exports = React.createClass({
tabs.push({name: "appearance", ui_name: "Appearance", icon: "glyphicon glyphicon-wrench"});
return (
- <div className="modal fade" ref="modal" id="user_settings1" role="dialog" tabIndex="-1" aria-hidden="true">
+ <div className="modal fade" ref="modal" id="user_settings" role="dialog" tabIndex="-1" aria-hidden="true">
<div className="modal-dialog settings-modal">
<div className="modal-content">
<div className="modal-header">
@@ -64,4 +64,3 @@ module.exports = React.createClass({
);
}
});
-
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 52d537f27..0eeb5fb65 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -34,6 +34,7 @@ var ChannelInfoModal = require('../components/channel_info_modal.jsx');
var AccessHistoryModal = require('../components/access_history_modal.jsx');
var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx')
+var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
var AsyncClient = require('../utils/async_client.jsx');
@@ -225,4 +226,10 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
document.getElementById('removed_from_channel_modal')
);
+ React.render(
+ <FileUploadOverlay
+ overlayType='center' />,
+ document.getElementById('file_upload_overlay')
+ );
+
};
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index e6380d19e..3f2248c44 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -13,89 +13,94 @@ var CHANGE_EVENT = 'change';
var utils;
function getWindowLocationOrigin() {
- if (!utils) utils = require('../utils/utils.jsx');
+ if (!utils) {
+ utils = require('../utils/utils.jsx');
+ }
return utils.getWindowLocationOrigin();
}
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();
+ 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.pGetTeams();
+ return c[id];
+ },
+ getByName: function(name) {
+ var t = this.pGetTeams();
- for (id in t) {
- if (t[id].name == name) {
- return t[id];
+ for (var id in t) {
+ if (t[id].name === name) {
+ return t[id];
+ }
}
- }
- return null;
- },
- getAll: function() {
- return this._getTeams();
- },
- setCurrentId: function(id) {
- if (id == null)
- BrowserStore.removeItem("current_team_id");
- else
- BrowserStore.setItem("current_team_id", id);
- },
- getCurrentId: function() {
- return BrowserStore.getItem("current_team_id");
- },
- getCurrent: function() {
- var currentId = TeamStore.getCurrentId();
+ return null;
+ },
+ getAll: function() {
+ return this.pGetTeams();
+ },
+ setCurrentId: function(id) {
+ if (id === null) {
+ BrowserStore.removeItem('current_team_id');
+ } else {
+ BrowserStore.setItem('current_team_id', id);
+ }
+ },
+ getCurrentId: function() {
+ return BrowserStore.getItem('current_team_id');
+ },
+ getCurrent: function() {
+ var currentId = TeamStore.getCurrentId();
- if (currentId != null)
- return this.get(currentId);
- else
- return null;
- },
- getCurrentTeamUrl: function() {
- return getWindowLocationOrigin() + "/" + this.getCurrent().name;
- },
- storeTeam: function(team) {
- var teams = this._getTeams();
- teams[team.id] = team;
- this._storeTeams(teams);
- },
- _storeTeams: function(teams) {
- BrowserStore.setItem("user_teams", teams);
- },
- _getTeams: function() {
- return BrowserStore.getItem("user_teams", {});
- }
+ if (currentId !== null) {
+ return this.get(currentId);
+ }
+ return null;
+ },
+ getCurrentTeamUrl: function() {
+ if (this.getCurrent()) {
+ return getWindowLocationOrigin() + '/' + this.getCurrent().name;
+ }
+ return null;
+ },
+ storeTeam: function(team) {
+ var teams = this.pGetTeams();
+ teams[team.id] = team;
+ this.pStoreTeams(teams);
+ },
+ pStoreTeams: function(teams) {
+ BrowserStore.setItem('user_teams', teams);
+ },
+ pGetTeams: function() {
+ return BrowserStore.getItem('user_teams', {});
+ }
});
-TeamStore.dispatchToken = AppDispatcher.register(function(payload) {
- var action = payload.action;
+TeamStore.dispatchToken = AppDispatcher.register(function registry(payload) {
+ var action = payload.action;
- switch(action.type) {
+ switch (action.type) {
- case ActionTypes.CLICK_TEAM:
- TeamStore.setCurrentId(action.id);
- TeamStore.emitChange();
- break;
+ case ActionTypes.CLICK_TEAM:
+ TeamStore.setCurrentId(action.id);
+ TeamStore.emitChange();
+ break;
- case ActionTypes.RECIEVED_TEAM:
- TeamStore.storeTeam(action.team);
- TeamStore.emitChange();
- break;
+ case ActionTypes.RECIEVED_TEAM:
+ TeamStore.storeTeam(action.team);
+ TeamStore.emitChange();
+ break;
- default:
- }
+ default:
+ }
});
module.exports = TeamStore;
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index a903ca063..2312fe225 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -9,7 +9,6 @@ var ActionTypes = Constants.ActionTypes;
var AsyncClient = require('./async_client.jsx');
var client = require('./client.jsx');
var Autolinker = require('autolinker');
-var formatText = require('../../static/js/marked/lib/marked.js');
module.exports.isEmail = function(email) {
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
@@ -96,36 +95,39 @@ module.exports.getCookie = function(name) {
if (parts.length == 2) return parts.pop().split(";").shift();
}
+
module.exports.notifyMe = function(title, body, channel) {
- if ('Notification' in window && Notification.permission !== 'denied') {
- Notification.requestPermission(function(permission) {
- if (Notification.permission !== permission) {
- Notification.permission = permission;
- }
+ if ("Notification" in window && Notification.permission !== 'denied') {
+ Notification.requestPermission(function (permission) {
+ if (Notification.permission !== permission) {
+ Notification.permission = permission;
+ }
- if (permission === 'granted') {
- var notification = new Notification(title,
- {body: body, tag: body, icon: '/static/images/icon50x50.gif'}
- );
- notification.onclick = function() {
- window.focus();
- if (channel) {
- module.exports.switchChannel(channel);
- } else {
- window.location.href = '/';
- }
- };
- setTimeout(function() {
- notification.close();
- }, 5000);
- }
- });
- }
-};
+ if (permission === "granted") {
+ var notification = new Notification(title,
+ { body: body, tag: body, icon: '/static/images/icon50x50.gif' }
+ );
+ notification.onclick = function() {
+ window.focus();
+ if (channel) {
+ module.exports.switchChannel(channel);
+ } else {
+ window.location.href = "/";
+ }
+ };
+ setTimeout(function(){
+ notification.close();
+ }, 5000);
+ }
+ });
+ }
+}
module.exports.ding = function() {
- var audio = new Audio('/static/images/ding.mp3');
- audio.play();
+ if (!module.exports.isBrowserFirefox()) {
+ var audio = new Audio('/static/images/ding.mp3');
+ audio.play();
+ }
}
module.exports.getUrlParameter = function(sParam) {
@@ -385,262 +387,112 @@ module.exports.searchForTerm = function(term) {
});
}
-/* Options:
- - disable: Parses out *'s and other format specifiers in the text, but doesn't convert to html
-*/
-module.exports.customMarkedRenderer = function(options) {
- var customTextRenderer = new formatText.Renderer();
- if (options && options.disable) {
- customTextRenderer.paragraph = function(text) {
- return text + ' ';
- };
- customTextRenderer.strong = function(text) {
- return text;
- };
- customTextRenderer.em = function(text) {
- return text;
- };
- customTextRenderer.codespan = function(code) {
- return code;
- };
- customTextRenderer.link = function(href) {
- return href;
- };
- customTextRenderer.image = function(href) {
- return href;
- };
- } else {
- customTextRenderer.link = function(href) {
- return href;
- };
- customTextRenderer.image = function(href) {
- return href;
- };
- }
- return customTextRenderer;
-};
-
-var puncStartRegex = /^((?![@#])[^A-Za-z0-9_<>])+/g;
-var puncEndRegex = /([^A-Za-z0-9_<>])+$/g;
-var startTagRegex = /(<\s*\w.*?>)+/g;
-var endTagRegex = /(<\s*\/\s*\w\s*.*?>|<\s*br\s*>)+/g;
+var oldExplicitMentionRegex = /(?:<mention>)([\s\S]*?)(?:<\/mention>)/g;
+var puncStartRegex = /^((?![@#])\W)+/g;
+var puncEndRegex = /(\W)+$/g;
-module.exports.textToJsx = function(textToChange, options) {
- var useTextFormatting = config.AllowTextFormatting && (!options || !options.noTextFormatting);
- var text = textToChange;
+module.exports.textToJsx = function(text, options) {
- if (useTextFormatting) {
- text = formatText(text, {sanitize: true, mangle: false, gfm: true, breaks: true, tables: false, smartypants: true, renderer: module.exports.customMarkedRenderer()});
+ if (options && options['singleline']) {
+ var repRegex = new RegExp("\n", "g");
+ text = text.replace(repRegex, " ");
}
- if (options && options.singleline) {
- var repRegex = new RegExp('\n', 'g');
- text = text.replace(repRegex, ' ');
+ var searchTerm = ""
+ if (options && options['searchTerm']) {
+ searchTerm = options['searchTerm'].toLowerCase()
}
- var searchTerm = '';
- if (options && options.searchTerm) {
- searchTerm = options.searchTerm.toLowerCase();
- }
-
- var mentionClass = 'mention-highlight';
- if (options && options.noMentionHighlight) {
- mentionClass = '';
+ var mentionClass = "mention-highlight";
+ if (options && options['noMentionHighlight']) {
+ mentionClass = "";
}
var inner = [];
- var codeFlag = false;
- var codeString = '';
// Function specific regex
var hashRegex = /^href="#[^"]+"|(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
var implicitKeywords = UserStore.getCurrentMentionKeys();
- var lines = text.split('\n');
+ var lines = text.split("\n");
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
- var words = line.split(' ');
- var highlightSearchClass = '';
+ var words = line.split(" ");
+ var highlightSearchClass = "";
for (var z = 0; z < words.length; z++) {
var word = words[z];
- var trimWord;
- if (useTextFormatting) {
- trimWord = word.replace(endTagRegex, '').replace(startTagRegex, '').replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
- } else {
- trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
- }
+ var trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here
var explicitMention = mentionRegex.exec(trimWord);
- var mClass;
-
- var prefix = '';
- var suffix = '';
- var prefixSpan = null;
- var suffixSpan = <span key={word + i + z + 'suf_span'}> </span>;
- if (useTextFormatting) {
- if (word.match(startTagRegex)) {
- prefix += word.match(startTagRegex);
- }
- if (word.replace(startTagRegex, '').match(puncStartRegex)) {
- prefix += word.replace(startTagRegex, '').match(puncStartRegex);
- }
- if (word.match(endTagRegex)) {
- suffix += word.match(endTagRegex);
- }
- if (word.replace(endTagRegex, '').match(puncEndRegex)) {
- suffix += word.replace(endTagRegex, '').match(puncEndRegex);
- }
+ if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm != "") {
- if (prefix) {
- prefixSpan = <span key={word + i + z + 'pre_span'}><span dangerouslySetInnerHTML={{__html: prefix}} /></span>;
- }
- if (suffix) {
- suffixSpan = <span key={word + i + z + 'suf_span'}><span dangerouslySetInnerHTML={{__html: suffix}} /> </span>;
- }
- } else {
- prefix = word.match(puncStartRegex);
- suffix = word.match(puncEndRegex);
- if (prefix) {
- prefixSpan = <span key={word + i + z + 'pre_span'}>{prefix}</span>;
- }
- if (suffix) {
- suffixSpan = <span key={word + i + z + 'suf_span'}>{suffix} </span>;
- }
+ highlightSearchClass = " search-highlight";
}
- if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm !== '') {
- highlightSearchClass = ' search-highlight';
- }
-
- if (useTextFormatting && (codeFlag || word.indexOf('<code>') !== -1)) {
- codeString += word + ' ';
- codeFlag = true;
-
- if (word.indexOf('</code>') !== -1) {
- inner.push(<span key={word + i + z + '_span'} className={highlightSearchClass} dangerouslySetInnerHTML={{__html: codeString}} />);
- codeString = '';
- codeFlag = false;
- }
- } else if (explicitMention &&
- (UserStore.getProfileByUsername(explicitMention[1]) ||
- Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1)) {
+ if (explicitMention &&
+ (UserStore.getProfileByUsername(explicitMention[1]) ||
+ Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1))
+ {
var name = explicitMention[1];
-
// do both a non-case sensitive and case senstive check
- mClass = '';
- if (implicitKeywords.indexOf('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) {
- mClass = mentionClass;
- }
+ var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : "";
+
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
if (searchTerm === name) {
- highlightSearchClass = ' search-highlight';
+ highlightSearchClass = " search-highlight";
}
- if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + 'word_span'}><a className={mClass + highlightSearchClass + ' mention-link'} key={name + i + z + '_link'} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>{'@' + name}</a></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
- }
- } else {
- 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>);
- }
+ 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 (testUrlMatch(word).length) {
var match = testUrlMatch(word)[0];
var link = match.link;
- prefix = word.substring(0, word.indexOf(match.text));
- suffix = word.substring(word.indexOf(match.text) + match.text.length);
- if (prefix) {
- prefixSpan = <span key={word + i + z + 'pre_span'}><span dangerouslySetInnerHTML={{__html: prefix}} /></span>;
- }
- if (suffix) {
- suffixSpan = <span key={word + i + z + 'suf_span'}><span dangerouslySetInnerHTML={{__html: suffix}} /> </span>;
- } else {
- suffixSpan = <span key={word + i + z + 'suf_span'}> </span>;
- }
+ var prefix = word.substring(0,word.indexOf(match.text));
+ var suffix = word.substring(word.indexOf(match.text)+match.text.length);
+
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a>{suffix} </span>);
- if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + '_span'}><a key={name + i + z + '_link'} className={'theme' + highlightSearchClass} target='_blank' href={link}>{match.text}</a></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
- }
- } else {
- inner.push(<span key={word + i + z + '_span'}>{prefix}<a key={word + i + z + '_link'} className={'theme' + highlightSearchClass} target='_blank' href={link}>{match.text}</a>{suffix} </span>);
- }
} else if (trimWord.match(hashRegex)) {
- mClass = '';
- if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
- mClass = mentionClass;
- }
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
+ var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : "";
if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
- highlightSearchClass = ' search-highlight';
+ highlightSearchClass = " search-highlight";
}
- if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + '_span'}><a key={word + i + z + '_hash'} className={'theme ' + mClass + highlightSearchClass} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
- }
- } else {
- 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>);
- }
+ 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 (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
+
if (trimWord.charAt(0) === '@') {
if (searchTerm === trimWord.substring(1).toLowerCase()) {
- highlightSearchClass = ' search-highlight';
- }
- if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + '_span'}><a className={mentionClass + highlightSearchClass} key={name + i + z + '_link'} href='#'>{trimWord}</a></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
- }
- } else {
- inner.push(<span key={word + i + z + '_span'}>{prefix}<a className={mentionClass + highlightSearchClass} key={name + i + z + '_link'} href='#'>{trimWord}</a>{suffix} </span>);
- }
- } else if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + '_span'}><span className={mentionClass + highlightSearchClass}>{trimWord}</span></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
+ highlightSearchClass = " search-highlight";
}
+ inner.push(<span key={word+i+z+"_span"} key={name+i+z+"_span"}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+"_link"} href="#">{trimWord}</a>{suffix} </span>);
} else {
- inner.push(<span key={word + i + z + '_span'}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
- }
- } else if (word !== '') {
- if (useTextFormatting) {
- inner.push(<span key={word + i + z + '_span'} className={highlightSearchClass} dangerouslySetInnerHTML={{__html: word + ' '}} />);
- } else {
- inner.push(<span key={word + i + z + '_span'}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
}
+
+ } else if (word === "") {
+ // if word is empty dont include a span
+ } else {
+ inner.push(<span key={word+i+z+"_span"}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
}
- highlightSearchClass = '';
- }
- if (!useTextFormatting && i !== lines.length - 1) {
- inner.push(<br key={'br_' + i}/>);
- } else if (useTextFormatting && !codeFlag && i < lines.length - 2) {
- inner.push(<br key={'br_' + i}/>);
+ highlightSearchClass = "";
}
+ if (i != lines.length-1)
+ inner.push(<br key={"br_"+i+z}/>);
}
return inner;
-};
+}
module.exports.getFileType = function(ext) {
ext = ext.toLowerCase();
@@ -1095,3 +947,7 @@ module.exports.generateId = function() {
return id;
};
+
+module.exports.isBrowserFirefox = function() {
+ return navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+}