Replies in the compose form
This commit is contained in:
		
							parent
							
								
									1e0e17ba85
								
							
						
					
					
						commit
						dbae8062f4
					
				
					 15 changed files with 257 additions and 307 deletions
				
			
		
							
								
								
									
										241
									
								
								.eslintrc
									
										
									
									
									
								
							
							
						
						
									
										241
									
								
								.eslintrc
									
										
									
									
									
								
							|  | @ -5,238 +5,15 @@ | ||||||
|     "es6": true |     "es6": true | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   "plugins": ["react"], |   "plugins": [ | ||||||
|  |     "react" | ||||||
|  |   ], | ||||||
| 
 | 
 | ||||||
|   "ecmaFeatures": { |   "parserOptions": { | ||||||
|     "arrowFunctions": true, |     "sourceType": "module", | ||||||
|     "binaryLiterals": true, | 
 | ||||||
|     "blockBindings": true, |     "ecmaFeatures": { | ||||||
|     "classes": true, |       "jsx": true | ||||||
|     "defaultParams": true, |     }, | ||||||
|     "destructuring": true, |  | ||||||
|     "forOf": true, |  | ||||||
|     "generators": true, |  | ||||||
|     "modules": true, |  | ||||||
|     "objectLiteralComputedProperties": true, |  | ||||||
|     "objectLiteralDuplicateProperties": true, |  | ||||||
|     "objectLiteralShorthandMethods": true, |  | ||||||
|     "objectLiteralShorthandProperties": true, |  | ||||||
|     "octalLiterals": true, |  | ||||||
|     "regexUFlag": true, |  | ||||||
|     "regexYFlag": true, |  | ||||||
|     "spread": true, |  | ||||||
|     "superInFunctions": true, |  | ||||||
|     "templateStrings": true, |  | ||||||
|     "unicodeCodePointEscapes": true, |  | ||||||
|     "globalReturn": true, |  | ||||||
|     "jsx": true |  | ||||||
|   }, |   }, | ||||||
| 
 |  | ||||||
