Thread.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <template>
  2. <div class='route_container'>
  3. <header class='thread_header'>
  4. <div
  5. class='thread_header__thread_title thread_header__thread_title--app_header'
  6. :class='{
  7. "thread_header__thread_title--app_header-show": headerTitle
  8. }'
  9. >
  10. {{thread}}
  11. </div>
  12. <div class='thread_header__thread_title' ref='title'>
  13. {{thread}}
  14. </div>
  15. <button class='button thread_header__reply_button' @click='replyThread' v-if='$store.state.username'>Reply to thread</button>
  16. </header>
  17. <input-editor
  18. v-model='editor'
  19. :float='true'
  20. :show='editorState'
  21. :replyUsername='replyUsername'
  22. v-on:close='hideEditor'
  23. v-on:submit='addPost'
  24. >
  25. </input-editor>
  26. <div class='posts'>
  27. <scroll-load
  28. :loading='loadingPosts'
  29. :show='$store.state.thread.nextURL !== null'
  30. @load='loadNewPosts'
  31. >
  32. <thread-post
  33. v-for='(post, index) in posts'
  34. @reply='replyUser'
  35. @goToPost='goToPost'
  36. :post='post'
  37. :show-reply='true'
  38. :highlight='highlightedPostIndex === index'
  39. :class='{"post--last": index === posts.length-1}'
  40. ref='posts'
  41. ></thread-post>
  42. </scroll-load>
  43. </div>
  44. </div>
  45. </template>
  46. <script>
  47. import InputEditor from '../InputEditor'
  48. import ScrollLoad from '../ScrollLoad'
  49. import ThreadPost from '../ThreadPost'
  50. import throttle from 'lodash.throttle'
  51. import AjaxErrorHandler from '../../assets/js/errorHandler'
  52. export default {
  53. name: 'Thread',
  54. components: {
  55. InputEditor,
  56. ScrollLoad,
  57. ThreadPost
  58. },
  59. data () {
  60. return {
  61. headerTitle: false,
  62. highlightedPostIndex: null
  63. }
  64. },
  65. computed: {
  66. thread () {
  67. return this.$store.state.thread.thread;
  68. },
  69. posts () {
  70. return this.$store.getters.sortedPosts;
  71. },
  72. replyUsername () {
  73. return this.$store.state.thread.reply.username
  74. },
  75. editor: {
  76. get () { return this.$store.state.thread.editor.value },
  77. set (val) {
  78. this.$store.commit('setThreadEditorValue', val)
  79. }
  80. },
  81. editorState () { return this.$store.state.thread.editor.show },
  82. loadingPosts () { return this.$store.state.thread.loadingPosts },
  83. },
  84. methods: {
  85. showEditor () {
  86. this.$store.commit('setThreadEditorState', true);
  87. },
  88. hideEditor () {
  89. this.$store.commit('setThreadEditorState', false);
  90. this.clearReply()
  91. },
  92. clearReply () {
  93. this.$store.commit({
  94. type: 'setReply',
  95. username: '',
  96. id: ''
  97. });
  98. },
  99. replyThread () {
  100. this.clearReply();
  101. this.showEditor();
  102. },
  103. replyUser (id, username) {
  104. this.$store.commit({
  105. type: 'setReply',
  106. username,
  107. id
  108. });
  109. this.showEditor();
  110. },
  111. addPost () {
  112. this.$store.dispatch('addPostAsync', this);
  113. },
  114. loadNewPosts () {
  115. this.$store.dispatch('loadNewPostsAsync', this);
  116. },
  117. goToPost (postId) {
  118. for(var i = 0; i < this.posts.length; i++) {
  119. let post = this.posts[i]
  120. if(post.id === postId) {
  121. let postTop = this.$refs.posts[i].$el.getBoundingClientRect().top
  122. let header = this.$refs.title.getBoundingClientRect().height
  123. window.scrollTo(0, postTop - header - 32)
  124. this.highlightedPostIndex = i
  125. setTimeout(() => {
  126. if(this.highlightedPostIndex === i) {
  127. this.highlightedPostIndex = null
  128. }
  129. }, 3000)
  130. break;
  131. }
  132. }
  133. }
  134. },
  135. created () {
  136. var self = this;
  137. var setHeader = function() {
  138. if(!self.$refs.title) return;
  139. if(self.$refs.title.getBoundingClientRect().top <= 32) {
  140. self.headerTitle = true;
  141. } else {
  142. self.headerTitle = false;
  143. }
  144. };
  145. setHeader();
  146. document.addEventListener('scroll', throttle(setHeader, 200));
  147. this.axios
  148. .get('/api/v1/thread/' + this.$route.params.id)
  149. .then(res => {
  150. this.$store.commit('setThread', res.data)
  151. this.$store.commit('setNextURL', res.data.meta.nextURL)
  152. this.$store.commit('setPosts', res.data.Posts)
  153. }).catch(AjaxErrorHandler(this.$store))
  154. }
  155. }
  156. </script>
  157. <style lang='scss' scoped>
  158. @import '../../assets/scss/variables.scss';
  159. .thread_header {
  160. display: flex;
  161. justify-content: space-between;
  162. @at-root #{&}__thread_title {
  163. @include text($font--role-default, 3rem, 400);
  164. margin-bottom: 1rem;
  165. @at-root #{&}--app_header {
  166. position: fixed;
  167. width: 80%;
  168. z-index: 2;
  169. text-align: center;
  170. left: 0;
  171. font-size: 2rem;
  172. top: 1rem;
  173. opacity: 0;
  174. pointer-events: none;
  175. transition: opacity 0.2s;
  176. @at-root #{&}-show {
  177. opacity: 1;
  178. transition: opacity 0.2s;
  179. }
  180. }
  181. }
  182. @at-root #{&}__reply_button {
  183. height: 3rem;
  184. position: fixed;
  185. right: 10%;
  186. margin-top: 0.75rem;
  187. }
  188. }
  189. .posts {
  190. width: 80%;
  191. }
  192. </style>