AdminRemoteReportModal.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <template>
  2. <b-modal
  3. v-model="isOpen"
  4. title="Remote Report"
  5. :ok-only="true"
  6. ok-title="Close"
  7. :lazy="true"
  8. :scrollable="true"
  9. ok-variant="outline-primary"
  10. v-on:hide="$emit('close')">
  11. <div v-if="isLoading" class="d-flex align-items-center justify-content-center">
  12. <b-spinner />
  13. </div>
  14. <template v-else>
  15. <div class="list-group">
  16. <div class="list-group-item d-flex justify-content-between align-items-center">
  17. <div class="text-muted small font-weight-bold">Instance</div>
  18. <div class="font-weight-bold">{{ model.instance }}</div>
  19. </div>
  20. <div v-if="model.message && model.message.length" class="list-group-item d-flex justify-content-between align-items-center flex-column gap-1">
  21. <div class="text-muted small font-weight-bold mb-2">Message</div>
  22. <div class="text-wrap w-100" style="word-break:break-all;font-size:12.5px;">
  23. <admin-read-more
  24. :content="model.message"
  25. font-size="11"
  26. :step="true"
  27. :initial-limit="100"
  28. :stepLimit="1000" />
  29. </div>
  30. </div>
  31. </div>
  32. <div class="list-group list-group-horizontal mt-3">
  33. <div
  34. v-if="model && model.reported"
  35. class="list-group-item d-flex align-items-center justify-content-between flex-row flex-grow-1"
  36. style="gap:0.4rem;">
  37. <div class="text-muted small font-weight-bold">Reported Account</div>
  38. <div class="d-flex justify-content-end flex-grow-1">
  39. <a v-if="model.reported && model.reported.id" :href="`/i/web/profile/${model.reported.id}`" target="_blank" class="text-primary">
  40. <div class="d-flex align-items-center" style="gap:0.61rem;">
  41. <img
  42. :src="model.reported.avatar"
  43. width="30"
  44. height="30"
  45. style="object-fit: cover;border-radius:30px;"
  46. onerror="this.src='/storage/avatars/default.png';this.error=null;">
  47. <div class="d-flex flex-column">
  48. <p class="font-weight-bold mb-0 text-break" style="font-size: 12px;max-width: 140px;line-height: 16px;" :class="[ model.reported.is_admin ? 'text-danger': '']">@{{model.reported.acct}}</p>
  49. <div class="d-flex text-muted mb-0" style="font-size: 10px;gap: 0.5rem;">
  50. <span>{{prettyCount(model.reported.followers_count)}} Followers</span>
  51. <span>·</span>
  52. <span>Joined {{ timeAgo(model.reported.created_at) }}</span>
  53. </div>
  54. </div>
  55. </div>
  56. </a>
  57. </div>
  58. </div>
  59. <div
  60. v-else
  61. class="list-group-item d-flex align-items-center justify-content-center flex-column flex-grow-1">
  62. <p class="font-weight-bold mb-0">Reported Account Unavailable</p>
  63. <p class="small mb-0">The reported account may have been deleted, or is otherwise not currently active. You can safely <strong>Close Report</strong> to mark this report as read.</p>
  64. </div>
  65. </div>
  66. <div v-if="model && model.statuses && model.statuses.length" class="list-group mt-3">
  67. <admin-modal-post
  68. v-for="(status, idx) in model.statuses"
  69. :key="`admin-modal-post-remote-post:${status.id}:${idx}`"
  70. :status="status"
  71. />
  72. </div>
  73. <div class="mt-4">
  74. <div>
  75. <button
  76. type="button"
  77. class="btn btn-dark btn-block rounded-pill"
  78. @click="handleAction('mark-read')">
  79. Close Report
  80. </button>
  81. <button
  82. type="button"
  83. class="btn btn-outline-dark btn-block text-center rounded-pill"
  84. style="word-break: break-all;"
  85. @click="handleAction('mark-all-read-by-domain')">
  86. <span class="font-weight-light">Close all reports from</span> <strong>{{ model.instance}}</strong>
  87. </button>
  88. <button
  89. v-if="model.reported"
  90. type="button"
  91. class="btn btn-outline-dark btn-block rounded-pill flex-grow-1"
  92. @click="handleAction('mark-all-read-by-username')">
  93. <span class="font-weight-light">Close all reports against</span> <strong>&commat;{{ model.reported.username }}</strong>
  94. </button>
  95. <template
  96. v-if="model && model.statuses && model.statuses.length && model.reported">
  97. <hr class="mt-3 mb-1">
  98. <div
  99. class="d-flex flex-row mt-2"
  100. style="gap:0.3rem;">
  101. <button
  102. type="button"
  103. class="btn btn-outline-danger btn-block btn-sm rounded-pill mt-0"
  104. @click="handleAction('cw-posts')">
  105. Apply CW to Post(s)
  106. </button>
  107. <button
  108. type="button"
  109. class="btn btn-outline-danger btn-block btn-sm rounded-pill mt-0"
  110. @click="handleAction('unlist-posts')">
  111. Unlist Post(s)
  112. </button>
  113. </div>
  114. <div class="d-flex flex-row mt-2">
  115. <button
  116. type="button"
  117. class="btn btn-outline-danger btn-block btn-sm rounded-pill mt-0"
  118. @click="handleAction('private-posts')">
  119. Make Post(s) Private
  120. </button>
  121. <button
  122. type="button"
  123. class="btn btn-outline-danger btn-block btn-sm rounded-pill mt-0"
  124. @click="handleAction('delete-posts')">
  125. Delete Post(s)
  126. </button>
  127. </div>
  128. </template>
  129. <template v-else-if="model && model.statuses && !model.statuses.length && model.reported">
  130. <hr class="mt-3 mb-1">
  131. <div
  132. class="d-flex flex-row mt-2"
  133. style="gap:0.3rem;">
  134. <button
  135. type="button"
  136. class="btn btn-outline-danger btn-block btn-sm rounded-pill mt-0"
  137. @click="handleAction('cw-all-posts')">
  138. Apply CW to all posts
  139. </button>
  140. <button
  141. type="button"
  142. class="btn btn-outline-danger btn-block btn-sm rounded-pill mt-0"
  143. @click="handleAction('unlist-all-posts')">
  144. Unlist all account posts
  145. </button>
  146. </div>
  147. <div
  148. class="d-flex flex-row mt-2"
  149. style="gap:0.3rem;">
  150. <button
  151. type="button"
  152. class="btn btn-outline-danger btn-block btn-sm rounded-pill mt-0"
  153. @click="handleAction('private-all-posts')">
  154. Make all posts private
  155. </button>
  156. </div>
  157. </template>
  158. </div>
  159. </div>
  160. </template>
  161. </b-modal>
  162. </template>
  163. <script>
  164. import AdminModalPost from "./AdminModalPost.vue";
  165. import AdminReadMore from "./AdminReadMore.vue";
  166. export default {
  167. props: {
  168. open: {
  169. type: Boolean,
  170. default: false
  171. },
  172. model: {
  173. type: Object
  174. }
  175. },
  176. components: {
  177. "admin-modal-post": AdminModalPost,
  178. "admin-read-more": AdminReadMore
  179. },
  180. watch: {
  181. open: {
  182. handler() {
  183. this.isOpen = this.open;
  184. },
  185. immediate: true,
  186. deep: true,
  187. }
  188. },
  189. data() {
  190. return {
  191. isLoading: true,
  192. isOpen: false,
  193. actions: [
  194. 'mark-read',
  195. 'cw-posts',
  196. 'unlist-posts',
  197. 'private-posts',
  198. 'delete-posts',
  199. 'mark-all-read-by-domain',
  200. 'mark-all-read-by-username',
  201. 'cw-all-posts',
  202. 'unlist-all-posts',
  203. 'private-all-posts',
  204. ],
  205. actionMap: {
  206. 'cw-posts': 'apply content warnings to all post(s) in this report?',
  207. 'unlist-posts': 'unlist all post(s) in this report?',
  208. 'delete-posts': 'delete all post(s) in this report?',
  209. 'private-posts': 'make all post(s) in this report private/followers-only?',
  210. 'mark-all-read-by-domain': 'mark all reports by this instance as closed?',
  211. 'mark-all-read-by-username': 'mark all reports against this user as closed?',
  212. 'cw-all-posts': 'apply content warnings to all post(s) belonging to this account?',
  213. 'unlist-all-posts': 'make all post(s) belonging to this account as unlisted?',
  214. 'private-all-posts': 'make all post(s) belonging to this account as private?',
  215. }
  216. }
  217. },
  218. mounted() {
  219. setTimeout(() => {
  220. this.isLoading = false;
  221. }, 300);
  222. },
  223. methods: {
  224. prettyCount(str) {
  225. if(str) {
  226. return str.toLocaleString('en-CA', { compactDisplay: "short", notation: "compact"});
  227. }
  228. return str;
  229. },
  230. timeAgo(str) {
  231. if(!str) {
  232. return str;
  233. }
  234. return App.util.format.timeAgo(str);
  235. },
  236. formatDate(str) {
  237. let date = new Date(str);
  238. return new Intl.DateTimeFormat('default', {
  239. month: 'long',
  240. day: 'numeric',
  241. year: 'numeric',
  242. hour: 'numeric',
  243. minute: 'numeric'
  244. }).format(date);
  245. },
  246. handleAction(action) {
  247. if(action === 'mark-read') {
  248. axios.post('/i/admin/api/reports/remote/handle', {
  249. id: this.model.id,
  250. action: action,
  251. }).then(res => {
  252. console.log(res.data)
  253. })
  254. .finally(() => {
  255. this.$emit('refresh');
  256. this.$emit('close');
  257. })
  258. return;
  259. }
  260. swal({
  261. title: 'Confirm',
  262. text: 'Are you sure you want to ' + this.actionMap[action],
  263. icon: 'warning',
  264. buttons: true,
  265. dangerMode: true,
  266. }).then(res => {
  267. if(res === true) {
  268. axios.post('/i/admin/api/reports/remote/handle', {
  269. id: this.model.id,
  270. action: action,
  271. }).finally(() => {
  272. this.$emit('refresh');
  273. this.$emit('close');
  274. })
  275. }
  276. });
  277. }
  278. }
  279. }
  280. </script>