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 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. },
  160. unsetIntersectionLoader() {
  161. const footerEl = document.querySelector(INTERSECT_SELECTOR)
  162. try {
  163. if(!footerEl) throw `cannot unset intersection handler missing el: ${footerEl}`
  164. if(!this.observer) throw `cannot unset intersection handler missing observer: ${this.observer}`
  165. this.observer.unobserve(footerEl)
  166. this.observer.disconnect()
  167. } catch (error) {
  168. console.error(error)
  169. }
  170. },
  171. clearAndInitPostList(caller) {
  172. console.log(`clearAndInitPostList from ${caller}...`)
  173. console.log('---')
  174. this.page = 0
  175. this.keepFetching = true
  176. this.checkAndSetHero(this.type)
  177. // Fires when loading from home
  178. // Clear any preloaded posts (from home, etc.)
  179. this.clearAllPosts()
  180. this.loadMorePosts()
  181. this.setIntersectionLoader()
  182. }
  183. },
  184. watch: {
  185. // This only fires navigating from
  186. // a list page, to another list page
  187. // and the post type has changed
  188. type(newType, oldType) {
  189. if(!postTypes.includes(newType)) return console.warn('type not found...')
  190. this.clearAndInitPostList('type watcher')
  191. },
  192. // Only fire if the sort type has changed
  193. // and the post type is the same
  194. $route(to, from) {
  195. const findSortBy = route => {
  196. return route.sortBy ? route.sortBy : route.fullPath
  197. .split('/')
  198. .filter(p => p)
  199. .pop()
  200. }
  201. if (
  202. findSortBy(to) != findSortBy(from) &&
  203. typeFromRoute(to) == typeFromRoute(from) &&
  204. // Post slug sometimes appears as a sort
  205. sortTypes.includes(findSortBy(to)) && sortTypes.includes(findSortBy(from))
  206. ) {
  207. this.clearAndInitPostList('$route watcher')
  208. }
  209. }
  210. },
  211. mounted() {
  212. // This only fires navigating from a single page, to a list page
  213. this.clearAndInitPostList('mounted')
  214. },
  215. beforeDestroy() {
  216. this.unsetIntersectionLoader()
  217. }
  218. }
  219. </script>
  220. <style lang="postcss">
  221. // prettier-ignore
  222. @import '../sss/variables.sss'
  223. @import '../sss/theme.sss'
  224. .page--list article
  225. > header
  226. padding: 1em
  227. > h1
  228. margin: 0
  229. > .content
  230. padding: 0
  231. width: 100%
  232. > footer
  233. padding: $ms-0
  234. /* posts not grid list */
  235. .posts
  236. list-style: none
  237. grid-gap: $ms--2
  238. .post
  239. width: 100%
  240. /* posts in grid list */
  241. .posts.is-grid
  242. width: 100%
  243. display: grid
  244. grid-template-columns: repeat(1, 1fr)
  245. align-items: start
  246. /* This is important for how the grid lines up to the page */
  247. justify-content: right
  248. .post img
  249. width: 100%
  250. @media (min-width: $medium)
  251. .page--list
  252. &.f-col
  253. flex-direction: row
  254. > article
  255. margin: 0 $ms--2 0 0
  256. .posts.is-grid
  257. grid-template-columns: repeat(3, 1fr)
  258. </style>