Thread.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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. @loadNext='loadNextPosts'
  29. @loadPrevious='loadPreviousPosts'
  30. >
  31. <thread-post-placeholder
  32. v-if='$store.state.thread.loadingPosts === "previous"'
  33. v-for='n in $store.state.thread.previousPostsCount'
  34. >
  35. </thread-post-placeholder>
  36. <thread-post
  37. v-for='(post, index) in posts'
  38. @reply='replyUser'
  39. @goToPost='goToPost'
  40. @like='updatePostLike'
  41. :post='post'
  42. :show-reply='true'
  43. :highlight='highlightedPostIndex === index'
  44. :class='{"post--last": index === posts.length-1}'
  45. ref='posts'
  46. ></thread-post>
  47. <thread-post-placeholder
  48. v-if='$store.state.thread.loadingPosts === "next"'
  49. v-for='n in $store.state.thread.nextPostsCount'
  50. >
  51. </thread-post-placeholder>
  52. </scroll-load>
  53. </div>
  54. </div>
  55. </template>
  56. <script>
  57. import InputEditor from '../InputEditor'
  58. import ScrollLoad from '../ScrollLoad'
  59. import ThreadPost from '../ThreadPost'
  60. import ThreadPostPlaceholder from '../ThreadPostPlaceholder'
  61. import throttle from 'lodash.throttle'
  62. import AjaxErrorHandler from '../../assets/js/errorHandler'
  63. export default {
  64. name: 'Thread',
  65. components: {
  66. InputEditor,
  67. ScrollLoad,
  68. ThreadPost,
  69. ThreadPostPlaceholder
  70. },
  71. data () {
  72. return {
  73. headerTitle: false,
  74. highlightedPostIndex: null
  75. }
  76. },
  77. computed: {
  78. thread () {
  79. return this.$store.state.thread.thread;
  80. },
  81. posts () {
  82. return this.$store.getters.sortedPosts;
  83. },
  84. replyUsername () {
  85. return this.$store.state.thread.reply.username
  86. },
  87. editor: {
  88. get () { return this.$store.state.thread.editor.value },
  89. set (val) {
  90. this.$store.commit('setThreadEditorValue', val)
  91. }
  92. },
  93. editorState () { return this.$store.state.thread.editor.show }
  94. },
  95. methods: {
  96. showEditor () {
  97. this.$store.commit('setThreadEditorState', true);
  98. },
  99. hideEditor () {
  100. this.$store.commit('setThreadEditorState', false);
  101. this.clearReply()
  102. },
  103. clearReply () {
  104. this.$store.commit({
  105. type: 'setReply',
  106. username: '',
  107. id: ''
  108. });
  109. },
  110. replyThread () {
  111. this.clearReply();
  112. this.showEditor();
  113. },
  114. replyUser (id, username) {
  115. this.$store.commit({
  116. type: 'setReply',
  117. username,
  118. id
  119. });
  120. this.showEditor();
  121. },
  122. addPost () {
  123. this.$store.dispatch('addPostAsync', this);
  124. },
  125. updatePostLike (id, state) {
  126. console.log(arguments)
  127. },
  128. loadNextPosts () {
  129. let vue = this
  130. this.$store.dispatch('loadPostsAsync', { vue, previous: false });
  131. },
  132. loadPreviousPosts () {
  133. let vue = this
  134. this.$store.dispatch('loadPostsAsync', { vue, previous: true });
  135. },
  136. loadInitialPosts () {
  137. this.$store.dispatch('loadInitialPostsAsync', this)
  138. },
  139. goToPost (number, getPostNumber) {
  140. let pushRoute = postNumber => {
  141. if(this.$route.params.post_number === postNumber) {
  142. this.highlightPost(postNumber)
  143. } else {
  144. this.$router.push({ name: 'thread-post', params: { post_number: postNumber } })
  145. }
  146. }
  147. if(getPostNumber) {
  148. this.axios
  149. .get('/api/v1/post/' + number)
  150. .then(res => pushRoute(res.data.postNumber) )
  151. } else {
  152. pushRoute(number)
  153. }
  154. },
  155. scrollTo (postNumber, cb) {
  156. for(var i = 0; i < this.posts.length; i++) {
  157. let post = this.posts[i]
  158. if(post.postNumber === postNumber) {
  159. this.$nextTick(() => {
  160. let postTop = this.$refs.posts[i].$el.getBoundingClientRect().top
  161. let header = this.$refs.title.getBoundingClientRect().height
  162. window.scrollTo(0, postTop - header - 32)
  163. if(cb) cb(i, post)
  164. })
  165. break;
  166. }
  167. }
  168. },
  169. highlightPost (postNumber) {
  170. this.scrollTo(postNumber, (i) => {
  171. this.highlightedPostIndex = i
  172. if(this.highlightedPostIndex === i) {
  173. setTimeout(() => this.highlightedPostIndex = null, 3000)
  174. }
  175. })
  176. }
  177. },
  178. watch: {
  179. '$route': 'loadInitialPosts'
  180. },
  181. created () {
  182. let self = this;
  183. let setHeader = function() {
  184. if(!self.$refs.title) return;
  185. if(self.$refs.title.getBoundingClientRect().top <= 32) {
  186. self.headerTitle = true;
  187. } else {
  188. self.headerTitle = false;
  189. }
  190. };
  191. setHeader();
  192. document.addEventListener('scroll', throttle(setHeader, 200));
  193. this.loadInitialPosts()
  194. }
  195. }
  196. </script>
  197. <style lang='scss' scoped>
  198. @import '../../assets/scss/variables.scss';
  199. .thread_header {
  200. display: flex;
  201. justify-content: space-between;
  202. @at-root #{&}__thread_title {
  203. @include text($font--role-default, 3rem, 400);
  204. width: calc(100% - 8rem);
  205. margin-bottom: 1rem;
  206. @at-root #{&}--app_header {
  207. position: fixed;
  208. width: 80%;
  209. z-index: 2;
  210. text-align: center;
  211. left: 0;
  212. font-size: 2rem;
  213. top: 0.5rem;
  214. opacity: 0;
  215. pointer-events: none;
  216. transition: opacity 0.2s;
  217. @at-root #{&}-show {
  218. opacity: 1;
  219. transition: opacity 0.2s;
  220. }
  221. }
  222. }
  223. @at-root #{&}__reply_button {
  224. height: 3rem;
  225. position: fixed;
  226. right: 10%;
  227. margin-top: 0.75rem;
  228. }
  229. }
  230. .posts {
  231. width: 80%;
  232. background-color: #fff;
  233. padding: 0.5rem 1rem;
  234. border-radius: 0.25rem;
  235. }
  236. </style>