controls.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import React from "react"
  2. import MergeModal from "./merge"
  3. import MoveModal from "./move"
  4. import * as thread from "misago/reducers/thread"
  5. import ajax from "misago/services/ajax"
  6. import modal from "misago/services/modal"
  7. import snackbar from "misago/services/snackbar"
  8. import store from "misago/services/store"
  9. export default class extends React.Component {
  10. callApi = (ops, successMessage) => {
  11. store.dispatch(thread.busy())
  12. // by the chance update thread acl too
  13. ops.push({ op: "add", path: "acl", value: true })
  14. ajax.patch(this.props.thread.api.index, ops).then(
  15. data => {
  16. store.dispatch(thread.update(data))
  17. store.dispatch(thread.release())
  18. snackbar.success(successMessage)
  19. },
  20. rejection => {
  21. store.dispatch(thread.release())
  22. if (rejection.status === 400) {
  23. snackbar.error(rejection.detail[0])
  24. } else {
  25. snackbar.apiError(rejection)
  26. }
  27. }
  28. )
  29. }
  30. pinGlobally = () => {
  31. this.callApi(
  32. [
  33. {
  34. op: "replace",
  35. path: "weight",
  36. value: 2
  37. }
  38. ],
  39. gettext("Thread has been pinned globally.")
  40. )
  41. }
  42. pinLocally = () => {
  43. this.callApi(
  44. [
  45. {
  46. op: "replace",
  47. path: "weight",
  48. value: 1
  49. }
  50. ],
  51. gettext("Thread has been pinned locally.")
  52. )
  53. }
  54. unpin = () => {
  55. this.callApi(
  56. [
  57. {
  58. op: "replace",
  59. path: "weight",
  60. value: 0
  61. }
  62. ],
  63. gettext("Thread has been unpinned.")
  64. )
  65. }
  66. approve = () => {
  67. this.callApi(
  68. [
  69. {
  70. op: "replace",
  71. path: "is-unapproved",
  72. value: false
  73. }
  74. ],
  75. gettext("Thread has been approved.")
  76. )
  77. }
  78. open = () => {
  79. this.callApi(
  80. [
  81. {
  82. op: "replace",
  83. path: "is-closed",
  84. value: false
  85. }
  86. ],
  87. gettext("Thread has been opened.")
  88. )
  89. }
  90. close = () => {
  91. this.callApi(
  92. [
  93. {
  94. op: "replace",
  95. path: "is-closed",
  96. value: true
  97. }
  98. ],
  99. gettext("Thread has been closed.")
  100. )
  101. }
  102. unhide = () => {
  103. this.callApi(
  104. [
  105. {
  106. op: "replace",
  107. path: "is-hidden",
  108. value: false
  109. }
  110. ],
  111. gettext("Thread has been made visible.")
  112. )
  113. }
  114. hide = () => {
  115. this.callApi(
  116. [
  117. {
  118. op: "replace",
  119. path: "is-hidden",
  120. value: true
  121. }
  122. ],
  123. gettext("Thread has been made hidden.")
  124. )
  125. }
  126. move = () => {
  127. modal.show(
  128. <MoveModal posts={this.props.posts} thread={this.props.thread} />
  129. )
  130. }
  131. merge = () => {
  132. modal.show(<MergeModal thread={this.props.thread} />)
  133. }
  134. delete = () => {
  135. if (!confirm(gettext("Are you sure you want to delete this thread?"))) {
  136. return
  137. }
  138. store.dispatch(thread.busy())
  139. ajax.delete(this.props.thread.api.index).then(
  140. data => {
  141. snackbar.success(gettext("Thread has been deleted."))
  142. window.location = this.props.thread.category.url.index
  143. },
  144. rejection => {
  145. store.dispatch(thread.release())
  146. snackbar.apiError(rejection)
  147. }
  148. )
  149. }
  150. getPinGloballyButton() {
  151. if (this.props.thread.weight === 2) return null
  152. if (!this.props.thread.acl.can_pin_globally) return null
  153. return (
  154. <li>
  155. <button
  156. className="btn btn-link"
  157. onClick={this.pinGlobally}
  158. type="button"
  159. >
  160. <span className="material-icon">bookmark</span>
  161. {gettext("Pin globally")}
  162. </button>
  163. </li>
  164. )
  165. }
  166. getPinLocallyButton() {
  167. if (this.props.thread.weight === 1) return null
  168. if (!this.props.thread.acl.can_pin) return null
  169. return (
  170. <li>
  171. <button
  172. className="btn btn-link"
  173. onClick={this.pinLocally}
  174. type="button"
  175. >
  176. <span className="material-icon">bookmark_border</span>
  177. {gettext("Pin locally")}
  178. </button>
  179. </li>
  180. )
  181. }
  182. getUnpinButton() {
  183. if (this.props.thread.weight === 0) return null
  184. if (!this.props.thread.acl.can_pin) return null
  185. return (
  186. <li>
  187. <button className="btn btn-link" onClick={this.unpin} type="button">
  188. <span className="material-icon">panorama_fish_eye</span>
  189. {gettext("Unpin")}
  190. </button>
  191. </li>
  192. )
  193. }
  194. getMoveButton() {
  195. if (!this.props.thread.acl.can_move) return null
  196. return (
  197. <li>
  198. <button className="btn btn-link" onClick={this.move} type="button">
  199. <span className="material-icon">arrow_forward</span>
  200. {gettext("Move")}
  201. </button>
  202. </li>
  203. )
  204. }
  205. getMergeButton() {
  206. if (!this.props.thread.acl.can_merge) return null
  207. return (
  208. <li>
  209. <button className="btn btn-link" onClick={this.merge} type="button">
  210. <span className="material-icon">call_merge</span>
  211. {gettext("Merge")}
  212. </button>
  213. </li>
  214. )
  215. }
  216. getApproveButton() {
  217. if (!this.props.thread.is_unapproved) return null
  218. if (!this.props.thread.acl.can_approve) return null
  219. return (
  220. <li>
  221. <button className="btn btn-link" onClick={this.approve} type="button">
  222. <span className="material-icon">done</span>
  223. {gettext("Approve")}
  224. </button>
  225. </li>
  226. )
  227. }
  228. getOpenButton() {
  229. if (!this.props.thread.is_closed) return null
  230. if (!this.props.thread.acl.can_close) return null
  231. return (
  232. <li>
  233. <button className="btn btn-link" onClick={this.open} type="button">
  234. <span className="material-icon">lock_open</span>
  235. {gettext("Open")}
  236. </button>
  237. </li>
  238. )
  239. }
  240. getCloseButton() {
  241. if (this.props.thread.is_closed) return null
  242. if (!this.props.thread.acl.can_close) return null
  243. return (
  244. <li>
  245. <button className="btn btn-link" onClick={this.close} type="button">
  246. <span className="material-icon">lock_outline</span>
  247. {gettext("Close")}
  248. </button>
  249. </li>
  250. )
  251. }
  252. getUnhideButton() {
  253. if (!this.props.thread.is_hidden) return null
  254. if (!this.props.thread.acl.can_unhide) return null
  255. return (
  256. <li>
  257. <button className="btn btn-link" onClick={this.unhide} type="button">
  258. <span className="material-icon">visibility</span>
  259. {gettext("Unhide")}
  260. </button>
  261. </li>
  262. )
  263. }
  264. getHideButton() {
  265. if (this.props.thread.is_hidden) return null
  266. if (!this.props.thread.acl.can_hide) return null
  267. return (
  268. <li>
  269. <button className="btn btn-link" onClick={this.hide} type="button">
  270. <span className="material-icon">visibility_off</span>
  271. {gettext("Hide")}
  272. </button>
  273. </li>
  274. )
  275. }
  276. getDeleteButton() {
  277. if (!this.props.thread.acl.can_delete) return null
  278. return (
  279. <li>
  280. <button className="btn btn-link" onClick={this.delete} type="button">
  281. <span className="material-icon">clear</span>
  282. {gettext("Delete")}
  283. </button>
  284. </li>
  285. )
  286. }
  287. render() {
  288. return (
  289. <ul className="dropdown-menu dropdown-menu-right stick-to-bottom">
  290. {this.getPinGloballyButton()}
  291. {this.getPinLocallyButton()}
  292. {this.getUnpinButton()}
  293. {this.getMoveButton()}
  294. {this.getMergeButton()}
  295. {this.getApproveButton()}
  296. {this.getOpenButton()}
  297. {this.getCloseButton()}
  298. {this.getUnhideButton()}
  299. {this.getHideButton()}
  300. {this.getDeleteButton()}
  301. </ul>
  302. )
  303. }
  304. }