summaryrefslogtreecommitdiffstats
path: root/web/react/components
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2015-08-14 09:28:24 -0400
committerJoram Wilander <jwawilander@gmail.com>2015-08-14 09:28:24 -0400
commitb9aef9f2a6b90663cb7ba4ff9e42560c145b631d (patch)
tree194e31d063e31009a15b994bbc34ad6497292e17 /web/react/components
parent92c4df5b109ac8b15f6384a5c027024696bbd4d8 (diff)
parent41e74860d8abc8e33648471249b127dd3f60fb88 (diff)
downloadchat-b9aef9f2a6b90663cb7ba4ff9e42560c145b631d.tar.gz
chat-b9aef9f2a6b90663cb7ba4ff9e42560c145b631d.tar.bz2
chat-b9aef9f2a6b90663cb7ba4ff9e42560c145b631d.zip
Merge pull request #360 from rgarmsen2295/mm-375d
MM-375 Adds text formatting features using a modified version of the marked js library
Diffstat (limited to 'web/react/components')
-rw-r--r--web/react/components/channel_header.jsx2
-rw-r--r--web/react/components/create_comment.jsx7
-rw-r--r--web/react/components/create_post.jsx7
-rw-r--r--web/react/components/post_body.jsx78
-rw-r--r--web/react/components/post_list.jsx10
-rw-r--r--web/react/components/post_right.jsx31
-rw-r--r--web/react/components/search_results.jsx12
-rw-r--r--web/react/components/sidebar.jsx6
8 files changed, 113 insertions, 40 deletions
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 90a776791..4d64e2b94 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});
+ var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true, noTextFormatting: 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 78e06c532..a0a018025 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -184,6 +184,7 @@ module.exports = React.createClass({
</div>
);
}
+ var allowTextFormatting = config.AllowTextFormatting;
var postError = null;
if (this.state.postError) {
@@ -204,6 +205,10 @@ 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}>
@@ -224,7 +229,7 @@ module.exports = React.createClass({
onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError} />
</div>
- <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} />
+ {extraInfo}
<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 9ca1d5388..3e1faba7d 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -224,6 +224,7 @@ module.exports = React.createClass({
</div>
);
}
+ var allowTextFormatting = config.AllowTextFormatting;
var postError = null;
if (this.state.postError) {
@@ -244,6 +245,10 @@ 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}>
@@ -268,7 +273,7 @@ module.exports = React.createClass({
{postError}
{serverError}
{preview}
- <MsgTyping channelId={this.state.channelId} parentId=''/>
+ {extraInfo}
</div>
</div>
</form>
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 860c96d84..fab6833e6 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -4,59 +4,69 @@
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 reply = "";
- var postClass = "";
+ var comment = '';
+ 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={function(){ utils.searchForTerm(profile.username); }}>{profile.username}</a>;
+ name = <a className='theme' onClick={utils.searchForTerm.bind(this, 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';
}
}
- comment = (
- <p className="post-link">
- <span>Commented on {name}{apostrophe} message: <a className="theme" onClick={this.props.handleCommentClick}>{message}</a></span>
- </p>
- );
+ 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>
+ );
+ }
- postClass += " post-comment";
+ postClass += ' post-comment';
}
var embed;
@@ -64,18 +74,26 @@ 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 }
- <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 className='post-body'>
+ {comment}
+ {innerHolder}
+ {fileAttachmentHolder}
+ {embed}
</div>
);
}
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 83f806b79..ad7f4a8bf 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -197,7 +197,10 @@ 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);
@@ -430,8 +433,13 @@ 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={post.id}
+ <Post ref={post.id} sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={postKey}
posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment}
/>
);
diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx
index ad8b54012..19e4cf67a 100644
--- a/web/react/components/post_right.jsx
+++ b/web/react/components/post_right.jsx
@@ -56,6 +56,7 @@ 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;
@@ -76,6 +77,11 @@ 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>
@@ -101,7 +107,7 @@ RootPost = React.createClass({
</li>
</ul>
<div className="post-body">
- <p>{message}</p>
+ {messageHolder}
{ post.filenames && post.filenames.length > 0 ?
<FileAttachmentList
filenames={post.filenames}
@@ -119,6 +125,7 @@ RootPost = React.createClass({
CommentPost = React.createClass({
render: function() {
+ var allowTextFormatting = config.AllowTextFormatting;
var post = this.props.post;
var commentClass = "post";
@@ -138,6 +145,11 @@ 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">
@@ -160,7 +172,7 @@ CommentPost = React.createClass({
</li>
</ul>
<div className="post-body">
- <p>{message}</p>
+ {messageHolder}
{ post.filenames && post.filenames.length > 0 ?
<FileAttachmentList
filenames={post.filenames}
@@ -273,11 +285,22 @@ 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);
}
}
@@ -300,10 +323,10 @@ module.exports = React.createClass({
<div className="sidebar-right__body">
<RhsHeaderPost fromSearch={this.props.fromSearch} isMentionSearch={this.props.isMentionSearch} />
<div className="post-right__scroll">
- <RootPost post={root_post} commentCount={posts_array.length}/>
+ <RootPost key={rootPostKey} 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.id} post={cpost} selected={ (cpost.id == selected_post.id) } />
+ return <CommentPost ref={cpost.id} key={cpost.cpostKey} 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 643ad112b..8f6bd861a 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -84,6 +84,8 @@ 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>
@@ -99,7 +101,7 @@ var SearchItem = React.createClass({
</time>
</li>
</ul>
- <div className="search-item-snippet"><span>{message}</span></div>
+ <div key={this.props.key + searchItemKey} className="search-item-snippet"><span>{message}</span></div>
</div>
</div>
);
@@ -131,6 +133,7 @@ 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);
}
}
@@ -152,6 +155,11 @@ 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>
@@ -162,7 +170,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={post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
+ return <SearchItem key={searchItemKey + post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
}, this)
}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 80e3632c7..988ef4a9c 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -11,6 +11,7 @@ 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;
@@ -209,6 +210,11 @@ 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') {