followers.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import React from 'react';
  2. import Button from 'misago/components/button'; // jshint ignore:line
  3. import Search from 'misago/components/quick-search'; // jshint ignore:line
  4. import UsersList from 'misago/components/users-list/root'; // jshint ignore:line
  5. import misago from 'misago/index';
  6. import { hydrate, append } from 'misago/reducers/users'; // jshint ignore:line
  7. import ajax from 'misago/services/ajax';
  8. import snackbar from 'misago/services/snackbar';
  9. import store from 'misago/services/store';
  10. import title from 'misago/services/page-title';
  11. export default class extends React.Component {
  12. constructor(props) {
  13. super(props);
  14. this.setSpecialProps();
  15. if (misago.has(this.PRELOADED_DATA_KEY)) {
  16. this.initWithPreloadedData(misago.pop(this.PRELOADED_DATA_KEY));
  17. } else {
  18. this.initWithoutPreloadedData();
  19. }
  20. }
  21. setSpecialProps() {
  22. this.PRELOADED_DATA_KEY = 'PROFILE_FOLLOWERS';
  23. this.TITLE = gettext('Followers');
  24. this.API_FILTER = 'followers';
  25. }
  26. initWithPreloadedData(data) {
  27. this.state = {
  28. isLoaded: true,
  29. isBusy: false,
  30. search: '',
  31. count: data.count,
  32. more: data.more,
  33. page: data.page,
  34. pages: data.pages
  35. };
  36. store.dispatch(hydrate(data.results));
  37. }
  38. initWithoutPreloadedData() {
  39. this.state = {
  40. isLoaded: false,
  41. isBusy: false,
  42. search: '',
  43. count: 0,
  44. more: 0,
  45. page: 1,
  46. pages: 1
  47. };
  48. this.loadUsers();
  49. }
  50. loadUsers(page=1, search=null) {
  51. const apiUrl = this.props.profile.api_url[this.API_FILTER];
  52. ajax.get(apiUrl, {
  53. search: search,
  54. page: page || 1
  55. }, 'user-' + this.API_FILTER).then((data) => {
  56. if (page === 1) {
  57. store.dispatch(hydrate(data.results));
  58. } else {
  59. store.dispatch(append(data.results));
  60. }
  61. this.setState({
  62. isLoaded: true,
  63. isBusy: false,
  64. count: data.count,
  65. more: data.more,
  66. page: data.page,
  67. pages: data.pages
  68. });
  69. }, (rejection) => {
  70. snackbar.apiError(rejection);
  71. });
  72. }
  73. componentDidMount() {
  74. title.set({
  75. title: this.TITLE,
  76. parent: this.props.profile.username
  77. });
  78. }
  79. /* jshint ignore:start */
  80. loadMore = () => {
  81. this.setState({
  82. isBusy: true
  83. });
  84. this.loadUsers(this.state.page + 1, this.state.search);
  85. };
  86. search = (ev) => {
  87. this.setState({
  88. isLoaded: false,
  89. isBusy: true,
  90. search: ev.target.value,
  91. count: 0,
  92. more: 0,
  93. page: 1,
  94. pages: 1
  95. });
  96. this.loadUsers(1, ev.target.value);
  97. };
  98. /* jshint ignore:end */
  99. getLabel() {
  100. if (!this.state.isLoaded) {
  101. return gettext('Loading...');
  102. } else if (this.state.search) {
  103. let message = ngettext(
  104. "Found %(users)s user.",
  105. "Found %(users)s users.",
  106. this.state.count);
  107. return interpolate(message, {
  108. 'users': this.state.count
  109. }, true);
  110. } else if (this.props.profile.id === this.props.user.id) {
  111. let message = ngettext(
  112. "You have %(users)s follower.",
  113. "You have %(users)s followers.",
  114. this.state.count);
  115. return interpolate(message, {
  116. 'users': this.state.count
  117. }, true);
  118. } else {
  119. let message = ngettext(
  120. "%(username)s has %(users)s follower.",
  121. "%(username)s has %(users)s followers.",
  122. this.state.count);
  123. return interpolate(message, {
  124. 'username': this.props.profile.username,
  125. 'users': this.state.count
  126. }, true);
  127. }
  128. }
  129. getEmptyMessage() {
  130. if (this.state.search) {
  131. return gettext("Search returned no users matching specified criteria.");
  132. } else if (this.props.user.id === this.props.profile.id) {
  133. return gettext("You have no followers.");
  134. } else {
  135. return interpolate(gettext("%(username)s has no followers."), {
  136. 'username': this.props.profile.username
  137. }, true);
  138. }
  139. }
  140. getMoreButton() {
  141. if (this.state.more) {
  142. /* jshint ignore:start */
  143. return <div className="pager-more">
  144. <Button loading={this.state.isBusy}
  145. onClick={this.loadMore}>
  146. {interpolate(gettext("Show more (%(more)s)"), {
  147. 'more': this.state.more
  148. }, true)}
  149. </Button>
  150. </div>;
  151. /* jshint ignore:end */
  152. } else {
  153. return null;
  154. }
  155. }
  156. getListBody() {
  157. if (this.state.isLoaded && this.state.count === 0) {
  158. /* jshint ignore:start */
  159. return <p className="lead">
  160. {this.getEmptyMessage()}
  161. </p>;
  162. /* jshint ignore:end */
  163. } else {
  164. /* jshint ignore:start */
  165. return <div>
  166. <UsersList isLoaded={this.state.isLoaded}
  167. users={this.props.users}
  168. showRank={true}
  169. cols={2} />
  170. {this.getMoreButton()}
  171. </div>;
  172. /* jshint ignore:end */
  173. }
  174. }
  175. getClassName() {
  176. return 'profile-' + this.API_FILTER;
  177. }
  178. render() {
  179. /* jshint ignore:start */
  180. return <div className={this.getClassName()}>
  181. <nav className="toolbar">
  182. <h3 className="toolbar-left">
  183. {this.getLabel()}
  184. </h3>
  185. <Search className="toolbar-right"
  186. value={this.state.search}
  187. onChange={this.search}
  188. placeholder={gettext("Search users...")} />
  189. </nav>
  190. {this.getListBody()}
  191. </div>;
  192. /* jshint ignore:end */
  193. }
  194. }