NEXT craftinamerica.org. Base setup for headless wordpress https://www.craftinamerica.org
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

list.vue 10KB

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