|   "rules": { |  | ||||||
| 
 |  | ||||||
|     // |  | ||||||
|     //Possible Errors |  | ||||||
|     // |  | ||||||
|     // The following rules point out areas where you might have made mistakes. |  | ||||||
|     // |  | ||||||
|     "comma-dangle": 2, // disallow or enforce trailing commas |  | ||||||
|     "no-cond-assign": 2, // disallow assignment in conditional expressions |  | ||||||
|     "no-console": 1, // disallow use of console (off by default in the node environment) |  | ||||||
|     "no-constant-condition": 2, // disallow use of constant expressions in conditions |  | ||||||
|     "no-control-regex": 2, // disallow control characters in regular expressions |  | ||||||
|     "no-debugger": 2, // disallow use of debugger |  | ||||||
|     "no-dupe-args": 2, // disallow duplicate arguments in functions |  | ||||||
|     "no-dupe-keys": 2, // disallow duplicate keys when creating object literals |  | ||||||
|     "no-duplicate-case": 2, // disallow a duplicate case label. |  | ||||||
|     "no-empty": 2, // disallow empty statements |  | ||||||
|     "no-empty-class": 2, // disallow the use of empty character classes in regular expressions |  | ||||||
|     "no-ex-assign": 2, // disallow assigning to the exception in a catch block |  | ||||||
|     "no-extra-boolean-cast": 2, // disallow double-negation boolean casts in a boolean context |  | ||||||
|     "no-extra-parens": 0, // disallow unnecessary parentheses (off by default) |  | ||||||
|     "no-extra-semi": 2, // disallow unnecessary semicolons |  | ||||||
|     "no-func-assign": 2, // disallow overwriting functions written as function declarations |  | ||||||
|     "no-inner-declarations": 2, // disallow function or variable declarations in nested blocks |  | ||||||
|     "no-invalid-regexp": 2, // disallow invalid regular expression strings in the RegExp constructor |  | ||||||
|     "no-irregular-whitespace": 2, // disallow irregular whitespace outside of strings and comments |  | ||||||
|     "no-negated-in-lhs": 2, // disallow negation of the left operand of an in expression |  | ||||||
|     "no-obj-calls": 2, // disallow the use of object properties of the global object (Math and JSON) as functions |  | ||||||
|     "no-regex-spaces": 2, // disallow multiple spaces in a regular expression literal |  | ||||||
|     "no-reserved-keys": 2, // disallow reserved words being used as object literal keys (off by default) |  | ||||||
|     "no-sparse-arrays": 2, // disallow sparse arrays |  | ||||||
|     "no-unreachable": 2, // disallow unreachable statements after a return, throw, continue, or break statement |  | ||||||
|     "use-isnan": 2, // disallow comparisons with the value NaN |  | ||||||
|     "valid-jsdoc": 2, // Ensure JSDoc comments are valid (off by default) |  | ||||||
|     "valid-typeof": 2, // Ensure that the results of typeof are compared against a valid string |  | ||||||
| 
 |  | ||||||
|     // |  | ||||||
|     // Best Practices |  | ||||||
|     // |  | ||||||
|     // These are rules designed to prevent you from making mistakes. |  | ||||||
|     // They either prescribe a better way of doing something or help you avoid footguns. |  | ||||||
|     // |  | ||||||
|     "block-scoped-var": 0, // treat var statements as if they were block scoped (off by default). 0: deep destructuring is not compatible https://github.com/eslint/eslint/issues/1863 |  | ||||||
|     "complexity": 0, // specify the maximum cyclomatic complexity allowed in a program (off by default) |  | ||||||
|     "consistent-return": 2, // require return statements to either always or never specify values |  | ||||||
|     "curly": 2, // specify curly brace conventions for all control statements |  | ||||||
|     "default-case": 2, // require default case in switch statements (off by default) |  | ||||||
|     "dot-notation": 2, // encourages use of dot notation whenever possible |  | ||||||
|     "eqeqeq": 2, // require the use of === and !== |  | ||||||
|     "guard-for-in": 2, // make sure for-in loops have an if statement (off by default) |  | ||||||
|     "no-alert": 2, // disallow the use of alert, confirm, and prompt |  | ||||||
|     "no-caller": 2, // disallow use of arguments.caller or arguments.callee |  | ||||||
|     "no-div-regex": 2, // disallow division operators explicitly at beginning of regular expression (off by default) |  | ||||||
|     "no-else-return": 2, // disallow else after a return in an if (off by default) |  | ||||||
|     "no-empty-label": 2, // disallow use of labels for anything other then loops and switches |  | ||||||
|     "no-eq-null": 2, // disallow comparisons to null without a type-checking operator (off by default) |  | ||||||
|     "no-eval": 2, // disallow use of eval() |  | ||||||
|     "no-extend-native": 2, // disallow adding to native types |  | ||||||
|     "no-extra-bind": 2, // disallow unnecessary function binding |  | ||||||
|     "no-fallthrough": 2, // disallow fallthrough of case statements |  | ||||||
|     "no-floating-decimal": 2, // disallow the use of leading or trailing decimal points in numeric literals (off by default) |  | ||||||
|     "no-implied-eval": 2, // disallow use of eval()-like methods |  | ||||||
|     "no-iterator": 2, // disallow usage of __iterator__ property |  | ||||||
|     "no-labels": 2, // disallow use of labeled statements |  | ||||||
|     "no-lone-blocks": 2, // disallow unnecessary nested blocks |  | ||||||
|     "no-loop-func": 2, // disallow creation of functions within loops |  | ||||||
|     "no-multi-spaces": 2, // disallow use of multiple spaces |  | ||||||
|     "no-multi-str": 2, // disallow use of multiline strings |  | ||||||
|     "no-native-reassign": 2, // disallow reassignments of native objects |  | ||||||
|     "no-new": 2, // disallow use of new operator when not part of the assignment or comparison |  | ||||||
|     "no-new-func": 2, // disallow use of new operator for Function object |  | ||||||
|     "no-new-wrappers": 2, // disallows creating new instances of String,Number, and Boolean |  | ||||||
|     "no-octal": 2, // disallow use of octal literals |  | ||||||
|     "no-octal-escape": 2, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; |  | ||||||
|     "no-param-reassign": 2, // disallow reassignment of function parameters (off by default) |  | ||||||
|     "no-process-env": 2, // disallow use of process.env (off by default) |  | ||||||
|     "no-proto": 2, // disallow usage of __proto__ property |  | ||||||
|     "no-redeclare": 2, // disallow declaring the same variable more then once |  | ||||||
|     "no-return-assign": 2, // disallow use of assignment in return statement |  | ||||||
|     "no-script-url": 2, // disallow use of javascript: urls. |  | ||||||
|     "no-self-compare": 2, // disallow comparisons where both sides are exactly the same (off by default) |  | ||||||
|     "no-sequences": 2, // disallow use of comma operator |  | ||||||
|     "no-throw-literal": 2, // restrict what can be thrown as an exception (off by default) |  | ||||||
|     "no-unused-expressions": 2, // disallow usage of expressions in statement position |  | ||||||
|     "no-void": 2, // disallow use of void operator (off by default) |  | ||||||
|     "no-warning-comments": [0, {"terms": ["todo", "fixme"], "location": "start"}], // disallow usage of configurable warning terms in comments": 2, // e.g. TODO or FIXME (off by default) |  | ||||||
|     "no-with": 2, // disallow use of the with statement |  | ||||||
|     "radix": 2, // require use of the second argument for parseInt() (off by default) |  | ||||||
|     "vars-on-top": 2, // requires to declare all vars on top of their containing scope (off by default) |  | ||||||
|     "wrap-iife": 2, // require immediate function invocation to be wrapped in parentheses (off by default) |  | ||||||
|     "yoda": 2, // require or disallow Yoda conditions |  | ||||||
| 
 |  | ||||||
|     // |  | ||||||
|     // Strict Mode |  | ||||||
|     // |  | ||||||
|     // These rules relate to using strict mode. |  | ||||||
|     // |  | ||||||
|     "strict": 0, // controls location of Use Strict Directives. 0: required by `babel-eslint` |  | ||||||
| 
 |  | ||||||
|     // |  | ||||||
|     // Variables |  | ||||||
|     // |  | ||||||
|     // These rules have to do with variable declarations. |  | ||||||
|     // |  | ||||||
|     "no-catch-shadow": 2, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment) |  | ||||||
|     "no-delete-var": 2, // disallow deletion of variables |  | ||||||
|     "no-label-var": 2, // disallow labels that share a name with a variable |  | ||||||
|     "no-shadow": 2, // disallow declaration of variables already declared in the outer scope |  | ||||||
|     "no-shadow-restricted-names": 2, // disallow shadowing of names such as arguments |  | ||||||
|     "no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block |  | ||||||
|     "no-undef-init": 2, // disallow use of undefined when initializing variables |  | ||||||
|     "no-undefined": 2, // disallow use of undefined variable (off by default) |  | ||||||
|     "no-unused-vars": 2, // disallow declaration of variables that are not used in the code |  | ||||||
|     "no-use-before-define": 2, // disallow use of variables before they are defined |  | ||||||
| 
 |  | ||||||
