NEXT craftinamerica.org. Base setup for headless wordpress https://www.craftinamerica.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

list.vue 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <template lang="pug">
  2. .page--list.f-col.between
  3. article.f-grow
  4. header.center.t-up
  5. .title.f-row
  6. h3 {{ type }} list
  7. span(v-if="sortBy")
  8. h3 sorted {{ sortBy.replace('-', ' ') }}
  9. h3(v-if="!loaded") loading...
  10. .content(v-else-if="allPagesLoaded && type && allPages[type]" v-html="allPages[type].content")
  11. .posts(v-if="posts && loaded" :class="{ 'is-grid': grid }")
  12. section(v-for="(post, i) in posts" :key="post.slug").shadow.post
  13. card(:content="post" :type="type" :wide="type == 'exhibition' && i > 1 || type == 'event' && i > 1 ")
  14. footer
  15. p {{ `${type} count: ${Object.values(posts).length}` }}
  16. p {{ `show sidebar: ${sidebar}` }}
  17. sidebar(v-if="sidebar" :type="`${type}`" layout="list")
  18. </template>
  19. <script>
  20. import featuredImage from '@/components/featured-image'
  21. import card from '@/components/card'
  22. import sidebar from '@/components/sidebars/sidebar'
  23. import { postTypeGetters, scrollTop } from './mixin-post-types'
  24. import { convertTitleCase, typeFromRoute, sortTypes } from '@/utils/helpers'
  25. const TIMEOUT = 1
  26. export default {
  27. components: { sidebar, featuredImage, card },
  28. props: {
  29. sidebar: { type: Boolean },
  30. grid: { type: Boolean },
  31. sortBy: { type: String },
  32. },
  33. mixins: [postTypeGetters, scrollTop],
  34. data() {
  35. return {
  36. page: 1,
  37. perPage: 9
  38. }
  39. },
  40. computed: {
  41. type() {
  42. // Checks for type and fixes Episodes route edge case
  43. return typeFromRoute(this.$route)
  44. },
  45. dispatchName() {
  46. let type = convertTitleCase(this.type) + 's'
  47. return this.sortBy ? `getAll${type.split('/')[0]}` : `getAll${type}`
  48. },
  49. getMoreName() {
  50. let type = convertTitleCase(this.type) + 's'
  51. return this.sortBy ? `getMore${type.split('/')[0]}` : `getMore${type}`
  52. },
  53. loaded() {
  54. let type = convertTitleCase(this.type) + 's'
  55. if (!type) return
  56. return this[`all${type}Loaded`]
  57. },
  58. posts() {
  59. let type = convertTitleCase(this.type) + 's'
  60. if (!type) return
  61. return this[`all${type}`]
  62. },
  63. },
  64. methods: {
  65. loadMorePosts() {
  66. // console.log('loading...')
  67. this.page++
  68. this.getPosts(false)
  69. },
  70. getPosts(clear) {
  71. // Limit
  72. let params = {
  73. limit: this.perPage,
  74. page: this.page
  75. }
  76. // Sorting
  77. let sort = this.sortBy
  78. ? this.sortBy
  79. : this.$route.path
  80. .split('/')
  81. .filter(p => p)
  82. .pop()
  83. // console.log('Gettings posts:', this.type)
  84. // if (!Object.values(sortTypes).includes(sort)) {
  85. // console.log('Sort not found:', sort)
  86. // sort = null
  87. // }
  88. // Don't dispatch if there's no type
  89. if (this.type && this.dispatchName && clear) {
  90. this.$store.dispatch(
  91. this.dispatchName,
  92. { sortType: sort, params }
  93. )
  94. if (this.type == 'event') {
  95. this.$store.dispatch(
  96. 'getAllExhibitions',
  97. {
  98. sortType: sortTypes.currentAndUpcoming,
  99. params
  100. }
  101. )
  102. }
  103. if (this.type == 'exhibition') {
  104. this.$store.dispatch(
  105. 'getAllEvents',
  106. {
  107. sortType: sortTypes.currentAndUpcoming,
  108. params
  109. }
  110. )
  111. }
  112. // Add to existing
  113. } else if (!clear) {
  114. this.$store.dispatch(
  115. this.getMoreName,
  116. { sortType: sort, params }
  117. )
  118. }
  119. },
  120. async checkAndSetHero(type) {
  121. if (!this['allPagesLoaded']) {
  122. await this.$store.dispatch('getAllPages', { sortType: null, params: null })
  123. }
  124. // We always set a hero no matter what
  125. // Because the hero component will deal
  126. // with how to render based on hero.url
  127. const page = this.allPages.filter(
  128. page => page.slug == type + 's',
  129. )[0]
  130. let hero = { url: null, heroType: null }
  131. // Clear the hero and bail
  132. if(!page) return this.$store.commit('SET_HERO', hero)
  133. console.log('setting hero:', page)
  134. hero.url = page.featured
  135. hero.heroType = 'image'
  136. if (
  137. page.hero &&
  138. JSON.parse(page.hero) &&
  139. JSON.parse(page.hero).url
  140. ) {
  141. hero = JSON.parse(page.hero)
  142. hero.heroType = 'video'
  143. }
  144. // No featured or youTube
  145. if (!hero.url) {
  146. hero.heroType = null
  147. }
  148. // Set the hero text to the post title or excerpt
  149. hero.text = page && page.excerpt ? page.excerpt : page.title
  150. this.$store.commit('SET_HERO', hero)
  151. },
  152. scrollTo(hashtag) {
  153. setTimeout(() => {
  154. location.href = hashtag
  155. }, TIMEOUT)
  156. },
  157. setIntersectionLoader() {
  158. window.removeEventListener("load", e => {}, false)
  159. window.addEventListener("load", e => {
  160. const footerEl = document.querySelector(".footer-wrapper footer")
  161. const observer = new IntersectionObserver(this.loadMorePosts, {
  162. rootMargin: "0px 0px 20px 0px",
  163. })
  164. observer.observe(footerEl)
  165. }, false)
  166. }
  167. },
  168. watch: {
  169. $route(to, from) {
  170. this.checkAndSetHero(this.type)
  171. let sort = to.path
  172. .split('/')
  173. .filter(p => p)
  174. .pop()
  175. if (!Object.values(sortTypes).includes(sort)) {
  176. // console.warn('sort not found:', sort)
  177. sort = null
  178. }
  179. // TODO: Track last loaded page per post type
  180. // Less http calls
  181. this.page = 1
  182. this.setIntersectionLoader()
  183. this.getPosts(true)
  184. },
  185. },
  186. mounted() {
  187. if (this.$route.hash) {
  188. setTimeout(() => this.scrollTo(this.$route.hash), TIMEOUT)
  189. }
  190. this.setIntersectionLoader()
  191. },
  192. created() {
  193. let type = convertTitleCase(this.type) + 's'
  194. this.checkAndSetHero(this.type)
  195. // We also need to check if only ONE has been loaded because
  196. // coming from the homepage there will only be 1 item
  197. if (!this[`all${type}Loaded`] || this[`all${type}`].length < 2) this.getPosts(true)
  198. },
  199. }
  200. </script>
  201. <style lang="postcss">
  202. // prettier-ignore
  203. @import '../sss/variables.sss'
  204. @import '../sss/theme.sss'
  205. .page--list
  206. /* background-color: white */
  207. article
  208. > header
  209. /* padding: 1em 0 1em 0 */
  210. padding: 1em
  211. > h1
  212. margin: 0
  213. > .content
  214. padding: 0
  215. width: 100%
  216. /* posts not grid list */
  217. ul
  218. img
  219. max-width: 50%
  220. .is-grid
  221. display: flex
  222. flex-direction: row
  223. flex-wrap: wrap
  224. justify-content: space-between
  225. /* extra padding required? */
  226. /* padding: $ms-0 */
  227. /* background-color: white */
  228. section
  229. width: 32.5%
  230. /* 4 column grid see how dense this becomes */
  231. /* width: 24% */
  232. ul
  233. flex-wrap: wrap
  234. list-style: none
  235. img
  236. max-width: 100%
  237. @media (min-width: $medium)
  238. .page--list.f-col
  239. flex-direction: row
  240. </style>