NEXT craftinamerica.org. Base setup for headless wordpress https://www.craftinamerica.org
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

list.vue 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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, sortFromRoute } 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. _getSortBy() {
  74. return this.sortBy
  75. ? this.sortBy
  76. : sortFromRoute(this.$route)
  77. },
  78. _getDispatchParams(perPage) {
  79. return {
  80. sortType: this._getSortBy(),
  81. params: {
  82. limit: perPage ? perPage : this.perPage,
  83. page: this.page
  84. }
  85. }
  86. },
  87. clearAllPosts() {
  88. if(!this.type) return console.error(`type: ${this.type}...`)
  89. const uppercaseType = this.type.toUpperCase() + 'S'
  90. this.$store.commit(`CLEAR_${uppercaseType}`)
  91. this.$store.commit(`${uppercaseType}_LOADED`)
  92. },
  93. async loadMorePosts() {
  94. if(!this.type) return console.warn(`this.type: ${type} not found...`)
  95. if(!this.keepFetching) return console.warn('nothing left to fetch...')
  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. },
  102. async getPosts() {
  103. let dispatchAction = `getMore${this.pType}`
  104. let params = this._getDispatchParams()
  105. // For episodes, or material sort we grab EVERYTHING
  106. const getAllClause = this.type == 'episode' || this.type == 'artist' && this._getSortBy() == sortTypes.material
  107. if(getAllClause) {
  108. dispatchAction = `getAll${this.pType}`
  109. params = this._getDispatchParams(-1)
  110. // Clear sortType if this is an episodes list
  111. params.sortType = this.type == 'episode' ? null : params.sortType
  112. }
  113. let res = []
  114. // We always grab all pages on hero init so no need to do it here
  115. if(this.pType && this.keepFetching && this.type != 'page') {
  116. res = await this.$store.dispatch(dispatchAction, params)
  117. }
  118. // Stop trying to load more posts
  119. if(res && !res.length || getAllClause) {
  120. this.keepFetching = false
  121. if(!res.length) console.warn(`Empty response for ${this.type}:`, res.length)
  122. if(getAllClause) console.warn(`Fetched all responses for ${this.type}:`, res.length)
  123. }
  124. },
  125. async getPageForType(type) {
  126. await this._getAll('page', this.$store)
  127. if(!this.allPages) return console.warn('no pages in state', this)
  128. const page = this.allPages.filter(page => page.slug == `${type}s`)[0]
  129. if(!page) return console.warn(`no page for ${type} found`)
  130. return page
  131. },
  132. // _setHeroInfo(post) {} from mixin
  133. // _clearHero(store) {} from mixin
  134. async checkAndSetHero(type) {
  135. this._clearHero(this.$store)
  136. const page = await this.getPageForType(type)
  137. // We always set a hero no matter what
  138. // Because the hero component will deal
  139. // with how to render based on hero.url
  140. this.$store.commit('SET_HERO', this._setHeroInfo(page))
  141. },
  142. setIntersectionLoader() {
  143. if(!this.type) return console.error('cannot setup intersection handler for undefined type')
  144. // KeepFetching is UNSET for certain post types and sort types in `loadMorePosts()`
  145. if(!this.keepFetching) return console.warn('cannot setup intersection handler keepFetching is set false')
  146. console.warn('Setting interesection loader...')
  147. // Always unset and reset the intersection loader
  148. this.unsetIntersectionLoader()
  149. const onIntersect = (entries, observer) => {
  150. entries.forEach(entry => {
  151. if (!entry.isIntersecting || this.loadingFetched) return
  152. setTimeout(() => this.loadMorePosts(), TIMEOUT)
  153. })
  154. }
  155. this.observer = new IntersectionObserver(onIntersect, { threshold: 0.80 })
  156. this.observer.observe(document.querySelector(INTERSECT_SELECTOR))
  157. },
  158. unsetIntersectionLoader() {
  159. const footerEl = document.querySelector(INTERSECT_SELECTOR)
  160. try {
  161. if(!footerEl) throw `cannot unset intersection handler missing el: ${footerEl}`
  162. if(!this.observer) throw `cannot unset intersection handler missing observer: ${this.observer}`
  163. this.observer.unobserve(footerEl)
  164. this.observer.disconnect()
  165. } catch (error) {
  166. console.error(error)
  167. }
  168. },
  169. clearAndInitPostList(caller) {
  170. console.log(`clearAndInitPostList from ${caller}...`)
  171. console.log('---')
  172. this.page = 0
  173. this.keepFetching = true
  174. this.checkAndSetHero(this.type)
  175. // Fires when loading from home
  176. // Clear any preloaded posts (from home, etc.)
  177. this.clearAllPosts()
  178. this.loadMorePosts()
  179. this.setIntersectionLoader()
  180. }
  181. },
  182. watch: {
  183. // This only fires navigating from
  184. // a list page, to another list page
  185. // and the post type has changed
  186. type(newType, oldType) {
  187. if(!postTypes.includes(newType)) return console.warn('type not valid...')
  188. // !:Ignore these default sorted types and let the route watcher deal with it
  189. const ignore = ['event', 'exhibition', 'post']
  190. if(ignore.includes(this.type)) return
  191. this.clearAndInitPostList('type watcher')
  192. },
  193. // Only fire if the sort type has changed
  194. // and the post type is the same
  195. // and both sorts are valid
  196. $route(to, from) {
  197. const validSorts = Object.values(sortTypes)
  198. if (
  199. typeFromRoute(to) == typeFromRoute(from) &&
  200. sortFromRoute(to) != sortFromRoute(from) &&
  201. // Post slug sometimes appears as a sort so we check it against known sorts
  202. validSorts.includes(sortFromRoute(to)) && validSorts.includes(sortFromRoute(from))
  203. ) {
  204. this.clearAndInitPostList('$route watcher')
  205. }
  206. }
  207. },
  208. mounted() {
  209. // This only fires navigating from a single page, to a list page
  210. this.clearAndInitPostList('mounted')
  211. },
  212. beforeDestroy() {
  213. this.unsetIntersectionLoader()
  214. }
  215. }
  216. </script>
  217. <style lang="postcss">
  218. // prettier-ignore
  219. @import '../sss/variables.sss'
  220. @import '../sss/theme.sss'
  221. .page--list article
  222. > header
  223. padding: 1em
  224. > h1
  225. margin: 0
  226. > .content
  227. padding: 0
  228. width: 100%
  229. > footer
  230. padding: $ms-0
  231. /* posts not grid list */
  232. .posts
  233. list-style: none
  234. grid-gap: $ms--2
  235. .post
  236. width: 100%
  237. /* posts in grid list */
  238. .posts.is-grid
  239. width: 100%
  240. display: grid
  241. grid-template-columns: repeat(1, 1fr)
  242. align-items: start
  243. /* This is important for how the grid lines up to the page */
  244. justify-content: right
  245. .post img
  246. width: 100%
  247. @media (min-width: $medium)
  248. .page--list
  249. &.f-col
  250. flex-direction: row
  251. > article
  252. margin: 0 $ms--2 0 0
  253. .posts.is-grid
  254. grid-template-columns: repeat(3, 1fr)
  255. </style>