Search.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. <template>
  2. <div class='route_container'>
  3. <h1>Search results for '{{$route.params.q}}'</h1>
  4. <transition name='fade' mode='out-in'>
  5. <div class='search__results' key='results' v-if='posts && posts.length'>
  6. <scroll-load
  7. :loading='loading'
  8. @loadNext='loadNextPage'
  9. >
  10. <thread-post
  11. class='search__post'
  12. v-for='post in posts'
  13. :post='post'
  14. :show-thread='true'
  15. :click-for-post='true'
  16. ></thread-post>
  17. <thread-post-placeholder
  18. class='search__post'
  19. v-if='loading'
  20. v-for='n in next'
  21. ></thread-post-placeholder>
  22. </scroll-load>
  23. </div>
  24. <div
  25. class='overlay_message search__overlay_message'
  26. v-else-if='posts && !posts.length'
  27. key='no results'
  28. >
  29. <span class='fa fa-exclamation-circle'></span>
  30. No results found
  31. </div>
  32. <div
  33. class='search__results'
  34. key='loading'
  35. v-else
  36. >
  37. <thread-post-placeholder class='search__post'
  38. ></thread-post-placeholder>
  39. </div>
  40. </transition>
  41. </div>
  42. </template>
  43. <script>
  44. import LoadingIcon from '../LoadingIcon'
  45. import ScrollLoad from '../ScrollLoad'
  46. import ThreadPost from '../ThreadPost'
  47. import ThreadPostPlaceholder from '../ThreadPostPlaceholder'
  48. import AjaxErrorHandler from '../../assets/js/errorHandler'
  49. export default {
  50. name: 'Search',
  51. components: {
  52. LoadingIcon,
  53. ThreadPost,
  54. ScrollLoad,
  55. ThreadPostPlaceholder
  56. },
  57. data () {
  58. return {
  59. posts: null,
  60. next: 0,
  61. offset: 0,
  62. loading: false,
  63. postsLoaded: false
  64. }
  65. },
  66. methods: {
  67. //Delay of 300ms so that you won't see
  68. //a flash of placeholders when it's not necessary
  69. setDelayedPostsNull () {
  70. this.postsLoaded = false
  71. setTimeout(() => {
  72. if(!this.postsLoaded) {
  73. this.posts = null
  74. }
  75. }, 300)
  76. },
  77. getResults () {
  78. this.$store.dispatch('setTitle', 'Search | ' + this.$route.params.q)
  79. this.setDelayedPostsNull()
  80. this.axios
  81. .get('/api/v1/search?q=' + this.$route.params.q)
  82. .then(res => {
  83. this.posts = res.data.posts
  84. this.next = res.data.next
  85. this.offset = res.data.offset
  86. this.postsLoaded = true
  87. })
  88. .catch(AjaxErrorHandler(this.$store))
  89. },
  90. loadNextPage () {
  91. if(this.next === 0) return
  92. this.loading = true
  93. this.axios
  94. .get(
  95. `/api/v1/search?q=${this.$route.params.q}&offset=${this.offset}`
  96. )
  97. .then(res => {
  98. this.posts.push(...res.data.posts)
  99. this.next = res.data.next
  100. this.offset = res.data.offset
  101. this.loading = false
  102. this.postsLoaded = true
  103. })
  104. .catch(e => {
  105. this.loading = false
  106. this.postsLoaded = true
  107. AjaxErrorHandler(this.$store)(e)
  108. })
  109. }
  110. },
  111. watch: {
  112. '$route.params': 'getResults'
  113. },
  114. mounted () {
  115. this.$store.dispatch('setTitle', 'Search | ' + this.$route.params.q)
  116. this.getResults()
  117. }
  118. }
  119. </script>
  120. <style lang='scss' scoped>
  121. @import '../../assets/scss/variables.scss';
  122. .search {
  123. @at-root #{&}__post {
  124. background-color: #fff;
  125. padding-left: 0.75rem;
  126. margin-bottom: 1rem;
  127. border: none;
  128. transition: box-shadow 0.2s;
  129. @extend .shadow_border;
  130. &:hover {
  131. @extend .shadow_border--hover;
  132. }
  133. }
  134. @at-root #{&}__overlay_message {
  135. margin-top: 5rem;
  136. @at-root #{&}--loading span {
  137. margin-bottom: 1rem;
  138. }
  139. }
  140. }
  141. </style>