diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 0585941653..6fb473ae68 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -109,6 +109,7 @@ export default class CreateComment extends React.Component { post.pending_post_id = `${userId}:${time}`; post.user_id = userId; post.create_at = time; + post.attachments = []; PostStore.storePendingPost(post); PostStore.storeCommentDraft(this.props.rootId, null); diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 5a69c9bfba..90ba477188 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -173,6 +173,7 @@ export default class CreatePost extends React.Component { post.create_at = time; post.root_id = this.state.rootId; post.parent_id = this.state.parentId; + post.attachments = []; const channel = ChannelStore.get(this.state.channelId); diff --git a/web/react/components/post_attachment.jsx b/web/react/components/post_attachment.jsx new file mode 100644 index 0000000000..6bc9ade476 --- /dev/null +++ b/web/react/components/post_attachment.jsx @@ -0,0 +1,240 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +const TextFormatting = require('../utils/text_formatting.jsx'); + +export default class PostAttachment extends React.Component { + constructor(props) { + super(props); + + this.getFieldsTable = this.getFieldsTable.bind(this); + } + + getFieldsTable() { + const fields = this.props.attachment.fields; + if (!fields || !fields.length) { + return ''; + } + + const compactTable = fields.filter((field) => field.short).length > 0; + let tHead; + let tBody; + + if (compactTable) { + let headerCols = []; + let bodyCols = []; + + fields.forEach((field, i) => { + headerCols.push( + + {field.title} + + ); + bodyCols.push( + + + ); + }); + + tHead = ( + + {headerCols} + + ); + tBody = ( + + {bodyCols} + + ); + } else { + tBody = []; + + fields.forEach((field, i) => { + tBody.push( + + + {field.title} + + + + + ); + }); + } + + return ( + + + {tHead} + + + {tBody} + +
+ ); + } + + render() { + const data = this.props.attachment; + + let preText; + if (data.pretext) { + preText = ( +
+
+ ); + } + + let author = []; + if (data.author_name || data.author_icon) { + if (data.author_icon) { + author.push( + + ); + } + if (data.author_name) { + author.push( + + {data.author_name} + + ); + } + } + if (data.author_link) { + author = ( + + {author} + + ); + } + + let title; + if (data.title) { + if (data.title_link) { + title = ( +

+ + {data.title} + +

+ ); + } else { + title = ( +

+ {data.title} +

+ ); + } + } + + let text; + if (data.text) { + text = ( +
+
+ ); + } + + let image; + if (data.image_url) { + image = ( + + ); + } + + let thumb; + if (data.thumb_url) { + thumb = ( +
+ +
+ ); + } + + const fields = this.getFieldsTable(); + + let useBorderStyle; + if (data.color && data.color[0] === '#') { + useBorderStyle = {borderLeftColor: data.color}; + } + + return ( +
+ {preText} +
+
+ {author} + {title} +
+
+ {text} + {image} + {fields} +
+ {thumb} +
+
+
+
+
+ ); + } +} + +PostAttachment.propTypes = { + attachment: React.PropTypes.object.isRequired +}; \ No newline at end of file diff --git a/web/react/components/post_attachment_list.jsx b/web/react/components/post_attachment_list.jsx new file mode 100644 index 0000000000..03b8666561 --- /dev/null +++ b/web/react/components/post_attachment_list.jsx @@ -0,0 +1,32 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +const PostAttachment = require('./post_attachment.jsx'); + +export default class PostAttachmentList extends React.Component { + constructor(props) { + super(props); + } + + render() { + let content = []; + this.props.attachments.forEach((attachment, i) => { + content.push( + + ); + }); + + return ( +
+ {content} +
+ ); + } +} + +PostAttachmentList.propTypes = { + attachments: React.PropTypes.array.isRequired +}; diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index e4094daf3d..4da13dace2 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -7,6 +7,7 @@ const Utils = require('../utils/utils.jsx'); const Constants = require('../utils/constants.jsx'); const TextFormatting = require('../utils/text_formatting.jsx'); const twemoji = require('twemoji'); +const PostAttachmentList = require('./post_attachment_list.jsx'); export default class PostBody extends React.Component { constructor(props) { @@ -316,6 +317,15 @@ export default class PostBody extends React.Component { ); } + let postAttachments = ''; + if (post.attachments && post.attachments.length) { + postAttachments = ( + + ); + } + return (
{comment} @@ -331,6 +341,7 @@ export default class PostBody extends React.Component { dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.state.message)}} />
+ {postAttachments} {fileAttachmentHolder} {embed} diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 9ce7ca1e8b..f006956bc7 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -481,6 +481,7 @@ export function applyTheme(theme) { changeCss('.modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1); changeCss('#navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1); changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarHeaderBg, 1); + changeCss('.attachment .attachment__container', 'border-left-color:' + theme.sidebarHeaderBg, 1); } if (theme.sidebarHeaderTextColor) { @@ -519,6 +520,7 @@ export function applyTheme(theme) { changeCss('.popover.left>.arrow:after', 'border-left-color:' + theme.centerChannelBg, 1); changeCss('.popover.top>.arrow:after, .tip-overlay.tip-overlay--chat .arrow', 'border-top-color:' + theme.centerChannelBg, 1); changeCss('.search-bar__container .search__form .search-bar, .form-control', 'background:' + theme.centerChannelBg, 1); + changeCss('.attachment__content', 'background:' + theme.centerChannelBg, 1); } if (theme.centerChannelColor) { @@ -552,6 +554,7 @@ export function applyTheme(theme) { changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1); changeCss('.input-group-addon, .search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); + changeCss('.attachment .attachment__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); changeCss('.channel-intro .channel-intro__content, .webhooks__container', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2); changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index ef19ac601c..dd88e6e167 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -610,3 +610,74 @@ body.ios { font-weight: 600; margin: 0 0 0 -4px; } + +.attachment { + .attachment__content { + border-width: 1px; + border-style: solid; + border-radius: 4px; + padding: 2px 5px; + margin: 0 0 5px 0; + } + .attachment__thumb-pretext { + border: 0 none; + background: transparent; + } + .attachment__container { + border-left-width: 4px; + border-left-style: solid; + padding: 2px 0 2px 10px; + &.attachment__container--good { + border-left-color: #00C100; + } + &.attachment__container--warning { + border-left-color: #DEDE01; + } + &.attachment__container--danger { + border-left-color: #E40303; + } + } + .attachment__body { + float: left; + width: 80%; + padding-right: 5px; + &.attachment__body--no_thumb { + width: 100%; + } + } + .attachment__thumb-pretext { + margin-left: 5px; + } + .attachment__title { + margin: 5px 0; + padding: 0; + line-height: 16px; + font-size: 16px; + a { + font-size: 16px; + } + } + .attachment__author-icon { + @include border-radius(50px); + margin-right: 5px; + width: 14px; + height: 14px; + } + .attachment__image { + max-width: 100%; + } + .attachment__thumb-container { + width: 20%; + float: right; + img { + height: 75px; + max-width: 100%; + } + } + .attachment___fields { + width: 100%; + .attachment___field-caption { + font-weight: 700; + } + } +} \ No newline at end of file