ThreadPost.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <template>
  2. <div
  3. class='post'
  4. :class='{
  5. "post--highlighted": highlight,
  6. "post--selected": selected
  7. }'
  8. @mouseenter='hover = true'
  9. @mouseleave='hover = false'
  10. @click='goToPost'
  11. >
  12. <span
  13. class='post__remove_icon fa fa-check'
  14. :class='{"post__remove_icon--show": showSelect && !post.removed}'
  15. @click.stop='toggleSelected'
  16. ></span>
  17. <modal-window v-model='showShareModal' @click.stop='() => {}'>
  18. <div slot='main'>
  19. <p>Copy this URL to share the post</p>
  20. <fancy-input placeholder='Post URL' :value='postURL' width='100%'></fancy-input>
  21. </div>
  22. <button slot='footer' class='button button--modal' @click.stop='setShareModalState(false)'>OK</button>
  23. </modal-window>
  24. <report-post-modal v-model='showReportPostModal' :post-id='post.id'></report-post-modal>
  25. <div class='post__meta_data'>
  26. <div style='display: inline-flex;'>
  27. <avatar-icon :user='post.User' class='post__avatar'></avatar-icon>
  28. <div class='post__thread' v-if='showThread' @click.stop='goToThread'>
  29. In thread <span class='post__thread__name'>{{post.Thread.name}}</span>
  30. &nbsp;&middot;&nbsp;
  31. </div>
  32. <div class='post__user' v-else>{{username}}</div>
  33. <replying-to
  34. style='margin-right: 0.5rem;'
  35. v-if='post.replyingToUsername'
  36. :replyId='post.replyId'
  37. :username='post.replyingToUsername'
  38. @click='$emit("goToPost", post.replyId, true)'
  39. ></replying-to>
  40. </div>
  41. <div class='post__date'>{{post.createdAt | formatDate('time|date', ', ')}}</div>
  42. </div>
  43. <div class='post__date post__date--mobile'>{{post.createdAt | formatDate('time|date', ', ')}}</div>
  44. <div class='post__content' v-html='postContentHTML'></div>
  45. <div class='post__footer'>
  46. <div
  47. class='post__footer_group'
  48. >
  49. <div class='post__footer_sub_group'>
  50. <heart-button :post='post' v-if='showReply'></heart-button>
  51. </div>
  52. <div class='post__footer_sub_group' v-if='post.Replies.length'>
  53. <span class='post__footer_sub_group__text post__footer_sub_group__text--replies'>replies</span>
  54. <post-reply
  55. v-for='(reply, index) in post.Replies'
  56. :key='reply.postNumber'
  57. :post='reply'
  58. :hover='hover'
  59. :first='index === 0'
  60. @click='$emit("goToPost", reply.postNumber)'
  61. ></post-reply>
  62. </div>
  63. </div>
  64. <div
  65. class='post__footer_group post__actions'
  66. :class='{ "post__actions--show": showActions }'
  67. >
  68. <div class='post__action post__share' @click.stop='setShareModalState(true)'>share</div>
  69. <div
  70. class='post__action'
  71. @click.stop='setShowReportPostModal(true)'
  72. v-if='$store.state.username && !post.removed'
  73. >
  74. report
  75. </div>
  76. <div
  77. class='post__action post__reply'
  78. v-if='$store.state.username && showReply'
  79. @click.stop='$emit("reply", post.id, username)'
  80. >
  81. reply
  82. </div>
  83. </div>
  84. </div>
  85. <div class='post__replies'>
  86. </div>
  87. </div>
  88. </template>
  89. <script>
  90. import PostReply from './PostReply'
  91. import HeartButton from './HeartButton'
  92. import ModalWindow from './ModalWindow'
  93. import FancyInput from './FancyInput'
  94. import ReplyingTo from './ReplyingTo'
  95. import AvatarIcon from './AvatarIcon'
  96. import ReportPostModal from './ReportPostModal'
  97. import AjaxErrorHandler from '../assets/js/errorHandler'
  98. export default {
  99. name: 'ThreadPost',
  100. props: [
  101. 'post',
  102. 'highlight',
  103. 'showReply',
  104. 'showThread',
  105. 'showSelect',
  106. 'clickForPost'
  107. ],
  108. components: {
  109. PostReply,
  110. ModalWindow,
  111. FancyInput,
  112. ReplyingTo,
  113. AvatarIcon,
  114. HeartButton,
  115. ReportPostModal
  116. },
  117. data () {
  118. let post = this.post
  119. return {
  120. hover: false,
  121. showShareModal: false,
  122. showReportPostModal: false,
  123. postURL: `${location.origin}/p/${post.id}`,
  124. selected: false,
  125. postContentHTML: post.content
  126. }
  127. },
  128. computed: {
  129. username () {
  130. if(this.post.User) {
  131. return this.post.User.username
  132. } else {
  133. return '[deleted]'
  134. }
  135. },
  136. showActions () {
  137. return this.hover || this.showShareModal || this.showReportPostModal
  138. }
  139. },
  140. methods: {
  141. setShareModalState (val) {
  142. this.showShareModal = val
  143. },
  144. setShowReportPostModal (val) {
  145. this.showReportPostModal = val
  146. },
  147. goToThread () {
  148. this.$router.push(`/thread/${this.post.Thread.slug}/${this.post.Thread.id}`)
  149. },
  150. goToPost () {
  151. if(this.clickForPost) {
  152. this.$router.push(
  153. '/thread/' +
  154. this.post.Thread.slug + '/' +
  155. this.post.Thread.id + '/' +
  156. this.post.postNumber
  157. )
  158. }
  159. },
  160. toggleSelected () {
  161. this.selected = !this.selected
  162. this.$emit('selected', this.post.id)
  163. }
  164. },
  165. watch: {
  166. showSelect () {
  167. if(this.selected) {
  168. this.$emit('selected', this.post.id)
  169. }
  170. this.selected = false
  171. }
  172. },
  173. mounted () {
  174. this.$linkExpander(this.post.content, v => this.postContentHTML = v);
  175. }
  176. }
  177. </script>
  178. <style lang='scss' scoped>
  179. @import '../assets/scss/variables.scss';
  180. @keyframes shake {
  181. 0% {
  182. left: 0rem;
  183. }
  184. 25% {
  185. left: -0.5rem;
  186. }
  187. 75% {
  188. left: 0.5rem;
  189. }
  190. 100% {
  191. left: 0rem;
  192. }
  193. }
  194. .post {
  195. position: relative;
  196. border-bottom: thin solid $color__gray--darker;
  197. transition: background-color 0.5s;
  198. margin: 0.5rem -0.5rem;
  199. padding: 0 0.5rem;
  200. border-radius: 0.25rem;
  201. @at-root #{&}--highlighted {
  202. background-color: $color__lightgray--darkest;
  203. animation-name: shake;
  204. animation-iteration-count: 5;
  205. animation-timing-function: linear;
  206. animation-duration: 0.25s;
  207. }
  208. @at-root #{&}--last {
  209. border-bottom: none;
  210. margin-bottom: 0;
  211. }
  212. @at-root #{&}__remove_icon {
  213. position: absolute;
  214. right: 1rem;
  215. display: inline-block;
  216. top: 1rem;
  217. color: #fff;
  218. cursor: pointer;
  219. background-color: gray;
  220. z-index: 1;
  221. border-radius: 100%;
  222. opacity: 0;
  223. pointer-events: none;
  224. padding: 0.25rem;
  225. transition: all 0.2s;
  226. @at-root #{&}--show {
  227. opacity: 1;
  228. pointer-events: all;
  229. }
  230. }
  231. @at-root #{&}--selected {
  232. transform: scale(0.95);
  233. padding: 1rem;
  234. background-color: $color__lightgray--primary;
  235. }
  236. @at-root #{&}__meta_data {
  237. display: flex;
  238. justify-content: space-between;
  239. padding-top: 0.75rem;
  240. position: relative;
  241. margin-left: 4rem;
  242. }
  243. @at-root #{&}__avatar {
  244. position: absolute;
  245. left: -4rem;
  246. }
  247. @at-root #{&}__user {
  248. @include text($font--role-default, 1rem, 600);
  249. margin-right: 0.5rem;
  250. }
  251. @at-root #{&}__thread {
  252. color: $color__text--secondary;
  253. @at-root #{&}__name {
  254. cursor: pointer;
  255. @include text($font--role-default, 1rem, 600);
  256. &:hover {
  257. color: $color__darkgray--primary;
  258. }
  259. }
  260. }
  261. @at-root #{&}__date {
  262. @at-root #{&}--mobile {
  263. display: none;
  264. }
  265. }
  266. @at-root #{&}__content {
  267. padding: 0 0.5rem 0 4rem;
  268. }
  269. @at-root #{&}__footer {
  270. padding: 0.5rem 0 0.75rem 0.5rem;
  271. display: flex;
  272. align-items: center;
  273. justify-content: space-between;
  274. transition: opacity 0.2s;
  275. @at-root #{&}_sub_group {
  276. display: flex;
  277. align-items: baseline;
  278. margin-right: 1rem;
  279. @at-root #{&}__text {
  280. font-variant: small-caps;
  281. margin: 0 0.25rem;
  282. margin-left: 0;
  283. font-size: 0.9rem;
  284. position: relative;
  285. bottom: 0.1rem;
  286. }
  287. }
  288. @at-root #{&}_group {
  289. align-items: center;
  290. display: inline-flex;
  291. position: relative;
  292. }
  293. }
  294. @at-root #{&}__action {
  295. color: $color__darkgray--primary;
  296. cursor: pointer;
  297. margin-right: 0.75rem;
  298. font-size: 0.9rem;
  299. font-variant: small-caps;
  300. position: relative;
  301. bottom: 0.1rem;
  302. transition: all 0.2s;
  303. &:hover {
  304. color: $color__darkgray--darkest;
  305. }
  306. }
  307. @at-root #{&}__actions {
  308. opacity: 0;
  309. @at-root #{&}--show {
  310. opacity: 1;
  311. transition: opacity 0.2s;
  312. }
  313. }
  314. }
  315. @media (max-width: 420px) {
  316. .post {
  317. @at-root #{&}__actions {
  318. opacity: 1;
  319. }
  320. @at-root #{&}__content {
  321. padding: 0 0.5rem;
  322. }
  323. @at-root #{&}__date {
  324. display: none;
  325. @at-root #{&}--mobile {
  326. display: block;
  327. padding-left: 4rem;
  328. font-size: 0.9rem;
  329. }
  330. }
  331. }
  332. }
  333. </style>