|     // |  | ||||||
|     //Stylistic Issues |  | ||||||
|     // |  | ||||||
|     // These rules are purely matters of style and are quite subjective. |  | ||||||
|     // |  | ||||||
|     "indent": [1, 2], // this option sets a specific tab width for your code (off by default) |  | ||||||
|     "brace-style": 1, // enforce one true brace style (off by default) |  | ||||||
|     "camelcase": 1, // require camel case names |  | ||||||
|     "comma-spacing": [1, {"before": false, "after": true}], // enforce spacing before and after comma |  | ||||||
|     "comma-style": [1, "last"], // enforce one true comma style (off by default) |  | ||||||
|     "consistent-this": [1, "_this"], // enforces consistent naming when capturing the current execution context (off by default) |  | ||||||
|     "eol-last": 1, // enforce newline at the end of file, with no multiple empty lines |  | ||||||
|     "func-names": 0, // require function expressions to have a name (off by default) |  | ||||||
|     "func-style": 0, // enforces use of function declarations or expressions (off by default) |  | ||||||
|     "key-spacing": [1, {"beforeColon": false, "afterColon": true}], // enforces spacing between keys and values in object literal properties |  | ||||||
|     "max-nested-callbacks": [1, 3], // specify the maximum depth callbacks can be nested (off by default) |  | ||||||
|     "new-cap": [1, {newIsCap: true, capIsNew: false}], // require a capital letter for constructors |  | ||||||
|     "new-parens": 1, // disallow the omission of parentheses when invoking a constructor with no arguments |  | ||||||
|     "newline-after-var": 0, // allow/disallow an empty newline after var statement (off by default) |  | ||||||
|     "no-array-constructor": 1, // disallow use of the Array constructor |  | ||||||
|     "no-inline-comments": 1, // disallow comments inline after code (off by default) |  | ||||||
|     "no-lonely-if": 1, // disallow if as the only statement in an else block (off by default) |  | ||||||
|     "no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation |  | ||||||
|     "no-multiple-empty-lines": [1, {"max": 2}], // disallow multiple empty lines (off by default) |  | ||||||
|     "no-nested-ternary": 1, // disallow nested ternary expressions (off by default) |  | ||||||
|     "no-new-object": 1, // disallow use of the Object constructor |  | ||||||
|     "no-spaced-func": 1, // disallow space between function identifier and application |  | ||||||
|     "no-ternary": 0, // disallow the use of ternary operators (off by default) |  | ||||||
|     "no-trailing-spaces": 1, // disallow trailing whitespace at the end of lines |  | ||||||
|     "no-underscore-dangle": 1, // disallow dangling underscores in identifiers |  | ||||||
|     "no-wrap-func": 1, // disallow wrapping of non-IIFE statements in parens |  | ||||||
|     "one-var": [1, "never"], // allow just one var statement per function (off by default) |  | ||||||
|     "operator-assignment": [1, "never"], // require assignment operator shorthand where possible or prohibit it entirely (off by default) |  | ||||||
|     "padded-blocks": [1, "never"], // enforce padding within blocks (off by default) |  | ||||||
|     "quote-props": [1, "as-needed"], // require quotes around object literal property names (off by default) |  | ||||||
|     "quotes": [1, "single"], // specify whether double or single quotes should be used |  | ||||||
|     "semi": [1, "always"], // require or disallow use of semicolons instead of ASI |  | ||||||
|     "semi-spacing": [1, {"before": false, "after": true}], // enforce spacing before and after semicolons |  | ||||||
|     "sort-vars": 0, // sort variables within the same declaration block (off by default) |  | ||||||
|     "space-after-keywords": [1, "always"], // require a space after certain keywords (off by default) |  | ||||||
|     "space-before-blocks": [1, "always"], // require or disallow space before blocks (off by default) |  | ||||||
|     "space-before-function-paren": [1, {"anonymous": "always", "named": "never"}], // require or disallow space before function opening parenthesis (off by default) |  | ||||||
|     "space-in-brackets": [1, "never"], // require or disallow spaces inside brackets (off by default) |  | ||||||
|     "space-in-parens": [1, "never"], // require or disallow spaces inside parentheses (off by default) |  | ||||||
|     "space-infix-ops": [1, "always"], // require spaces around operators |  | ||||||
|     "space-return-throw-case": [1, "always"], // require a space after return, throw, and case |  | ||||||
|     "space-unary-ops": [1, {"words": true, "nonwords": false}], // Require or disallow spaces before/after unary operators (words on by default, nonwords off by default) |  | ||||||
|     "spaced-line-comment": [1, "always"], // require or disallow a space immediately following the // in a line comment (off by default) |  | ||||||
|     "wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default) |  | ||||||
| 
 |  | ||||||
