route.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import React from "react"
  2. import Participants from "misago/components/participants"
  3. import { Poll, PollForm } from "misago/components/poll"
  4. import PostsList from "misago/components/posts-list"
  5. import * as participants from "misago/reducers/participants"
  6. import * as poll from "misago/reducers/poll"
  7. import * as posts from "misago/reducers/posts"
  8. import * as thread from "misago/reducers/thread"
  9. import ajax from "misago/services/ajax"
  10. import polls from "misago/services/polls"
  11. import snackbar from "misago/services/snackbar"
  12. import posting from "misago/services/posting"
  13. import store from "misago/services/store"
  14. import title from "misago/services/page-title"
  15. import { PostingQuoteSelection } from "../posting"
  16. import PageContainer from "../PageContainer"
  17. import ThreadHeader from "./ThreadHeader"
  18. import ThreadToolbarBottom from "./ThreadToolbarBottom"
  19. import ThreadToolbarTop from "./ThreadToolbarTop"
  20. export default class extends React.Component {
  21. constructor(props) {
  22. super(props)
  23. this.state = {
  24. editPoll: false,
  25. }
  26. }
  27. componentDidMount() {
  28. if (this.shouldFetchData()) {
  29. this.fetchData()
  30. this.setPageTitle()
  31. }
  32. this.startPollingApi()
  33. }
  34. componentDidUpdate() {
  35. if (this.shouldFetchData()) {
  36. this.fetchData()
  37. this.startPollingApi()
  38. this.setPageTitle()
  39. }
  40. }
  41. componentWillUnmount() {
  42. this.stopPollingApi()
  43. }
  44. shouldFetchData() {
  45. if (this.props.posts.isLoaded) {
  46. const page = (this.props.params.page || 1) * 1
  47. return page != this.props.posts.page
  48. } else {
  49. return false
  50. }
  51. }
  52. fetchData() {
  53. store.dispatch(posts.unload())
  54. ajax
  55. .get(
  56. this.props.thread.api.posts.index,
  57. {
  58. page: this.props.params.page || 1,
  59. },
  60. "posts"
  61. )
  62. .then(
  63. (data) => {
  64. this.update(data)
  65. },
  66. (rejection) => {
  67. snackbar.apiError(rejection)
  68. }
  69. )
  70. }
  71. startPollingApi() {
  72. polls.start({
  73. poll: "thread-posts",
  74. url: this.props.thread.api.posts.index,
  75. data: {
  76. page: this.props.params.page || 1,
  77. },
  78. update: this.update,
  79. frequency: 120 * 1000,
  80. delayed: true,
  81. })
  82. }
  83. stopPollingApi() {
  84. polls.stop("thread-posts")
  85. }
  86. setPageTitle() {
  87. title.set({
  88. title: this.props.thread.title,
  89. parent: this.props.thread.category.name,
  90. page: (this.props.params.page || 1) * 1,
  91. })
  92. }
  93. update = (data) => {
  94. store.dispatch(thread.replace(data))
  95. store.dispatch(posts.load(data.post_set))
  96. if (data.participants) {
  97. store.dispatch(participants.replace(data.participants))
  98. }
  99. if (data.poll) {
  100. store.dispatch(poll.replace(data.poll))
  101. }
  102. this.setPageTitle()
  103. }
  104. openPollForm = () => {
  105. this.setState({ editPoll: true })
  106. }
  107. closePollForm = () => {
  108. this.setState({ editPoll: false })
  109. }
  110. openReplyForm = () => {
  111. posting.open({
  112. mode: "REPLY",
  113. thread: this.props.thread,
  114. config: this.props.thread.api.editor,
  115. submit: this.props.thread.api.posts.index,
  116. })
  117. }
  118. render() {
  119. const category = this.props.thread.category
  120. let className = "page page-thread"
  121. if (category.css_class) {
  122. className += " page-thread-" + category.css_class
  123. }
  124. const styleName =
  125. category.special_role === "private_threads"
  126. ? "private-threads"
  127. : category.css_class || "category-threads"
  128. const threadModeration = getThreadModeration(
  129. this.props.thread,
  130. this.props.user
  131. )
  132. const postsModeration = getPostsModeration(
  133. this.props.posts.results,
  134. this.props.user
  135. )
  136. const selection = this.props.posts.results.filter((post) => post.isSelected)
  137. return (
  138. <div className={className}>
  139. <ThreadHeader
  140. styleName={styleName}
  141. thread={this.props.thread}
  142. posts={this.props.posts}
  143. user={this.props.user}
  144. moderation={threadModeration}
  145. />
  146. <PageContainer>
  147. <Participants
  148. participants={this.props.participants}
  149. thread={this.props.thread}
  150. user={this.props.user}
  151. />
  152. <ThreadToolbarTop
  153. thread={this.props.thread}
  154. posts={this.props.posts}
  155. user={this.props.user}
  156. selection={selection}
  157. moderation={postsModeration}
  158. pollDisabled={this.state.editPoll}
  159. onPoll={this.openPollForm}
  160. onReply={this.openReplyForm}
  161. />
  162. {this.state.editPoll ? (
  163. <PollForm
  164. poll={this.props.poll}
  165. thread={this.props.thread}
  166. close={this.closePollForm}
  167. />
  168. ) : (
  169. <Poll
  170. poll={this.props.poll}
  171. thread={this.props.thread}
  172. user={this.props.user}
  173. edit={this.openPollForm}
  174. />
  175. )}
  176. {this.props.thread.acl.can_reply ? (
  177. <PostingQuoteSelection
  178. posting={{
  179. mode: "REPLY",
  180. thread: this.props.thread,
  181. config: this.props.thread.api.editor,
  182. submit: this.props.thread.api.posts.index,
  183. }}
  184. >
  185. <PostsList {...this.props} />
  186. </PostingQuoteSelection>
  187. ) : (
  188. <PostsList {...this.props} />
  189. )}
  190. <ThreadToolbarBottom
  191. thread={this.props.thread}
  192. posts={this.props.posts}
  193. user={this.props.user}
  194. selection={selection}
  195. moderation={postsModeration}
  196. onReply={this.openReplyForm}
  197. />
  198. </PageContainer>
  199. </div>
  200. )
  201. }
  202. }
  203. const getThreadModeration = (thread, user) => {
  204. const moderation = {
  205. enabled: false,
  206. edit: false,
  207. approve: false,
  208. close: false,
  209. open: false,
  210. hide: false,
  211. unhide: false,
  212. move: false,
  213. merge: false,
  214. pinGlobally: false,
  215. pinLocally: false,
  216. unpin: false,
  217. delete: false,
  218. }
  219. if (!user.is_authenticated) return moderation
  220. moderation.edit = thread.acl.can_edit
  221. moderation.approve = thread.acl.can_approve && thread.is_unapproved
  222. moderation.close = thread.acl.can_close && !thread.is_closed
  223. moderation.open = thread.acl.can_close && thread.is_closed
  224. moderation.hide = thread.acl.can_hide && !thread.is_hidden
  225. moderation.unhide = thread.acl.can_unhide && thread.is_hidden
  226. moderation.move = thread.acl.can_move
  227. moderation.merge = thread.acl.can_merge
  228. moderation.pinGlobally = thread.acl.can_pin_globally && thread.weight < 2
  229. moderation.pinLocally = thread.acl.can_pin && thread.weight !== 1
  230. moderation.unpin =
  231. (thread.acl.can_pin && thread.weight === 1) ||
  232. (thread.acl.can_pin_globally && thread.weight === 2)
  233. moderation.delete = thread.acl.can_delete
  234. moderation.enabled =
  235. moderation.edit ||
  236. moderation.approve ||
  237. moderation.close ||
  238. moderation.open ||
  239. moderation.hide ||
  240. moderation.unhide ||
  241. moderation.move ||
  242. moderation.merge ||
  243. moderation.pinGlobally ||
  244. moderation.pinLocally ||
  245. moderation.unpin ||
  246. moderation.delete
  247. return moderation
  248. }
  249. const getPostsModeration = (posts, user) => {
  250. const moderation = {
  251. enabled: false,
  252. approve: false,
  253. move: false,
  254. merge: false,
  255. protect: false,
  256. hide: false,
  257. delete: false,
  258. }
  259. if (!user.is_authenticated) return moderation
  260. posts.forEach((post) => {
  261. if (!post.is_event) {
  262. if (post.acl.can_approve && post.is_unapproved) {
  263. moderation.approve = true
  264. }
  265. if (post.acl.can_move) moderation.move = true
  266. if (post.acl.can_merge) moderation.merge = true
  267. if (post.acl.can_protect || post.acl.can_unprotect) {
  268. moderation.protect = true
  269. }
  270. if (post.acl.can_hide || post.acl.can_unhide) {
  271. moderation.hide = true
  272. }
  273. if (post.acl.can_delete) moderation.delete = true
  274. if (
  275. moderation.approve ||
  276. moderation.move ||
  277. moderation.merge ||
  278. moderation.protect ||
  279. moderation.hide ||
  280. moderation.delete
  281. ) {
  282. moderation.enabled = true
  283. }
  284. }
  285. })
  286. return moderation
  287. }