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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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 }}s&nbsp;
  7. span(v-if="sortBy")
  8. h3 {{ sortBy.replace('-', ' ') }}
  9. h3(v-if="!loaded") loading...
  10. .content(
  11. v-else-if="allPagesLoaded && type && allPages[type]"
  12. v-html="allPages[type].content"
  13. )
  14. ul.posts.f-col(v-if="posts && loaded" :class="{ 'is-grid': grid }")
  15. template(v-for="(post, i) in posts" :key="post.slug")
  16. li.post.shadow(v-if="!post.inbetween" )
  17. card(:content="post" :type="type" :wide="isWide")
  18. li.post.shadow.inbetween.t-up.f-row.w-max(v-else-if="post.inbetween" :id="post.slug")
  19. p {{ post.slug }}
  20. //- Important: Do NOT remove this! Required for intersection observer
  21. footer
  22. p(v-if="loadingFetched") loading more {{ type }}...
  23. sidebar(v-if="sidebar" :type="`${type}`" layout="list")
  24. </template>
  25. <script>
  26. import featuredImage from '@/components/featured-image'
  27. import card from '@/components/card'
  28. import sidebar from '@/components/sidebars/sidebar'
  29. import { postTypeGetters, scrollTop, heroUtils } from './mixin-post-types'
  30. import { postTypes, sortTypes, convertTitleCase } from '@/utils/helpers'
  31. const TIMEOUT = 1
  32. const INTERSECT_SELECTOR = '.page--list > article footer'
  33. const wideTypes = ['event', 'exhibition', 'guide', 'post', 'publication']
  34. const gridTypes = ['episode', 'artist', 'object', 'short']
  35. const sansSidebarTypes = ['episode']
  36. export default {
  37. components: { sidebar, featuredImage, card },
  38. mixins: [postTypeGetters, scrollTop, heroUtils],
  39. data() {
  40. return {
  41. page: 0,
  42. perPage: 21,
  43. keepFetching: true,
  44. loadingFetched: false,
  45. observer: null
  46. }
  47. },
  48. computed: {
  49. type() {
  50. return postTypes.includes(this.$route.params.type) ? this.$route.params.type : 'post'
  51. },
  52. sortBy() {
  53. return this.$route.params.sortBy
  54. },
  55. grid() {
  56. return gridTypes.includes(this.type)
  57. },
  58. isWide() {
  59. return wideTypes.includes(this.type)
  60. },
  61. sidebar() {
  62. return !sansSidebarTypes.includes(this.type)
  63. },
  64. pType() {
  65. if(!this.type) return
  66. return `${convertTitleCase(this.type)}s`
  67. },
  68. loaded() {
  69. if (!this.pType) return
  70. return this[`all${this.pType}Loaded`]
  71. },
  72. posts() {
  73. if (!this.pType) return
  74. return this[`all${this.pType}`]
  75. },
  76. shouldLoadAllAtOnce() {
  77. return this.type == 'episode' ||
  78. this.type == 'artist' && this.sortBy == sortTypes.material ||
  79. this.type == 'artist' && this.sortBy == sortTypes.episode
  80. }
  81. },
  82. methods: {
  83. async loadMorePosts(shouldClear) {
  84. if(!this.keepFetching) return console.warn('Nothing left to fetch...')
  85. const getPosts = async (params, dispatchType) => {
  86. if(!this.type) throw `post type: ${this.type} not found...`
  87. console.log(`Getting more ${this.type} posts`)
  88. // We always grab all pages on hero init so no need to do it here
  89. return this.pType && this.type != 'page' ? await this.$store.dispatch(
  90. `get${dispatchType}${this.pType}`,
  91. { sortType: this.sortBy, params }
  92. ) : []
  93. }
  94. if(shouldClear) {
  95. this.$store.commit(`CLEAR_${this.pType.toUpperCase()}`)
  96. // Clear any state needed to track title inbetweens
  97. const hasInbetweens = ['artist']
  98. if(hasInbetweens.includes(this.type)) {
  99. this.$store.commit(`CLEAR_${this.pType.toUpperCase()}_SEEN`)
  100. }
  101. }
  102. try {
  103. this.loadingFetched = true
  104. this.page++
  105. const res = await getPosts(
  106. {
  107. limit: this.shouldLoadAllAtOnce ? -1 : this.perPage,
  108. page: this.page
  109. },
  110. this.shouldLoadAllAtOnce ? 'All' : 'More'
  111. )
  112. this.loadingFetched = false
  113. // Stop trying to load more posts
  114. if(res && !res.length || this.shouldLoadAllAtOnce) {
  115. this.keepFetching = false
  116. if(!res.length) console.warn(`Empty response for ${this.type}:`, res.length)
  117. if(this.shouldLoadAllAtOnce) console.warn(`Fetched all responses for ${this.type}:`, res.length)
  118. }
  119. } catch (err) { console.error(err) }
  120. },
  121. async getPage(type) {
  122. await this._getAllIfNotLoaded('page', this.$store)
  123. if(!this.allPages) throw 'no pages in state'
  124. const page = this.allPages.filter(page => page.slug == `${type}s`)[0]
  125. if(!page) throw `No page: ${type} found. Cannot set hero.`
  126. return page
  127. },
  128. // _setHeroInfo(post) {} from mixin
  129. // _clearHero(store) {} from mixin
  130. async checkAndSetHero(type) {
  131. this._clearHero(this.$store)
  132. try {
  133. const page = await this.getPage(type)
  134. // We always set a hero no matter what
  135. // Because the hero component will deal
  136. // with how to render based on hero.url
  137. this.$store.commit('SET_HERO', this._setHeroInfo(page))
  138. } catch (err) { console.error(err) }
  139. },
  140. setIntersectionLoader() {
  141. // KeepFetching is UNSET for certain post types and sort types in `loadMorePosts()`
  142. if(!this.keepFetching) return console.warn('Cannot setup intersection handler keepFetching is set false')
  143. // Always unset before setting the intersection loader
  144. this.unsetIntersectionLoader()
  145. // console.warn('Setting interesection loader...')
  146. this.observer = new IntersectionObserver(entries => {
  147. entries.forEach(entry => {
  148. if (!entry.isIntersecting || this.loadingFetched) return
  149. setTimeout(this.loadMorePosts, TIMEOUT)
  150. })
  151. }, { threshold: 0.80 })
  152. this.observer.observe(document.querySelector(INTERSECT_SELECTOR))
  153. },
  154. unsetIntersectionLoader() {
  155. const footerEl = document.querySelector(INTERSECT_SELECTOR)
  156. try {
  157. if(!footerEl) throw `Cannot unset intersection handler missing el: ${footerEl}`
  158. if(!this.observer) return
  159. this.observer.unobserve(footerEl)
  160. this.observer.disconnect()
  161. } catch (error) { console.error(error) }
  162. },
  163. initPostList() {
  164. this.page = 0
  165. this.keepFetching = true
  166. this.checkAndSetHero(this.type)
  167. // Clear any preloaded posts (from home, etc.)
  168. this.loadMorePosts(true)
  169. this.setIntersectionLoader()
  170. }
  171. },
  172. watch: {
  173. // This only fires navigating from a list page, to another list page
  174. type(newType) {
  175. if(!postTypes.includes(newType)) return console.warn('Type not valid...')
  176. // Ignore types with presorts so the sortBy watcher can handle them
  177. const ignore = ['event', 'exhibition', 'artist']
  178. if(ignore.includes(newType)) return
  179. this.initPostList()
  180. },
  181. sortBy(newSort) {
  182. if(!Object.values(sortTypes).includes(newSort)) return
  183. this.initPostList()
  184. }
  185. },
  186. mounted() {
  187. // This only fires navigating from a non-list page > list page
  188. this.initPostList()
  189. },
  190. beforeUnmount() {
  191. this.unsetIntersectionLoader()
  192. }
  193. }
  194. </script>
  195. <style lang="postcss">
  196. // prettier-ignore
  197. @import '../sss/variables.sss'
  198. @import '../sss/theme.sss'
  199. .page--list
  200. /* Puts the aside bar on top */
  201. flex-direction: column-reverse !important
  202. article
  203. > header
  204. padding: 1em
  205. > h1
  206. margin: 0
  207. > .content
  208. padding: 0
  209. width: 100%
  210. .title.f-row
  211. /* flex-direction: column */
  212. justify-content: flex-start
  213. > footer
  214. padding: $ms-0
  215. /* posts not grid list */
  216. .posts
  217. list-style: none
  218. grid-gap: $ms--2
  219. .post
  220. width: 100%
  221. &.inbetween
  222. grid-column: span 2
  223. background: white
  224. padding: 0.3em 0
  225. font-size: 1.3em
  226. > p
  227. margin: 0
  228. /* posts in grid list */
  229. .posts.is-grid
  230. width: 100%
  231. min-width: 185px
  232. display: grid
  233. grid-template-columns: repeat(2, 1fr)
  234. align-items: start
  235. /* This is important for how the grid lines up to the page */
  236. justify-content: right
  237. .post
  238. min-width: 177px
  239. img
  240. width: 100%
  241. @media (min-width: $medium)
  242. .page--list
  243. &.f-col
  244. flex-direction: row !important
  245. > article
  246. margin: 0 $ms--2 0 0
  247. header
  248. .title.f-row
  249. flex-direction: row
  250. justify-content: center
  251. .posts.is-grid
  252. grid-template-columns: repeat(3, 1fr)
  253. .post.inbetween
  254. grid-column: span 3
  255. </style>