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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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. return this.type == 'exhibition' || this.type == 'event'
  69. }
  70. },
  71. methods: {
  72. clearAllPosts() {
  73. if(!this.type) return console.error(`type: ${this.type}...`)
  74. const uppercaseType = this.type.toUpperCase() + 'S'
  75. this.$store.commit(`CLEAR_${uppercaseType}`)
  76. this.$store.commit(`${uppercaseType}_LOADED`)
  77. },
  78. async loadMorePosts() {
  79. const type = typeFromRoute(this.$route)
  80. if(!type) return console.warn(`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. if(this.type == 'episode' || this.type == 'artist' && this._getSortBy() == sortTypes.material) {
  109. dispatchAction = `getAll${this.pType}`
  110. params = this._getDispatchParams(-1)
  111. }
  112. const ignore = ['getMorePages']
  113. let res = null
  114. if(this.pType && !ignore.includes(dispatchAction) && this.keepFetching) {
  115. res = await this.$store.dispatch(dispatchAction, params)
  116. }
  117. // Stop trying to load more posts
  118. if(res && !res.length) {
  119. console.warn(`empty response for ${this.type}:`, res)
  120. this.keepFetching = false
  121. }
  122. this.loadingFetched = false
  123. },
  124. // _setHeroInfo(post) {} from mixin
  125. // _clearHero(store) {} from mixin
  126. async checkAndSetHero(type) {
  127. this._clearHero(this.$store)
  128. await this._getAll('page', this.$store)
  129. // We always set a hero no matter what
  130. // Because the hero component will deal
  131. // with how to render based on hero.url
  132. if(!this.allPages) return console.warn('no pages in state', this)
  133. const page = this.allPages.filter(
  134. page => page.slug == `${type}s`,
  135. )[0]
  136. if(!page) return console.warn(`no page for ${type} found`)
  137. this.$store.commit('SET_HERO', this._setHeroInfo(page))
  138. },
  139. setIntersectionLoader() {
  140. if(!this.type) return console.error('cannot setup intersection handler for undefined type')
  141. console.log('Setting interesection loader...')
  142. const footerEl = document.querySelector(INTERSECT_SELECTOR)
  143. if(!footerEl) return
  144. const onIntersect = (entries, observer) => {
  145. entries.forEach(entry => {
  146. if (!entry.isIntersecting || this.loadingFetched) return
  147. setTimeout(() => {
  148. this.loadMorePosts()
  149. }, TIMEOUT)
  150. })
  151. }
  152. this.observer = new IntersectionObserver(onIntersect, {
  153. threshold: 0.80
  154. })
  155. this.observer['_for_type'] = this.type
  156. this.observer.observe(footerEl)
  157. },
  158. unsetIntersectionLoader() {
  159. const footerEl = document.querySelector(INTERSECT_SELECTOR)
  160. if(!footerEl) return
  161. console.warn('unsetting intersection handler on:', footerEl, this.sortBy)
  162. this.observer.unobserve(footerEl)
  163. this.observer.disconnect()
  164. },
  165. clearAndInitPostList() {
  166. this.page = 0
  167. this.keepFetching = true
  168. this.checkAndSetHero(this.type)
  169. // Fires when loading from home
  170. // Clear any preloaded posts (from home, etc.)
  171. this.clearAllPosts()
  172. this.loadMorePosts()
  173. },
  174. routeInfoFromPath(fullPath) {
  175. const [ potentialType, potentialSort ] = fullPath.split('/').filter(e => e)
  176. return {
  177. sortBy: Object.values(sortTypes).includes(potentialSort) ? potentialSort : null,
  178. type: postTypes.includes(potentialType) ? potentialType : null
  179. }
  180. }
  181. },
  182. watch: {
  183. // This only fires navigating from
  184. // a list page, to another list page
  185. type(newType, oldType) {
  186. if(!postTypes.includes(newType)) return console.warn('type not found...')
  187. },
  188. $route(to, from) {
  189. console.log('---')
  190. const toPathInfo = this.routeInfoFromPath(to.fullPath)
  191. const fromPathInfo = this.routeInfoFromPath(from.fullPath)
  192. console.log('to :', toPathInfo)
  193. console.log('from :', fromPathInfo)
  194. // Always unset and reset the intersection loader
  195. this.unsetIntersectionLoader()
  196. if(!toPathInfo.sortBy != sortTypes.material) {
  197. this.setIntersectionLoader()
  198. }
  199. // Check if we changed sort method
  200. // from no sort to sorted
  201. // or sorted to no sort
  202. // or sort to a different sort(?)
  203. if(
  204. from.fullPath.includes(this.sortBy) ||
  205. (!to.fullPath.includes(this.sortBy) && !from.fullPath.includes(this.sortBy))
  206. ) {
  207. const ignore = ['event', 'exhibition', 'post']
  208. if(ignore.includes(this.type)) return
  209. console.log('init from $route watch...')
  210. this.clearAndInitPostList()
  211. }
  212. }
  213. },
  214. mounted() {
  215. this.setIntersectionLoader()
  216. this.clearAndInitPostList()
  217. },
  218. beforeDestroy() {
  219. this.unsetIntersectionLoader()
  220. }
  221. }
  222. </script>
  223. <style lang="postcss">
  224. // prettier-ignore
  225. @import '../sss/variables.sss'
  226. @import '../sss/theme.sss'
  227. .page--list article
  228. > header
  229. padding: 1em
  230. > h1
  231. margin: 0
  232. > .content
  233. padding: 0
  234. width: 100%
  235. > footer
  236. padding: $ms-0
  237. /* posts not grid list */
  238. .posts
  239. list-style: none
  240. grid-gap: $ms--2
  241. .post
  242. width: 100%
  243. /* posts in grid list */
  244. .posts.is-grid
  245. width: 100%
  246. display: grid
  247. grid-template-columns: repeat(1, 1fr)
  248. align-items: start
  249. /* This is important for how the grid lines up to the page */
  250. justify-content: right
  251. .post img
  252. width: 100%
  253. @media (min-width: $medium)
  254. .page--list
  255. &.f-col
  256. flex-direction: row
  257. > article
  258. margin: 0 $ms--2 0 0
  259. .posts.is-grid
  260. grid-template-columns: repeat(3, 1fr)
  261. </style>