|     // |  | ||||||
|     // ECMAScript 6 |  | ||||||
|     // |  | ||||||
|     // These rules are only relevant to ES6 environments and are off by default. |  | ||||||
|     // |  | ||||||
|     "no-var": 2, // require let or const instead of var (off by default) |  | ||||||
|     "generator-star-spacing": [2, "before"], // enforce the spacing around the * in generator functions (off by default) |  | ||||||
| 
 |  | ||||||
|     // |  | ||||||
|     // Legacy |  | ||||||
|     // |  | ||||||
|     // The following rules are included for compatibility with JSHint and JSLint. |  | ||||||
|     // While the names of the rules may not match up with the JSHint/JSLint counterpart, |  | ||||||
|     // the functionality is the same. |  | ||||||
|     // |  | ||||||
|     "max-depth": [2, 3], // specify the maximum depth that blocks can be nested (off by default) |  | ||||||
|     "max-len": [2, 100, 2], // specify the maximum length of a line in your program (off by default) |  | ||||||
|     "max-params": [2, 5], // limits the number of parameters that can be used in the function declaration. (off by default) |  | ||||||
|     "max-statements": 0, // specify the maximum number of statement allowed in a function (off by default) |  | ||||||
|     "no-bitwise": 0, // disallow use of bitwise operators (off by default) |  | ||||||
|     "no-plusplus": 2, // disallow use of unary operators, ++ and -- (off by default) |  | ||||||
| 
 |  | ||||||
|     // |  | ||||||
|     // eslint-plugin-react |  | ||||||
|     // |  | ||||||
|     // React specific linting rules for ESLint |  | ||||||
|     // |  | ||||||
|     "react/display-name": 0, // Prevent missing displayName in a React component definition |  | ||||||
|     "react/jsx-quotes": [2, "double", "avoid-escape"], // Enforce quote style for JSX attributes |  | ||||||
|     "react/jsx-no-undef": 2, // Disallow undeclared variables in JSX |  | ||||||
|     "react/jsx-sort-props": 0, // Enforce props alphabetical sorting |  | ||||||
|     "react/jsx-uses-react": 2, // Prevent React to be incorrectly marked as unused |  | ||||||
|     "react/jsx-uses-vars": 2, // Prevent variables used in JSX to be incorrectly marked as unused |  | ||||||
|     "react/no-did-mount-set-state": 2, // Prevent usage of setState in componentDidMount |  | ||||||
|     "react/no-did-update-set-state": 2, // Prevent usage of setState in componentDidUpdate |  | ||||||
|     "react/no-multi-comp": 0, // Prevent multiple component definition per file |  | ||||||
|     "react/no-unknown-property": 2, // Prevent usage of unknown DOM property |  | ||||||
|     "react/prop-types": 2, // Prevent missing props validation in a React component definition |  | ||||||
|     "react/react-in-jsx-scope": 2, // Prevent missing React when using JSX |  | ||||||
|     "react/self-closing-comp": 2, // Prevent extra closing tags for components without children |  | ||||||
|     "react/wrap-multilines": 2, // Prevent missing parentheses around multilines JSX |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,8 @@ export const COMPOSE_SUBMIT         = 'COMPOSE_SUBMIT'; | ||||||
| export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST'; | export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST'; | ||||||
| export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; | export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; | ||||||
| export const COMPOSE_SUBMIT_FAIL    = 'COMPOSE_SUBMIT_FAIL'; | export const COMPOSE_SUBMIT_FAIL    = 'COMPOSE_SUBMIT_FAIL'; | ||||||
|  | export const COMPOSE_REPLY          = 'COMPOSE_REPLY'; | ||||||
|  | export const COMPOSE_REPLY_CANCEL   = 'COMPOSE_REPLY_CANCEL'; | ||||||
| 
 | 
 | ||||||
