From 27a8169ae8dd41eb5a2b8a9b11e69fc4b922b115 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Wed, 21 Oct 2015 13:55:41 -0700 Subject: [PATCH 1/3] Changed post list resize code to use react state; fixed various eslint warnings --- web/react/components/post_list.jsx | 89 +++++++++++++++++------------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 4402745e1b..5a4e8abaf9 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -12,7 +12,7 @@ const UserStore = require('../stores/user_store.jsx'); const SocketStore = require('../stores/socket_store.jsx'); const PreferenceStore = require('../stores/preference_store.jsx'); -const utils = require('../utils/utils.jsx'); +const Utils = require('../utils/utils.jsx'); const Client = require('../utils/client.jsx'); const Constants = require('../utils/constants.jsx'); const ActionTypes = Constants.ActionTypes; @@ -40,11 +40,13 @@ export default class PostList extends React.Component { this.loadFirstPosts = this.loadFirstPosts.bind(this); this.activate = this.activate.bind(this); this.deactivate = this.deactivate.bind(this); - this.resize = this.resize.bind(this); + this.handleResize = this.handleResize.bind(this); + this.resizePostList = this.resizePostList.bind(this); const state = this.getStateFromStores(props.channelId); state.numToDisplay = Constants.POST_CHUNK_SIZE; state.isFirstLoadComplete = false; + state.windowHeight = Utils.windowHeight(); this.state = state; } @@ -115,12 +117,7 @@ export default class PostList extends React.Component { const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); - $(window).resize(() => { - this.resize(); - if (!this.scrolled) { - this.scrollToBottom(); - } - }); + window.addEventListener('resize', this.handleResize); postHolder.on('scroll', () => { const position = postHolder.scrollTop() + postHolder.height() + 14; @@ -154,7 +151,7 @@ export default class PostList extends React.Component { this.loadFirstPosts(this.props.channelId); } - this.resize(); + this.handleResize(); this.onChange(); this.scrollToBottom(); } @@ -164,7 +161,9 @@ export default class PostList extends React.Component { SocketStore.removeChangeListener(this.onSocketChange); PreferenceStore.removeChangeListener(this.onTimeChange); $('body').off('click.userpopover'); - $(window).off('resize'); + + window.removeEventListener('resize', this.handleResize); + var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); postHolder.off('scroll'); } @@ -202,7 +201,7 @@ export default class PostList extends React.Component { // it's by the user and not a comment } else if (isNewPost && userId === firstPost.user_id && - !utils.isComment(firstPost)) { + !Utils.isComment(firstPost)) { this.scrollToBottom(true); // the user clicked 'load more messages' @@ -219,6 +218,10 @@ export default class PostList extends React.Component { } else { this.scrollTo(this.prevScrollTop); } + + if (prevState.windowHeight !== this.state.windowHeight) { + this.handleResize(); + } } componentWillUpdate() { var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); @@ -231,10 +234,20 @@ export default class PostList extends React.Component { this.deactivate(); } } - resize() { + handleResize() { + this.setState({ + windowHeight: Utils.windowHeight() + }); + + this.resizePostList(); + if (!this.scrolled) { + this.scrollToBottom(); + } + } + resizePostList() { const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); if ($('#create_post').length > 0) { - const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50; + const height = this.state.windowHeight - $('#create_post').height() - $('#error_bar').outerHeight() - 50; postHolder.css('height', height + 'px'); } } @@ -280,7 +293,7 @@ export default class PostList extends React.Component { onChange() { var newState = this.getStateFromStores(this.props.channelId); - if (!utils.areStatesEqual(newState.postList, this.state.postList)) { + if (!Utils.areStatesEqual(newState.postList, this.state.postList)) { this.setState(newState); } } @@ -310,7 +323,7 @@ export default class PostList extends React.Component { } } createDMIntroMessage(channel) { - var teammate = utils.getDirectTeammate(channel.id); + var teammate = Utils.getDirectTeammate(channel.id); if (teammate) { var teammateName = teammate.username; @@ -370,13 +383,13 @@ export default class PostList extends React.Component { createDefaultIntroMessage(channel) { return (
-

Beginning of {channel.display_name}

+

{'Beginning of ' + channel.display_name}

- Welcome to {channel.display_name}! + {'Welcome to ' + channel.display_name + '!'}

- This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know. + {'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'}

- To create a new channel or join an existing one, go to the Left Sidebar under “Channels” and click “More…”. + {'To create a new channel or join an existing one, go to the Left Sidebar under “Channels” and click “More…”.'}

@@ -385,7 +398,7 @@ export default class PostList extends React.Component { createOffTopicIntroMessage(channel) { return (
-

Beginning of {channel.display_name}

+

{'Beginning of ' + channel.display_name}

{'This is the start of ' + channel.display_name + ', a channel for non-work-related conversations.'}
@@ -399,7 +412,7 @@ export default class PostList extends React.Component { data-title={channel.display_name} data-channelid={channel.id} > - Set a description + {'Set a description'} - Invite others to this channel + {'Invite others to this channel'}

); @@ -422,7 +435,7 @@ export default class PostList extends React.Component { var members = ChannelStore.getExtraInfo(channel.id).members; for (var i = 0; i < members.length; i++) { - if (utils.isAdmin(members[i].roles)) { + if (Utils.isAdmin(members[i].roles)) { return members[i].username; } } @@ -443,14 +456,14 @@ export default class PostList extends React.Component { var createMessage; if (creatorName === '') { - createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + utils.displayDate(channel.create_at) + '.'; + createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + Utils.displayDate(channel.create_at) + '.'; } else { - createMessage = (This is the start of the {uiName} {uiType}, created by {creatorName} on {utils.displayDate(channel.create_at)}); + createMessage = (This is the start of the {uiName} {uiType}, created by {creatorName} on {Utils.displayDate(channel.create_at)}); } return (
-

Beginning of {uiName}

+

{'Beginning of ' + uiName}

{createMessage} {memberMessage} @@ -465,7 +478,7 @@ export default class PostList extends React.Component { data-title={channel.display_name} data-channelid={channel.id} > - Set a description + {'Set a description'} - Invite others to this {uiType} + {'Invite others to this ' + uiType}

); @@ -507,7 +520,7 @@ export default class PostList extends React.Component { if (prevPost) { sameUser = prevPost.user_id === post.user_id && post.create_at - prevPost.create_at <= 1000 * 60 * 5; - sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id); + sameRoot = Utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id); // hide the profile pic if: // the previous post was made by the same user as the current post, @@ -516,8 +529,8 @@ export default class PostList extends React.Component { // the current post is not from a webhook // and the previous post is not from a webhook if ((prevPost.user_id === post.user_id) && - !utils.isComment(prevPost) && - !utils.isComment(post) && + !Utils.isComment(prevPost) && + !Utils.isComment(post) && (!post.props || !post.props.from_webhook) && (!prevPost.props || !prevPost.props.from_webhook)) { hideProfilePic = true; @@ -526,7 +539,7 @@ export default class PostList extends React.Component { // check if it's the last comment in a consecutive string of comments on the same post // 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 isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id); var postCtl = ( ); - let currentPostDay = utils.getDateForUnixTicks(post.create_at); + const currentPostDay = Utils.getDateForUnixTicks(post.create_at); if (currentPostDay.toDateString() !== previousPostDay.toDateString()) { postCtls.push(
lastViewed && !renderedLastViewed) { renderedLastViewed = true; - // Temporary fix to solve ie10/11 rendering issue + // Temporary fix to solve ie11 rendering issue let newSeparatorId = ''; - if (!utils.isBrowserIE()) { + if (!Utils.isBrowserIE()) { newSeparatorId = 'new_message_' + this.props.channelId; } postCtls.push( @@ -572,7 +585,7 @@ export default class PostList extends React.Component {
-
New Messages
+
{'New Messages'}
); } @@ -638,7 +651,7 @@ export default class PostList extends React.Component { order = this.state.postList.order; } - var moreMessages =

Beginning of Channel

; + var moreMessages =

{'Beginning of Channel'}

; if (channel != null) { if (order.length >= this.state.numToDisplay) { moreMessages = ( @@ -648,7 +661,7 @@ export default class PostList extends React.Component { href='#' onClick={this.loadMorePosts} > - Load more messages + {'Load more messages'} ); } else { From 4129b204c3f6d76154f98ed715c11508d5348cc9 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Wed, 21 Oct 2015 15:08:04 -0700 Subject: [PATCH 2/3] Fixed typos in create_post.jsx resizing code --- web/react/components/create_post.jsx | 6 +++--- web/react/components/post_list.jsx | 18 ++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 0358995929..8b5fc4162b 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -51,7 +51,7 @@ export default class CreatePost extends React.Component { submitting: false, initialText: draft.messageText, windowWidth: Utils.windowWidth(), - windowHeigth: Utils.windowHeight() + windowHeight: Utils.windowHeight() }; } handleResize() { @@ -71,7 +71,7 @@ export default class CreatePost extends React.Component { return; } - if (prevState.windowWidth !== this.state.windowWidth || prevState.windowHeight !== this.state.windowHeigth) { + if (prevState.windowWidth !== this.state.windowWidth || prevState.windowHeight !== this.state.windowHeight) { this.resizePostHolder(); return; } @@ -208,7 +208,7 @@ export default class CreatePost extends React.Component { PostStore.storeCurrentDraft(draft); } resizePostHolder() { - const height = this.state.windowHeigth - $(ReactDOM.findDOMNode(this.refs.topDiv)).height() - 50; + const height = this.state.windowHeight - $(ReactDOM.findDOMNode(this.refs.topDiv)).height() - 50; $('.post-list-holder-by-time').css('height', `${height}px`); if (this.state.windowWidth > 960) { $('#post_textbox').focus(); diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 5a4e8abaf9..b7df483e90 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -151,7 +151,7 @@ export default class PostList extends React.Component { this.loadFirstPosts(this.props.channelId); } - this.handleResize(); + this.resizePostList(); this.onChange(); this.scrollToBottom(); } @@ -172,6 +172,13 @@ export default class PostList extends React.Component { return; } + if (prevState.windowHeight !== this.state.windowHeight) { + this.resizePostList(); + if (!this.scrolled) { + this.scrollToBottom(); + } + } + $('.post-list__content div .post').removeClass('post--last'); $('.post-list__content div:last-child .post').addClass('post--last'); @@ -218,10 +225,6 @@ export default class PostList extends React.Component { } else { this.scrollTo(this.prevScrollTop); } - - if (prevState.windowHeight !== this.state.windowHeight) { - this.handleResize(); - } } componentWillUpdate() { var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); @@ -238,11 +241,6 @@ export default class PostList extends React.Component { this.setState({ windowHeight: Utils.windowHeight() }); - - this.resizePostList(); - if (!this.scrolled) { - this.scrollToBottom(); - } } resizePostList() { const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); From 5ecf45e109a5910959e0cc6927b826ce5a406a7d Mon Sep 17 00:00:00 2001 From: it33 Date: Thu, 22 Oct 2015 16:20:35 -0700 Subject: [PATCH 3/3] Create API.md --- doc/developer/API.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 doc/developer/API.md diff --git a/doc/developer/API.md b/doc/developer/API.md new file mode 100644 index 0000000000..6327f1173b --- /dev/null +++ b/doc/developer/API.md @@ -0,0 +1,35 @@ +# Mattermost APIs + +Mattermost APIs let you integrate your favorite tools and services withing your Mattermost experience. + +## Slack-compatible integration support + +To offer an alternative to propreitary SaaS services, Mattermost focuses on being "Slack-compatible, but not Slack limited". That means providing support for developers of Slack applications to easily extend their apps to Mattermost, as well as support and capabilities beyond what Slack offers. + +### [Incoming Webhooks](https://github.com/mattermost/platform/blob/master/doc/integrations/webhooks/Incoming-Webhooks.md) + +Incoming webhooks allow external applications to post messages into Mattermost channels and private groups by sending a JSON payload via HTTP POST request to a secret Mattermost URL generated specifically for each application. + +In addition to supporting Slack's incoming webhook formatting, Mattermost webhooks offer full support of industry-standard markdown formatting, including headings, tables and in-line images. + +### Outgoing Webhooks (coming in Mattermost v1.2) + +Outgoing webhooks allow external applications to receive webhook events from events happening within Mattermost channels and private groups via JSON payloads via HTTP POST requests sent to incoming webhook URLs defined by your applications. + +Over time, Mattermost outgoing webhooks will support not only Slack applications using a compatible format, but also offer optional events and triggers beyond Slack's feature set. + +## Mattermost Drivers + +Mattermost is written in Golang and React and designed as a self-hosted system, which differs from Slack's technical platform and focus on SaaS. Therefore the Mattermost drivers will differ from Slack's interfaces. + +Another key difference is that as an open source project, you are welcome to access and use Mattermost's APIs on your installations the same way the core team would use them for buildling new features. + +While detailed documentation of the interfaces is pending, if you want to build deep integrations with Mattermost there are two drivers at the heart of the system: + +### [ReactJS Javascript Driver](https://github.com/mattermost/platform/blob/master/web/react/utils/client.jsx) + +[client.jsx](https://github.com/mattermost/platform/blob/master/web/react/utils/client.jsx) - This Javascript driver connects with the ReactJS components of Mattermost. The web client does the vast majority of its work by connecting to a RESTful JSON web service. There is a very small amount of processing for error checking and set up that happens on the web server. + +### [Golang Driver](https://github.com/mattermost/platform/blob/master/model/client.go) + +[client.go](https://github.com/mattermost/platform/blob/master/model/client.go) - This is a RESTful driver connecting with the Golang-based webservice of Mattermost and is used by unit tests.