move.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import React from "react"
  2. import Form from "misago/components/form"
  3. import FormGroup from "misago/components/form-group"
  4. import CategorySelect from "misago/components/category-select"
  5. import * as select from "misago/reducers/selection"
  6. import { filterThreads } from "misago/reducers/threads"
  7. import modal from "misago/services/modal"
  8. import store from "misago/services/store"
  9. export default class extends Form {
  10. constructor(props) {
  11. super(props)
  12. this.state = {
  13. category: null
  14. }
  15. const acls = {}
  16. for (const i in props.user.acl.categories) {
  17. if (!props.user.acl.categories.hasOwnProperty(i)) {
  18. continue
  19. }
  20. const acl = props.user.acl.categories[i]
  21. acls[acl.id] = acl
  22. }
  23. this.categoryChoices = []
  24. props.categories.forEach(category => {
  25. if (category.level > 0) {
  26. const acl = acls[category.id]
  27. const disabled =
  28. !acl.can_start_threads ||
  29. (category.is_closed && !acl.can_close_threads)
  30. this.categoryChoices.push({
  31. value: category.id,
  32. disabled: disabled,
  33. level: category.level - 1,
  34. label: category.name
  35. })
  36. if (!disabled && !this.state.category) {
  37. this.state.category = category.id
  38. }
  39. }
  40. })
  41. }
  42. handleSubmit = event => {
  43. // we don't reload page on submissions
  44. event.preventDefault()
  45. modal.hide()
  46. const onSuccess = () => {
  47. store.dispatch(
  48. filterThreads(this.props.route.category, this.props.categoriesMap)
  49. )
  50. // deselect threads moved outside of visible scope
  51. const storeState = store.getState()
  52. const leftThreads = storeState.threads.map(thread => thread.id)
  53. store.dispatch(
  54. select.all(
  55. storeState.selection.filter(thread => {
  56. return leftThreads.indexOf(thread) !== -1
  57. })
  58. )
  59. )
  60. }
  61. this.props.callApi(
  62. [
  63. { op: "replace", path: "category", value: this.state.category },
  64. { op: "replace", path: "flatten-categories", value: null },
  65. { op: "add", path: "acl", value: true }
  66. ],
  67. gettext("Selected threads were moved."),
  68. onSuccess
  69. )
  70. }
  71. getClassName() {
  72. if (!this.state.category) {
  73. return "modal-dialog modal-message"
  74. } else {
  75. return "modal-dialog"
  76. }
  77. }
  78. renderForm() {
  79. return (
  80. <form onSubmit={this.handleSubmit}>
  81. <div className="modal-body">
  82. <FormGroup label={gettext("New category")} for="id_new_category">
  83. <CategorySelect
  84. id="id_new_category"
  85. onChange={this.bindInput("category")}
  86. value={this.state.category}
  87. choices={this.categoryChoices}
  88. />
  89. </FormGroup>
  90. </div>
  91. <div className="modal-footer">
  92. <button
  93. className="btn btn-default"
  94. data-dismiss="modal"
  95. disabled={this.state.isLoading}
  96. type="button"
  97. >
  98. {gettext("Cancel")}
  99. </button>
  100. <button className="btn btn-primary">{gettext("Move threads")}</button>
  101. </div>
  102. </form>
  103. )
  104. }
  105. renderCantMoveMessage() {
  106. return (
  107. <div className="modal-body">
  108. <div className="message-icon">
  109. <span className="material-icon">info_outline</span>
  110. </div>
  111. <div className="message-body">
  112. <p className="lead">
  113. {gettext(
  114. "You can't move threads because there are no categories you are allowed to move them to."
  115. )}
  116. </p>
  117. <p>
  118. {gettext(
  119. "You need permission to start threads in category to be able to move threads to it."
  120. )}
  121. </p>
  122. <button
  123. className="btn btn-default"
  124. data-dismiss="modal"
  125. type="button"
  126. >
  127. {gettext("Ok")}
  128. </button>
  129. </div>
  130. </div>
  131. )
  132. }
  133. render() {
  134. return (
  135. <div className={this.getClassName()} role="document">
  136. <div className="modal-content">
  137. <div className="modal-header">
  138. <button
  139. type="button"
  140. className="close"
  141. data-dismiss="modal"
  142. aria-label={gettext("Close")}
  143. >
  144. <span aria-hidden="true">&times;</span>
  145. </button>
  146. <h4 className="modal-title">{gettext("Move threads")}</h4>
  147. </div>
  148. {this.state.category
  149. ? this.renderForm()
  150. : this.renderCantMoveMessage()}
  151. </div>
  152. </div>
  153. )
  154. }
  155. }