NEXT craftinamerica.org. Base setup for headless wordpress https://www.craftinamerica.org
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

list.vue 8.0KB

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