NEXT craftinamerica.org. Base setup for headless wordpress https://www.craftinamerica.org
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

list.vue 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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(
  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. li(v-for="(post, i) in posts" :key="post.slug").post.shadow
  16. card(:content="post" :type="type" :wide="isWide")
  17. //- Important: Do NOT remove this! Required for intersection observer
  18. footer
  19. p(v-if="loadingFetched") loading more {{ type }}...
  20. p(v-if="showMeta") {{ `${type} count: ${Object.values(posts).length}` }}
  21. p(v-if="showMeta") {{ `show sidebar: ${sidebar}` }}
  22. sidebar(v-if="sidebar" :type="`${type}`" layout="list")
  23. </template>
  24. <script>
  25. import featuredImage from '@/components/featured-image'
  26. import card from '@/components/card'
  27. import sidebar from '@/components/sidebars/sidebar'
  28. import { postTypeGetters, scrollTop, heroUtils } from './mixin-post-types'
  29. import { postTypes, sortTypes, convertTitleCase, typeFromRoute } from '@/utils/helpers'
  30. const TIMEOUT = 1
  31. const INTERSECT_SELECTOR = '.page--list > article footer'
  32. export default {
  33. components: { sidebar, featuredImage, card },
  34. props: {
  35. sidebar: { type: Boolean },
  36. grid: { type: Boolean },
  37. sortBy: { type: String },
  38. },
  39. mixins: [postTypeGetters, scrollTop, heroUtils],
  40. data() {
  41. return {
  42. showMeta:false,
  43. page: 0,
  44. perPage: 21,
  45. keepFetching: true,
  46. loadingFetched: false,
  47. observer: null
  48. }
  49. },
  50. computed: {
  51. type() {
  52. // Checks for type and fixes Episodes route edge case
  53. return typeFromRoute(this.$route)
  54. },
  55. pType() {
  56. if(!typeFromRoute(this.$route)) return
  57. return this.sortBy ? `${convertTitleCase(typeFromRoute(this.$route).split('/')[0])}s` : `${convertTitleCase(this.type)}s`
  58. },
  59. loaded() {
  60. if (!this.pType) return
  61. return this[`all${this.pType}Loaded`]
  62. },
  63. posts() {
  64. if (!this.pType) return
  65. return this[`all${this.pType}`]
  66. },
  67. isWide() {
  68. const wide = ['exhibition', 'event']
  69. return wide.includes(this.type)
  70. }
  71. },
  72. methods: {
  73. clearAllPosts() {
  74. if(!this.type) return console.error(`type: ${this.type}...`)
  75. const uppercaseType = this.type.toUpperCase() + 'S'
  76. this.$store.commit(`CLEAR_${uppercaseType}`)
  77. this.$store.commit(`${uppercaseType}_LOADED`)
  78. },
  79. async loadMorePosts() {
  80. if(!this.type) return console.warn(`this.type: ${type} not found...`)
  81. if(!this.keepFetching) return console.warn('nothing left to fetch...')
  82. this.loadingFetched = true
  83. // console.warn(`loading page ${this.page + 1} of ${type} posts: ${this.page * this.perPage + 1} to ${(this.page + 1) * this.perPage}`)
  84. this.page++
  85. await this.getPosts()
  86. },
  87. _getSortBy() {
  88. return this.sortBy
  89. ? this.sortBy
  90. : this.$route.path
  91. .split('/')
  92. .filter(p => p)
  93. .pop()
  94. },
  95. _getDispatchParams(perPage) {
  96. return {
  97. sortType: this._getSortBy(),
  98. params: {
  99. limit: perPage ? perPage : this.perPage,
  100. page: this.page
  101. }
  102. }
  103. },
  104. async getPosts() {
  105. let dispatchAction = `getMore${this.pType}`
  106. let params = this._getDispatchParams()
  107. // For episodes, or material sort we grab EVERYTHING
  108. const getAllClause = this.type == 'episode' || this.type == 'artist' && this._getSortBy() == sortTypes.material
  109. if(getAllClause) {
  110. dispatchAction = `getAll${this.pType}`
  111. params = this._getDispatchParams(-1)
  112. // Clear sortType if this is an episodes list
  113. params.sortType = this.type == 'episode' ? null : params.sortType
  114. }
  115. let res = null
  116. // We always grab all pages on hero init so no need to do it here
  117. if(this.pType && this.keepFetching && this.type != 'page') {
  118. res = await this.$store.dispatch(dispatchAction, params)
  119. }
  120. // Stop trying to load more posts
  121. if(res && !res.length || getAllClause) {
  122. console.warn(`empty response for ${this.type}:`, res)
  123. this.keepFetching = false
  124. }
  125. this.loadingFetched = false
  126. },
  127. async getPageForType(type) {
  128. await this._getAll('page', this.$store)
  129. if(!this.allPages) return console.warn('no pages in state', this)
  130. const page = this.allPages.filter(page => page.slug == `${type}s`)[0]
  131. if(!page) return console.warn(`no page for ${type} found`)
  132. return page
  133. },
  134. // _setHeroInfo(post) {} from mixin
  135. // _clearHero(store) {} from mixin
  136. async checkAndSetHero(type) {
  137. this._clearHero(this.$store)
  138. const page = await this.getPageForType(type)
  139. // We always set a hero no matter what
  140. // Because the hero component will deal
  141. // with how to render based on hero.url
  142. this.$store.commit('SET_HERO', this._setHeroInfo(page))
  143. },
  144. setIntersectionLoader() {
  145. if(!this.type) return console.error('cannot setup intersection handler for undefined type')
  146. // KeepFetching is UNSET for certain post types and sort types in `loadMorePosts()`
  147. if(!this.keepFetching) return console.warn('cannot setup intersection handler keepFetching is set false')
  148. console.warn('Setting interesection loader...')
  149. // Always unset and reset the intersection loader
  150. this.unsetIntersectionLoader()
  151. const onIntersect = (entries, observer) => {
  152. entries.forEach(entry => {
  153. if (!entry.isIntersecting || this.loadingFetched) return
  154. setTimeout(() => this.loadMorePosts(), TIMEOUT)
  155. })
  156. }
  157. this.observer = new IntersectionObserver(onIntersect, { threshold: 0.80 })
  158. this.observer.observe(document.querySelector(INTERSECT_SELECTOR))
  159. console.log('--- observer')
  160. console.log(this.observer)
  161. },
  162. unsetIntersectionLoader() {
  163. const footerEl = document.querySelector(INTERSECT_SELECTOR)
  164. try {
  165. if(!footerEl) throw `cannot unset intersection handler missing el: ${footerEl}`
  166. if(!this.observer) throw `cannot unset intersection handler missing observer: ${this.observer}`
  167. console.warn('unsetting intersection handler on:', footerEl, this.sortBy)
  168. this.observer.unobserve(footerEl)
  169. this.observer.disconnect()
  170. } catch (error) {
  171. console.error(error)
  172. }
  173. },
  174. clearAndInitPostList(caller) {
  175. console.log(`clearAndInitPostList from ${caller}...`)
  176. console.log('---')
  177. this.page = 0
  178. this.keepFetching = true
  179. this.checkAndSetHero(this.type)
  180. // Fires when loading from home
  181. // Clear any preloaded posts (from home, etc.)
  182. this.clearAllPosts()
  183. this.loadMorePosts()
  184. this.setIntersectionLoader()
  185. }
  186. },
  187. watch: {
  188. // This only fires navigating from
  189. // a list page, to another list page
  190. type(newType, oldType) {
  191. if(!postTypes.includes(newType)) return console.warn('type not found...')
  192. this.clearAndInitPostList('watcher')
  193. }
  194. },
  195. mounted() {
  196. // This only fires navigating from a single page, to a list page
  197. this.clearAndInitPostList('mounted')
  198. },
  199. beforeDestroy() {
  200. this.unsetIntersectionLoader()
  201. }
  202. }
  203. </script>
  204. <style lang="postcss">
  205. // prettier-ignore
  206. @import '../sss/variables.sss'
  207. @import '../sss/theme.sss'
  208. .page--list article
  209. > header
  210. padding: 1em
  211. > h1
  212. margin: 0
  213. > .content
  214. padding: 0
  215. width: 100%
  216. > footer
  217. padding: $ms-0
  218. /* posts not grid list */
  219. .posts
  220. list-style: none
  221. grid-gap: $ms--2
  222. .post
  223. width: 100%
  224. /* posts in grid list */
  225. .posts.is-grid
  226. width: 100%
  227. display: grid
  228. grid-template-columns: repeat(1, 1fr)
  229. align-items: start
  230. /* This is important for how the grid lines up to the page */
  231. justify-content: right
  232. .post img
  233. width: 100%
  234. @media (min-width: $medium)
  235. .page--list
  236. &.f-col
  237. flex-direction: row
  238. > article
  239. margin: 0 $ms--2 0 0
  240. .posts.is-grid
  241. grid-template-columns: repeat(3, 1fr)
  242. </style>