diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/react/.eslintrc | 287 | ||||
-rw-r--r-- | web/react/components/login.jsx | 2 | ||||
-rw-r--r-- | web/react/components/post.jsx | 3 | ||||
-rw-r--r-- | web/react/components/post_info.jsx | 171 | ||||
-rw-r--r-- | web/react/components/post_list.jsx | 45 | ||||
-rw-r--r-- | web/react/package.json | 27 | ||||
-rw-r--r-- | web/react/stores/post_store.jsx | 22 | ||||
-rw-r--r-- | web/react/utils/constants.jsx | 5 |
8 files changed, 356 insertions, 206 deletions
diff --git a/web/react/.eslintrc b/web/react/.eslintrc index d8b36f6ca..cdf96905b 100644 --- a/web/react/.eslintrc +++ b/web/react/.eslintrc @@ -1,139 +1,160 @@ { - "ecmaFeatures": { - "jsx": true, - "blockBindings": true, - "modules": true - }, - "plugins": [ - "react" - ], - "env": { - "browser": true, - "node": true, - "jquery": true, - "es6": true - }, - "globals": { - "React": false - }, - "rules": { - "comma-dangle": [2, "never"], - "no-cond-assign": [2, "except-parens"], - "no-console": 1, - "no-constant-condition": 1, - "no-debugger": 1, - "no-dupe-args": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty": 1, - "no-ex-assign": 1, - "no-extra-semi": 2, - "no-func-assign": 1, - "no-inner-declarations": 0, - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-unreachable": 2, - "valid-typeof": 2, - "no-unexpected-multiline": 2, + "extends": "eslint:recommended", + "ecmaFeatures": { + "jsx": true, + "blockBindings": true, + "modules": true, + "classes": true + }, + "plugins": [ + "react" + ], + "env": { + "browser": true, + "node": true, + "jquery": true, + "es6": true + }, + "globals": { + "React": false + }, + "rules": { + "comma-dangle": [2, "never"], + "no-cond-assign": [2, "except-parens"], + "no-console": 1, + "no-constant-condition": 1, + "no-debugger": 1, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty": 1, + "no-ex-assign": 1, + "no-extra-semi": 2, + "no-func-assign": 1, + "no-inner-declarations": 0, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-unreachable": 2, + "valid-typeof": 2, + "no-unexpected-multiline": 2, - "block-scoped-var": 1, - "complexity": [1, 8], - "consistent-return": 2, - "curly": [2, "all"], - "dot-notation": 2, - "dot-location": [2, "object"], - "eqeqeq": [2, "smart"], - "guard-for-in": 1, - "no-alert": 1, - "no-caller": 2, - "no-div-regex": 1, - "no-else-return": 1, - "no-eval": 2, - "no-extend-native": 2, - "no-floating-decimal": 2, - "no-labels": 2, - "no-lone-blocks": 1, - "no-multi-spaces": [2, { "exceptions": { "Property": false } }], - "no-multi-str": 0, - "no-param-reassign": 2, - "no-process-env": 2, - "no-redeclare": 2, - "no-return-assign": [2, "always"], - "no-script-url": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-throw-literal": 2, - "no-unused-expressions": 2, - "no-void": 2, - "no-warning-comments": 0, - "no-with": 2, - "radix": 2, - "vars-on-top": 0, - "wrap-iife": [2, "outside"], - "yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}], + "block-scoped-var": 1, + "complexity": [1, 8], + "consistent-return": 2, + "curly": [2, "all"], + "dot-notation": 2, + "dot-location": [2, "object"], + "eqeqeq": [2, "smart"], + "guard-for-in": 1, + "no-alert": 1, + "no-array-constructor": 2, + "no-caller": 2, + "no-div-regex": 1, + "no-else-return": 1, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-floating-decimal": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-labels": 2, + "no-lone-blocks": 1, + "no-loop-func": 2, + "no-multi-spaces": [2, { "exceptions": { "Property": false } }], + "no-multi-str": 0, + "no-native-reassign": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-param-reassign": 2, + "no-process-env": 2, + "no-process-exit": 2, + "no-proto": 2, + "no-redeclare": 2, + "no-return-assign": [2, "always"], + "no-script-url": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-throw-literal": 2, + "no-unused-expressions": 2, + "no-undef-init": 2, + "no-void": 2, + "no-warning-comments": 0, + "no-with": 2, + "radix": 2, + "vars-on-top": 0, + "wrap-iife": [2, "outside"], + "yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}], - "no-undefined": 2, - "no-shadow": [2, {"hoist": "functions"}], - "no-unused-vars": [2, {"vars": "all", "args": "all"}], - "no-use-before-define": [2, "nofunc"], + "no-undefined": 2, + "no-shadow": [2, {"hoist": "functions"}], + "no-shadow-restricted-names": 2, + "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], + "no-use-before-define": [2, "nofunc"], - // Style - "array-bracket-spacing": [2, "never"], - "brace-style": [2, "1tbs", { "allowSingleLine": false }], - "camelcase": [2, {"properties": "always"}], - "comma-spacing": [2, {"before": false, "after": true}], - "comma-style": [2, "last"], - "computed-property-spacing": [2, "never"], - "consistent-this": [2, "self"], - "func-names": 2, - "func-style": [2, "declaration"], - "indent": [2, 4, {"indentSwitchCase": false}], - "key-spacing": [2, {"beforeColon": false, "afterColon": true}], - "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }], - "linebreak-style": 2, - "new-cap": 2, - "new-parens": 2, - "no-lonely-if": 2, - "no-mixed-spaces-and-tabs": 2, - "no-multiple-empty-lines": [2, {"max": 1}], - "no-spaced-func": 2, - "no-ternary": 2, - "no-trailing-spaces": [2, { "skipBlankLines": false }], - "no-underscore-dangle": 2, - "no-unneeded-ternary": 2, - "object-curly-spacing": [2, "never"], - "one-var": [2, "never"], - "operator-linebreak": [2, "after"], - "padded-blocks": [2, "never"], - "quote-props": [2, "as-needed"], - "quotes": [2, "single", "avoid-escape"], - "semi-spacing": [2, {"before": false, "after": true}], - "semi": [2, "always"], - "space-after-keywords": [2, "always"], - "space-before-blocks": [2, "always"], - "space-before-function-paren": [2, "never"], - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-return-throw-case": 2, - "space-unary-ops": [2, { "words": true, "nonwords": false }], - "wrap-regex": 2, + // Style + "array-bracket-spacing": [2, "never"], + "brace-style": [2, "1tbs", { "allowSingleLine": false }], + "camelcase": [2, {"properties": "always"}], + "comma-spacing": [2, {"before": false, "after": true}], + "comma-style": [2, "last"], + "computed-property-spacing": [2, "never"], + "consistent-this": [2, "self"], + "func-names": 2, + "func-style": [2, "declaration"], + "indent": [2, 4, {"SwitchCase": 0}], + "key-spacing": [2, {"beforeColon": false, "afterColon": true}], + "lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }], + "linebreak-style": 2, + "new-cap": 2, + "new-parens": 2, + "no-lonely-if": 2, + "no-mixed-spaces-and-tabs": 2, + "no-multiple-empty-lines": [2, {"max": 1}], + "no-spaced-func": 2, + "no-ternary": 2, + "no-trailing-spaces": [2, { "skipBlankLines": false }], + "no-underscore-dangle": 2, + "no-unneeded-ternary": 2, + "object-curly-spacing": [2, "never"], + "one-var": [2, "never"], + "operator-linebreak": [2, "after"], + "padded-blocks": [2, "never"], + "quote-props": [2, "as-needed"], + "quotes": [2, "single", "avoid-escape"], + "semi-spacing": [2, {"before": false, "after": true}], + "semi": [2, "always"], + "space-after-keywords": [2, "always"], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "never"], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "wrap-regex": 2, - // React Specific - "react/display-name": [2, { "acceptTranspilerName": true }], - "react/jsx-boolean-value": [2, "always"], - "react/jsx-curly-spacing": [2, "never"], - "react/jsx-no-duplicate-props": [2, { "ignoreCase": false }], - "react/jsx-no-undef": 2, - "react/jsx-quotes": [2, "single", "avoid-escape"], - "react/jsx-uses-react": 2, - "react/jsx-uses-vars": 2, - "react/no-danger": 0, - "react/no-did-mount-set-state": 2, - "react/no-did-update-set-state": 2, - "react/no-multi-comp": 2, - "react/no-unknown-property": 2, - "react/prop-types": 2, - "react/sort-comp": 0, - "react/wrap-multilines": 2 - } + // React Specific + "react/display-name": [2, { "acceptTranspilerName": true }], + "react/jsx-boolean-value": [2, "always"], + "react/jsx-curly-spacing": [2, "never"], + "react/jsx-max-props-per-line": [2, { "maximum": 1 }], + // SOON "react/jsx-indent-props": [2, 4], + "react/jsx-no-duplicate-props": [2, { "ignoreCase": false }], + "react/jsx-no-literals": 0, + "react/jsx-no-undef": 2, + "react/jsx-quotes": [2, "single", "avoid-escape"], + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/no-danger": 0, + "react/no-did-mount-set-state": 2, + "react/no-did-update-set-state": 2, + "react/no-multi-comp": 2, + "react/no-unknown-property": 2, + "react/prop-types": 2, + "react/sort-comp": 0, + "react/self-closing-comp": 2, + "react/wrap-multilines": 2 + } } diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index f9eacf094..b61ea931e 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -56,7 +56,7 @@ module.exports = React.createClass({ }, function loginFailed(err) { if (err.message === 'Login failed because email address has not been verified') { - window.location.href = '/verify_email?name=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email); + window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email); return; } state.serverError = err.message; diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index b798dc7ca..7bc6a8c01 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -3,7 +3,6 @@ var PostHeader = require('./post_header.jsx'); var PostBody = require('./post_body.jsx'); -var PostInfo = require('./post_info.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var UserStore = require('../stores/user_store.jsx'); @@ -13,6 +12,8 @@ var client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var ActionTypes = Constants.ActionTypes; +var PostInfo = require('./post_info.jsx'); + module.exports = React.createClass({ displayName: "Post", handleCommentClick: function(e) { diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index f6ab0ed8a..c96a04c7c 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -6,68 +6,145 @@ var utils = require('../utils/utils.jsx'); var Constants = require('../utils/constants.jsx'); -module.exports = React.createClass({ - getInitialState: function() { - return { }; - }, - render: function() { +export default class PostInfo extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + shouldShowComment(state, type, isOwner) { + if (state === Constants.POST_FAILED || state === Constants.POST_LOADING) { + return false; + } + return isOwner || (this.props.allowReply === 'true' && type !== 'Comment'); + } + createDropdown() { var post = this.props.post; var isOwner = UserStore.getCurrentId() === post.user_id; var isAdmin = UserStore.getCurrentUser().roles.indexOf('admin') > -1; + if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || post.state === Constants.POST_DELETED) { + return ''; + } + var type = 'Post'; if (post.root_id && post.root_id.length > 0) { type = 'Comment'; } - var comments = ''; - var lastCommentClass = ' comment-icon__container__hide'; - if (this.props.isLastComment) { - lastCommentClass = ' comment-icon__container__show'; + if (!this.shouldShowComment(post.state, type, isOwner)) { + return ''; } - if (this.props.commentCount >= 1 && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) { - comments = <a href='#' className={'comment-icon__container theme' + lastCommentClass} onClick={this.props.handleCommentClick}><span className='comment-icon' dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}} />{this.props.commentCount}</a>; + var dropdownContents = []; + var dataComments = 0; + if (type === 'Post') { + dataComments = this.props.commentCount; } - var showDropdown = isOwner || (this.props.allowReply === 'true' && type !== 'Comment'); - if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING) { - showDropdown = false; + if (isOwner) { + dropdownContents.push( + <li role='presentation'> + <a + href='#' + role='menuitem' + data-toggle='modal' + data-target='#edit_post' + data-title={type} + data-message={post.message} + data-postid={post.id} + data-channelid={post.channel_id} + data-comments={dataComments} + > + Edit + </a> + </li> + ); } - var dropdownContents = []; - var dropdown; - if (showDropdown) { - var dataComments = 0; - if (type === 'Post') { - dataComments = this.props.commentCount; - } - - if (isOwner) { - dropdownContents.push(<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#edit_post' data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id} data-comments={dataComments}>Edit</a></li>); - } + if (isOwner || isAdmin) { + dropdownContents.push( + <li role='presentation'> + <a + href='#' + role='menuitem' + data-toggle='modal' + data-target='#delete_post' + data-title={type} + data-postid={post.id} + data-channelid={post.channel_id} + data-comments={dataComments} + > + Delete + </a> + </li> + ); + } - if (isOwner || isAdmin) { - dropdownContents.push(<li role='presentation'><a href='#' role='menuitem' data-toggle='modal' data-target='#delete_post' data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={dataComments}>Delete</a></li>); - } + if (this.props.allowReply === 'true') { + dropdownContents.push( + <li role='presentation'> + <a + className='reply-link theme' + href='#' + onClick={this.props.handleCommentClick} + > + Reply + </a> + </li> + ); + } - if (this.props.allowReply === 'true') { - dropdownContents.push(<li role='presentation'><a className='reply-link theme' href='#' onClick={this.props.handleCommentClick}>Reply</a></li>); - } + return ( + <div> + <a + href='#' + className='dropdown-toggle theme' + type='button' + data-toggle='dropdown' + aria-expanded='false' + /> + <ul + className='dropdown-menu' + role='menu' + > + {dropdownContents} + </ul> + </div> + ); + } + render() { + var post = this.props.post; + var comments = ''; + var lastCommentClass = ' comment-icon__container__hide'; + if (this.props.isLastComment) { + lastCommentClass = ' comment-icon__container__show'; + } - dropdown = ( - <div> - <a href='#' className='dropdown-toggle theme' type='button' data-toggle='dropdown' aria-expanded='false' /> - <ul className='dropdown-menu' role='menu'> - {dropdownContents} - </ul> - </div> + if (this.props.commentCount >= 1 && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) { + comments = ( + <a + href='#' + className={'comment-icon__container theme' + lastCommentClass} + onClick={this.props.handleCommentClick} + > + <span + className='comment-icon' + dangerouslySetInnerHTML={{__html: Constants.COMMENT_ICON}} + /> + {this.props.commentCount} + </a> ); } + var dropdown = this.createDropdown(); + return ( <ul className='post-header post-info'> - <li className='post-header-col'><time className='post-profile-time'>{utils.displayDateTime(post.create_at)}</time></li> + <li className='post-header-col'> + <time className='post-profile-time'> + {utils.displayDateTime(post.create_at)} + </time> + </li> <li className='post-header-col post-header__reply'> <div className='dropdown'> {dropdown} @@ -77,4 +154,18 @@ module.exports = React.createClass({ </ul> ); } -}); +} + +PostInfo.defaultProps = { + post: null, + commentCount: 0, + isLastComment: false, + allowReply: false +}; +PostInfo.propTypes = { + post: React.PropTypes.object, + commentCount: React.PropTypes.number, + isLastComment: React.PropTypes.bool, + allowReply: React.PropTypes.string, + handleCommentClick: React.PropTypes.func +}; diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index bebd6847f..5fbee99f6 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -23,12 +23,31 @@ function getStateFromStores() { } var postList = PostStore.getCurrentPosts(); + var deletedPosts = PostStore.getUnseenDeletedPosts(channel.id); + + if (deletedPosts && Object.keys(deletedPosts).length > 0) { + for (var pid in deletedPosts) { + postList.posts[pid] = deletedPosts[pid]; + postList.order.unshift(pid); + } + + postList.order.sort(function postSort(a, b) { + if (postList.posts[a].create_at > postList.posts[b].create_at) { + return -1; + } + if (postList.posts[a].create_at < postList.posts[b].create_at) { + return 1; + } + return 0; + }); + } + var pendingPostList = PostStore.getPendingPosts(channel.id); if (pendingPostList) { postList.order = pendingPostList.order.concat(postList.order); - for (var pid in pendingPostList.posts) { - postList.posts[pid] = pendingPostList.posts[pid]; + for (var ppid in pendingPostList.posts) { + postList.posts[ppid] = pendingPostList.posts[ppid]; } } @@ -88,7 +107,6 @@ module.exports = React.createClass({ $('.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 resize() { $(postHolder).perfectScrollbar('update'); @@ -185,6 +203,7 @@ module.exports = React.createClass({ } } if (this.state.channel.id !== newState.channel.id) { + PostStore.clearUnseenDeletedPosts(this.state.channel.id); this.scrolledToNew = false; } this.setState(newState); @@ -220,23 +239,19 @@ module.exports = React.createClass({ activeRootPostId = activeRoot.id; } - if (this.state.channel.id === msg.channel_id) { - postList = this.state.postList; - if (!(msg.props.post_id in this.state.postList.posts)) { - return; - } + post = JSON.parse(msg.props.post); + postList = this.state.postList; + + PostStore.storeUnseenDeletedPost(post); - delete postList.posts[msg.props.post_id]; - var index = postList.order.indexOf(msg.props.post_id); + if (postList.posts[post.id]) { + delete postList.posts[post.id]; + var index = postList.order.indexOf(post.id); if (index > -1) { postList.order.splice(index, 1); } - this.setState({postList: postList}); - PostStore.storePosts(msg.channel_id, postList); - } else { - AsyncClient.getPosts(true, msg.channel_id); } if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) { @@ -318,7 +333,7 @@ module.exports = React.createClass({ var lastViewed = Number.MAX_VALUE; if (ChannelStore.getCurrentMember() != null) { - lastViewed = ChannelStore.getCurrentMember().lastViewed_at; + lastViewed = ChannelStore.getCurrentMember().last_viewed_at; } if (this.state.postList != null) { diff --git a/web/react/package.json b/web/react/package.json index 2bba29e2b..c930c4db6 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -3,22 +3,21 @@ "version": "0.0.1", "private": true, "dependencies": { - "flux": "^2.0.0", - "keymirror": "~0.1.0", - "object-assign": "^1.0.0", - "react": "^0.12.0", - "autolinker": "^0.15.2", + "autolinker": "^0.18.1", + "flux": "^2.1.1", + "keymirror": "^0.1.1", + "object-assign": "^3.0.0", + "react": "^0.13.3", "react-zeroclipboard-mixin": "^0.1.0" }, "devDependencies": { - "browserify": "^6.2.0", - "envify": "^3.0.0", - "jest-cli": "~0.1.17", - "reactify": "^0.15.2", - "uglify-js": "~2.4.15", - "watchify": "^2.1.1", - "eslint": "^0.24.1", - "eslint-plugin-react": "^3.0.0" + "browserify": "^11.0.1", + "envify": "^3.4.0", + "babelify": "^6.1.3", + "uglify-js": "^2.4.24", + "watchify": "^3.3.1", + "eslint": "^1.1.0", + "eslint-plugin-react": "^3.2.3" }, "scripts": { "start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx", @@ -28,7 +27,7 @@ }, "browserify": { "transform": [ - "reactify", + ["babelify", {"blacklist": ["strict"]}], "envify" ] }, diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 3e4fde30a..2fffb17d0 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -172,6 +172,28 @@ var PostStore = assign({}, EventEmitter.prototype, { getPendingPosts: function(channelId) { return BrowserStore.getItem('pending_posts_' + channelId); }, + storeUnseenDeletedPost: function(post) { + var posts = this.getUnseenDeletedPosts(post.channel_id); + + if (!posts) { + posts = {}; + } + + post.message = '(message deleted)'; + post.state = Constants.POST_DELETED; + + posts[post.id] = post; + this.storeUnseenDeletedPosts(post.channel_id, posts); + }, + storeUnseenDeletedPosts: function(channelId, posts) { + BrowserStore.setItem('deleted_posts_' + channelId, posts); + }, + getUnseenDeletedPosts: function(channelId) { + return BrowserStore.getItem('deleted_posts_' + channelId); + }, + clearUnseenDeletedPosts: function(channelId) { + BrowserStore.setItem('deleted_posts_' + channelId, {}); + }, removePendingPost: function(channelId, pendingPostId) { this._removePendingPost(channelId, pendingPostId); this.emitChange(); diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 41b02c8d6..8239a4a69 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -63,8 +63,9 @@ module.exports = { GOOGLE_SERVICE: 'google', POST_CHUNK_SIZE: 60, MAX_POST_CHUNKS: 3, - POST_LOADING: "loading", - POST_FAILED: "failed", + POST_LOADING: 'loading', + POST_FAILED: 'failed', + POST_DELETED: 'deleted', RESERVED_TEAM_NAMES: [ "www", "web", |