| export function changeCompose(text) { | export function changeCompose(text) { | ||||||
|   return { |   return { | ||||||
|  | @ -13,13 +15,26 @@ export function changeCompose(text) { | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function replyCompose(payload) { | ||||||
|  |   return { | ||||||
|  |     type: COMPOSE_REPLY, | ||||||
|  |     payload: payload | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function cancelReplyCompose() { | ||||||
|  |   return { | ||||||
|  |     type: COMPOSE_REPLY_CANCEL | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function submitCompose() { | export function submitCompose() { | ||||||
|   return function (dispatch, getState) { |   return function (dispatch, getState) { | ||||||
|     dispatch(submitComposeRequest()); |     dispatch(submitComposeRequest()); | ||||||
| 
 | 
 | ||||||
|     api(getState).post('/api/statuses', { |     api(getState).post('/api/statuses', { | ||||||
|       status: getState().getIn(['compose', 'text'], ''), |       status: getState().getIn(['compose', 'text'], ''), | ||||||
|       in_reply_to_id: getState().getIn(['compose', 'in_reply_to_id'], null) |       in_reply_to_id: getState().getIn(['compose', 'in_reply_to', 'id'], null) | ||||||
|     }).then(function (response) { |     }).then(function (response) { | ||||||
|       dispatch(submitComposeSuccess(response.data)); |       dispatch(submitComposeSuccess(response.data)); | ||||||
|     }).catch(function (error) { |     }).catch(function (error) { | ||||||
|  |  | ||||||
|  | @ -3,15 +3,16 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||||
| const Avatar = React.createClass({ | const Avatar = React.createClass({ | ||||||
| 
 | 
 | ||||||
|   propTypes: { |   propTypes: { | ||||||
|     src: React.PropTypes.string.isRequired |     src: React.PropTypes.string.isRequired, | ||||||
|  |     size: React.PropTypes.number.isRequired | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   mixins: [PureRenderMixin], |   mixins: [PureRenderMixin], | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ width: '48px', height: '48px', flex: '0 0 auto' }}> |       <div style={{ width: `${this.props.size}px`, height: `${this.props.size}px` }}> | ||||||
|         <img src={this.props.src} width={48} height={48} alt='' style={{ display: 'block', borderRadius: '4px' }} /> |         <img src={this.props.src} width={this.props.size} height={this.props.size} alt='' style={{ display: 'block', borderRadius: '4px' }} /> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -5,14 +5,12 @@ const Button = React.createClass({ | ||||||
|   propTypes: { |   propTypes: { | ||||||
|     text: React.PropTypes.string.isRequired, |     text: React.PropTypes.string.isRequired, | ||||||
|     onClick: React.PropTypes.func, |     onClick: React.PropTypes.func, | ||||||
|     disabled: React.PropTypes.boolean |     disabled: React.PropTypes.bool | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   mixins: [PureRenderMixin], |   mixins: [PureRenderMixin], | ||||||
| 
 | 
 | ||||||
|   handleClick (e) { |   handleClick (e) { | ||||||
|     e.preventDefault(); |  | ||||||
| 
 |  | ||||||
|     if (!this.props.disabled) { |     if (!this.props.disabled) { | ||||||
|       this.props.onClick(); |       this.props.onClick(); | ||||||
|     } |     } | ||||||
|  | @ -20,9 +18,9 @@ const Button = React.createClass({ | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     return ( |     return ( | ||||||
|       <a href='#' onClick={this.handleClick} style={{ display: 'inline-block', position: 'relative', boxSizing: 'border-box', textAlign: 'center', border: '10px none', backgroundColor: '#2b90d9', color: '#fff', fontSize: '14px', fontWeight: '500', letterSpacing: '0', textTransform: 'uppercase', padding: '0 16px', height: '36px', cursor: 'pointer', lineHeight: '36px', borderRadius: '4px', textDecoration: 'none' }}> |       <button className='button' disabled={this.props.disabled} onClick={this.handleClick} style={{ display: 'inline-block', position: 'relative', boxSizing: 'border-box', textAlign: 'center', border: '10px none', color: '#fff', fontSize: '14px', fontWeight: '500', letterSpacing: '0', textTransform: 'uppercase', padding: '0 16px', height: '36px', cursor: 'pointer', lineHeight: '36px', borderRadius: '4px', textDecoration: 'none' }}> | ||||||
|         {this.props.text} |         {this.props.text} | ||||||
|       </a> |       </button> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,14 +1,18 @@ | ||||||
| import CharacterCounter from './character_counter'; | import CharacterCounter   from './character_counter'; | ||||||
| import Button           from './button'; | import Button             from './button'; | ||||||
| import PureRenderMixin  from 'react-addons-pure-render-mixin'; | import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||||
|  | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
|  | import ReplyIndicator     from './reply_indicator'; | ||||||
| 
 | 
 | ||||||
| const ComposerDrawer = React.createClass({ | const ComposerDrawer = React.createClass({ | ||||||
| 
 | 
 | ||||||
|   propTypes: { |   propTypes: { | ||||||
|     text: React.PropTypes.string.isRequired, |     text: React.PropTypes.string.isRequired, | ||||||
|     isSubmitting: React.PropTypes.boolean, |     is_submitting: React.PropTypes.bool, | ||||||
|  |     in_reply_to: ImmutablePropTypes.map, | ||||||
|     onChange: React.PropTypes.func.isRequired, |     onChange: React.PropTypes.func.isRequired, | ||||||
|     onSubmit: React.PropTypes.func.isRequired |     onSubmit: React.PropTypes.func.isRequired, | ||||||
|  |     onCancelReply: React.PropTypes.func.isRequired | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   mixins: [PureRenderMixin], |   mixins: [PureRenderMixin], | ||||||
|  | @ -28,9 +32,17 @@ const ComposerDrawer = React.createClass({ | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|  |     let replyArea = ''; | ||||||
|  | 
 | ||||||
|  |     if (this.props.in_reply_to) { | ||||||
|  |       replyArea = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ width: '280px', boxSizing: 'border-box', background: '#454b5e', margin: '10px', marginRight: '0', padding: '10px' }}> |       <div style={{ width: '280px', boxSizing: 'border-box', background: '#454b5e', margin: '10px', marginRight: '0', padding: '10px' }}> | ||||||
|         <textarea disabled={this.props.isSubmitting} placeholder='What is on your mind?' value={this.props.text} onKeyUp={this.handleKeyUp} onChange={this.handleChange} style={{ display: 'block', boxSizing: 'border-box', width: '100%', height: '100px', background: '#fff', resize: 'none', border: 'none', color: '#282c37', padding: '10px', fontFamily: 'Roboto', fontSize: '14px' }} /> |         {replyArea} | ||||||
|  | 
 | ||||||
|  |         <textarea disabled={this.props.isSubmitting} placeholder='What is on your mind?' value={this.props.text} onKeyUp={this.handleKeyUp} onChange={this.handleChange} className='compose-drawer__textarea' style={{ display: 'block', boxSizing: 'border-box', width: '100%', height: '100px', resize: 'none', border: 'none', color: '#282c37', padding: '10px', fontFamily: 'Roboto', fontSize: '14px', margin: '0' }} /> | ||||||
| 
 | 
 | ||||||
|         <div style={{ marginTop: '10px', overflow: 'hidden' }}> |         <div style={{ marginTop: '10px', overflow: 'hidden' }}> | ||||||
|           <div style={{ float: 'right' }}><Button text='Publish' onClick={this.handleSubmit} disabled={this.props.isSubmitting} /></div> |           <div style={{ float: 'right' }}><Button text='Publish' onClick={this.handleSubmit} disabled={this.props.isSubmitting} /></div> | ||||||
|  |  | ||||||
|  | @ -1,26 +0,0 @@ | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import PureRenderMixin    from 'react-addons-pure-render-mixin'; |  | ||||||
| 
 |  | ||||||
| const DisplayName = React.createClass({ |  | ||||||
| 
 |  | ||||||
|   propTypes: { |  | ||||||
|     account: ImmutablePropTypes.map.isRequired |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   mixins: [PureRenderMixin], |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     var displayName = this.props.account.get('display_name', this.props.account.get('username')); |  | ||||||
|     var acct        = this.props.account.get('acct'); |  | ||||||
|     var url         = this.props.account.get('url'); |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|       <a href={url} style={{ display: 'inline-block', color: '#616b86', textDecoration: 'none', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', width: '190px' }}> |  | ||||||
|         <strong style={{ fontWeight: 'bold', color: '#fff' }}>{displayName}</strong> <span>{acct}</span> |  | ||||||
|       </a> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| export default DisplayName; |  | ||||||
							
								
								
									
										35
									
								
								app/assets/javascripts/components/components/icon_button.jsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/assets/javascripts/components/components/icon_button.jsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||||
|  | 
 | ||||||
|  | const IconButton = React.createClass({ | ||||||
|  | 
 | ||||||
|  |   propTypes: { | ||||||
|  |     title: React.PropTypes.string.isRequired, | ||||||
|  |     icon: React.PropTypes.string.isRequired, | ||||||
|  |     onClick: React.PropTypes.func.isRequired, | ||||||
|  |     size: React.PropTypes.number | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   getDefaultProps () { | ||||||
|  |     return { | ||||||
|  |       size: 18 | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   mixins: [PureRenderMixin], | ||||||
|  | 
 | ||||||
|  |   handleClick (e) { | ||||||
|  |     e.preventDefault(); | ||||||
|  |     this.props.onClick(); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     return ( | ||||||
|  |       <a href='#' title={this.props.title} className='icon-button' onClick={this.handleClick} style={{ display: 'inline-block', fontSize: `${this.props.size}px`, width: `${this.props.size}px`, height: `${this.props.size}px`, lineHeight: `${this.props.size}px`}}> | ||||||
|  |         <i className={`fa fa-fw fa-${this.props.icon}`}></i> | ||||||
|  |       </a> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default IconButton; | ||||||
|  | @ -48,7 +48,7 @@ const RelativeTimestamp = React.createClass({ | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     return ( |     return ( | ||||||
|       <span style={{ color: '#616b86' }}> |       <span> | ||||||
|         {this.state.text} |         {this.state.text} | ||||||
|       </span> |       </span> | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||||
|  | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
|  | import Avatar             from './avatar'; | ||||||
|  | import IconButton         from './icon_button'; | ||||||
|  | 
 | ||||||
|  | const ReplyIndicator = React.createClass({ | ||||||
|  | 
 | ||||||
|  |   propTypes: { | ||||||
|  |     status: ImmutablePropTypes.map.isRequired, | ||||||
|  |     onCancel: React.PropTypes.func.isRequired | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   mixins: [PureRenderMixin], | ||||||
|  | 
 | ||||||
|  |   handleClick () { | ||||||
|  |     this.props.onCancel(); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     let content = { __html: this.props.status.get('content') }; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div style={{ background: '#9baec8', padding: '10px' }}> | ||||||
|  |         <div style={{ overflow: 'hidden', marginBottom: '5px' }}> | ||||||
|  |           <div style={{ float: 'right', lineHeight: '24px' }}><IconButton title='Cancel' icon='times' onClick={this.handleClick} /></div> | ||||||
|  | 
 | ||||||
|  |           <a href={this.props.status.getIn(['account', 'url'])} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#282c37', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}> | ||||||
|  |             <div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={this.props.status.getIn(['account', 'avatar'])} /></div> | ||||||
|  |             <strong style={{ fontWeight: '500' }}>{this.props.status.getIn(['account', 'display_name'])}</strong> <span>@{this.props.status.getIn(['account', 'acct'])}</span> | ||||||
|  |           </a> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div className='reply-indicator__content' dangerouslySetInnerHTML={content} /> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default ReplyIndicator; | ||||||
|  | @ -1,35 +1,50 @@ | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import Avatar             from './avatar'; | import Avatar             from './avatar'; | ||||||
| import DisplayName        from './display_name'; |  | ||||||
| import RelativeTimestamp  from './relative_timestamp'; | import RelativeTimestamp  from './relative_timestamp'; | ||||||
| import PureRenderMixin    from 'react-addons-pure-render-mixin'; | import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||||
|  | import IconButton         from './icon_button'; | ||||||
| 
 | 
 | ||||||
| const Status = React.createClass({ | const Status = React.createClass({ | ||||||
| 
 | 
 | ||||||
|   propTypes: { |   propTypes: { | ||||||
|     status: ImmutablePropTypes.map.isRequired |     status: ImmutablePropTypes.map.isRequired, | ||||||
|  |     onReply: React.PropTypes.func | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   mixins: [PureRenderMixin], |   mixins: [PureRenderMixin], | ||||||
| 
 | 
 | ||||||
|  |   handleReplyClick () { | ||||||
|  |     this.props.onReply(this.props.status); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|   render () { |   render () { | ||||||
|     var content = { __html: this.props.status.get('content') }; |     var content = { __html: this.props.status.get('content') }; | ||||||
|     var status  = this.props.status; |     var status  = this.props.status; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ padding: '8px 10px', display: 'flex', flexDirection: 'row', borderBottom: '1px solid #363c4b', cursor: 'pointer' }}> |       <div style={{ padding: '8px 10px', paddingLeft: '68px', position: 'relative', minHeight: '48px', borderBottom: '1px solid #363c4b', cursor: 'pointer' }}> | ||||||
|         <Avatar src={status.getIn(['account', 'avatar'])} /> |         <div style={{ fontSize: '15px' }}> | ||||||
| 
 |           <div style={{ float: 'right' }}> | ||||||
|         <div style={{ flex: '1 1 auto', marginLeft: '10px' }}> |             <a href={status.get('url')} className='status__relative-time' style={{ color: '#616b86' }}><RelativeTimestamp timestamp={status.get('created_at')} /></a> | ||||||
|           <div style={{ overflow: 'hidden', fontSize: '15px' }}> |  | ||||||
|             <div style={{ float: 'right' }}> |  | ||||||
|               <a href={status.get('url')} style={{ textDecoration: 'none' }}><RelativeTimestamp timestamp={status.get('created_at')} /></a> |  | ||||||
|             </div> |  | ||||||
| 
 |  | ||||||
|             <DisplayName account={status.get('account')} /> |  | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|           <div className='status__content' dangerouslySetInnerHTML={content} /> |           <a href={status.getIn(['account', 'url'])} className='status__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#616b86' }}> | ||||||
|  |             <div style={{ position: 'absolute', left: '10px', top: '10px', width: '48px', height: '48px' }}> | ||||||
|  |               <Avatar src={status.getIn(['account', 'avatar'])} size={48} /> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <span style={{ display: 'inline-block', maxWidth: '100%', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}> | ||||||
|  |               <strong style={{ fontWeight: 'bold', color: '#fff' }}>{status.getIn(['account', 'display_name'])}</strong> <span>@{status.getIn(['account', 'acct'])}</span> | ||||||
|  |             </span> | ||||||
|  |           </a> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div className='status__content' dangerouslySetInnerHTML={content} /> | ||||||
|  | 
 | ||||||
|  |         <div style={{ marginTop: '10px', overflow: 'hidden' }}> | ||||||
|  |           <div style={{ float: 'left', marginRight: '10px'}}><IconButton title='Reply' icon='reply' onClick={this.handleReplyClick} /></div> | ||||||
|  |           <div style={{ float: 'left', marginRight: '10px'}}><IconButton title='Reblog' icon='retweet' /></div> | ||||||
|  |           <div style={{ float: 'left'}}><IconButton title='Favourite' icon='star' /></div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ const StatusList = React.createClass({ | ||||||
|       <div style={{ overflowY: 'scroll', flex: '1 1 auto' }}> |       <div style={{ overflowY: 'scroll', flex: '1 1 auto' }}> | ||||||
|         <div> |         <div> | ||||||
|           {this.props.statuses.map((status) => { |           {this.props.statuses.map((status) => { | ||||||
|             return <Status key={status.get('id')} status={status} />; |             return <Status key={status.get('id')} status={status} onReply={this.props.onReply} />; | ||||||
|           })} |           })} | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
| import { connect }                      from 'react-redux'; | import { connect }                                          from 'react-redux'; | ||||||
| import ComposerDrawer                   from '../components/composer_drawer'; | import ComposerDrawer                                       from '../components/composer_drawer'; | ||||||
| import { changeCompose, submitCompose } from '../actions/compose'; | import { changeCompose, submitCompose, cancelReplyCompose } from '../actions/compose'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = function (state, props) { | const mapStateToProps = function (state, props) { | ||||||
|   return { |   return { | ||||||
|     text: state.getIn(['compose', 'text']), |     text: state.getIn(['compose', 'text']), | ||||||
|     isSubmitting: state.getIn(['compose', 'isSubmitting']) |     is_submitting: state.getIn(['compose', 'is_submitting']), | ||||||
|  |     in_reply_to: state.getIn(['compose', 'in_reply_to']) | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -17,6 +18,10 @@ const mapDispatchToProps = function (dispatch) { | ||||||
| 
 | 
 | ||||||
|     onSubmit: function () { |     onSubmit: function () { | ||||||
|       dispatch(submitCompose()); |       dispatch(submitCompose()); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     onCancelReply: function () { | ||||||
|  |       dispatch(cancelReplyCompose()); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| import { connect } from 'react-redux'; | import { connect }      from 'react-redux'; | ||||||
| import StatusList  from '../components/status_list'; | import StatusList       from '../components/status_list'; | ||||||
|  | import { replyCompose } from '../actions/compose'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = function (state, props) { | const mapStateToProps = function (state, props) { | ||||||
|   return { |   return { | ||||||
|  | @ -7,4 +8,12 @@ const mapStateToProps = function (state, props) { | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps)(StatusList); | const mapDispatchToProps = function (dispatch) { | ||||||
|  |   return { | ||||||
|  |     onReply: function (status) { | ||||||
|  |       dispatch(replyCompose(status)); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, mapDispatchToProps)(StatusList); | ||||||
|  |  | ||||||
|  | @ -1,24 +1,32 @@ | ||||||
| import { COMPOSE_CHANGE, COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_SUCCESS, COMPOSE_SUBMIT_FAIL } from '../actions/compose'; | import * as constants from '../actions/compose'; | ||||||
| import Immutable                                                                               from 'immutable'; | import Immutable                                                                                              from 'immutable'; | ||||||
| 
 | 
 | ||||||
| const initialState = Immutable.Map({ | const initialState = Immutable.Map({ | ||||||
|   text: '', |   text: '', | ||||||
|   in_reply_to_id: null, |   in_reply_to: null, | ||||||
|   isSubmitting: false |   is_submitting: false | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default function compose(state = initialState, action) { | export default function compose(state = initialState, action) { | ||||||
|   switch(action.type) { |   switch(action.type) { | ||||||
|     case COMPOSE_CHANGE: |     case constants.COMPOSE_CHANGE: | ||||||
|       return state.set('text', action.text); |       return state.set('text', action.text); | ||||||
|     case COMPOSE_SUBMIT_REQUEST: |     case constants.COMPOSE_REPLY: | ||||||
|       return state.set('isSubmitting', true); |  | ||||||
|     case COMPOSE_SUBMIT_SUCCESS: |  | ||||||
|       return state.withMutations(map => { |       return state.withMutations(map => { | ||||||
|         map.set('text', '').set('isSubmitting', false); |         map.set('in_reply_to', action.payload).set('text', `@${action.payload.getIn(['account', 'acct'])} `); | ||||||
|       }); |       }); | ||||||
|     case COMPOSE_SUBMIT_FAIL: |     case constants.COMPOSE_REPLY_CANCEL: | ||||||
|       return state.set('isSubmitting', false); |       return state.withMutations(map => { | ||||||
|  |         map.set('in_reply_to', null).set('text', ''); | ||||||
|  |       }); | ||||||
|  |     case constants.COMPOSE_SUBMIT_REQUEST: | ||||||
|  |       return state.set('is_submitting', true); | ||||||
|  |     case constants.COMPOSE_SUBMIT_SUCCESS: | ||||||
|  |       return state.withMutations(map => { | ||||||
|  |         map.set('text', '').set('is_submitting', false),set('in_reply_to', null); | ||||||
|  |       }); | ||||||
|  |     case constants.COMPOSE_SUBMIT_FAIL: | ||||||
|  |       return state.set('is_submitting', false); | ||||||
|     default: |     default: | ||||||
|       return state; |       return state; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,38 @@ | ||||||
| .status__content { | .button { | ||||||
|  |   background-color: #2b90d9; | ||||||
|  | 
 | ||||||
|  |   &:hover { | ||||||
|  |     background-color: #489fde; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &:disabled { | ||||||
|  |     background-color: #9baec8; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .icon-button { | ||||||
|  |   color: #616b86; | ||||||
|  |   cursor: pointer; | ||||||
|  | 
 | ||||||
|  |   &:hover { | ||||||
|  |     color: #717b98; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &.disabled { | ||||||
|  |     color: #535b72; | ||||||
|  |     cursor: default; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .compose-drawer__textarea { | ||||||
|  |   background: #fff; | ||||||
|  | 
 | ||||||
|  |   &:disabled { | ||||||
|  |     background: #d9e1e8; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .status__content, .reply-indicator__content { | ||||||
|   font-size: 15px; |   font-size: 15px; | ||||||
|   line-height: 20px; |   line-height: 20px; | ||||||
|   white-space: pre-wrap; |   white-space: pre-wrap; | ||||||
|  | @ -24,3 +58,30 @@ | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .reply-indicator__content { | ||||||
|  |   color: #282c37; | ||||||
|  |   font-size: 14px; | ||||||
|  | 
 | ||||||
|  |   a { | ||||||
|  |     color: #535b72; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .status__display-name, .status__relative-time { | ||||||
|  |   text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .status__display-name, .reply-indicator__display-name { | ||||||
|  |   &:hover { | ||||||
|  |     strong { | ||||||
|  |       text-decoration: underline; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .status__relative-time { | ||||||
|  |   &:hover { | ||||||
|  |     text-decoration: underline; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue