NEXT craftinamerica.org. Base setup for headless wordpress https://www.craftinamerica.org
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <template lang="pug">
  2. .page--single.f-col.between
  3. article.w-max.f-grow.shadow(v-if="!singlePost || loading")
  4. header
  5. p loading...
  6. article(v-else).w-max.f-grow.shadow
  7. header
  8. //- breadcrumb links at top of page, needs link routing
  9. breadcrumb(:type="type" :post="singlePost")
  10. h1.t-b {{ singlePost.title }}
  11. .date-info.t-cntr(v-if="['exhibition', 'event'].includes(type)")
  12. //- for events display: date, time-time
  13. h4(v-if="singlePost.start, singlePost.end && type == 'event'") {{ dateFrom(singlePost.start, type == 'event') }} - {{ dateFrom(singlePost.end, type == 'event').split(',')[1] }}
  14. //- else for single, exhibition: date-date
  15. h4(v-else-if="singlePost.start, singlePost.end") {{ dateFrom(singlePost.start, type == 'event') }} - {{ dateFrom(singlePost.end, type == 'event') }}
  16. //- WP main content
  17. section.content(v-html="singlePost.content")
  18. //- related artists section for episodes
  19. template(v-if="type === 'episode' && post")
  20. section.related-artists
  21. h2.t-up featured in this episode
  22. ul
  23. li.f-row.between(v-for="artist in p2pPostsByType['artist']")
  24. card(:content="artist" type="artist" :wide="true" :hide-type="true")
  25. credits(:post="singlePost")
  26. //- end of article icon
  27. footer.f-col
  28. img(src="../star.svg")
  29. vue-easy-lightbox(
  30. :visible="activeGalleryIndex >= 0"
  31. :imgs="activeGalleryImages"
  32. :index="activeImageIndex"
  33. @hide="activeGalleryIndex = -1"
  34. )
  35. sidebar(:type="`${type}`" layout="single" :related="p2pPostsByType")
  36. </template>
  37. <script>
  38. import card from '@/components/card.vue'
  39. import sidebar from '@/components/sidebars/sidebar'
  40. import gallery from '@/components/gallery/'
  41. import credits from '@/components/credits'
  42. import breadcrumb from '@/components/breadcrumb'
  43. import { postTypeGetters, scrollTop, heroUtils } from './mixin-post-types'
  44. import { postTypes, convertTitleCase, formatDate } from '@/utils/helpers'
  45. import { nextTick } from '@vue/runtime-core'
  46. export default {
  47. components: { sidebar, gallery, credits, card, breadcrumb },
  48. mixins: [postTypeGetters, scrollTop, heroUtils],
  49. data() {
  50. return {
  51. activeGalleryIndex: -1,
  52. activeImageIndex: 0,
  53. loading: true,
  54. }
  55. },
  56. computed: {
  57. type() {
  58. return postTypes.includes(this.$route.params.type)
  59. ? this.$route.params.type
  60. : 'post'
  61. },
  62. slug() {
  63. return this.$route.params.slug
  64. },
  65. /**
  66. * We get the actual post data using the slug
  67. * Careful with name collisions with vuex helpers
  68. */
  69. singlePost() {
  70. const postType = this.type == 'blog' ? 'post' : this.type
  71. if (!this[postType]) return
  72. // State not a getter!
  73. const singleOfTypeFromState =
  74. this[postType][`single${convertTitleCase(postType)}`]
  75. return singleOfTypeFromState ? singleOfTypeFromState : {}
  76. },
  77. singlePostGalleries() {
  78. if (!this.singlePost.galleries) return
  79. const galleries = []
  80. this.singlePost.galleries.forEach(gallery => {
  81. if (!gallery.ids) return
  82. const withImages = gallery.ids.map(
  83. imageId => this.singlePost.attached[imageId],
  84. )
  85. galleries.push(withImages)
  86. })
  87. return galleries
  88. },
  89. activeGalleryImages() {
  90. if (!this.singlePostGalleries || this.activeGalleryIndex < 0)
  91. return []
  92. return this.singlePostGalleries[this.activeGalleryIndex]
  93. },
  94. p2pPostsByType() {
  95. return this.singlePost && this.singlePost.relatedto
  96. ? Object.values(this.singlePost.relatedto).reduce(
  97. (byType, relatedPost) => {
  98. if (!byType[relatedPost.type])
  99. byType[relatedPost.type] = []
  100. byType[relatedPost.type].push(relatedPost)
  101. return byType
  102. },
  103. {},
  104. )
  105. : {}
  106. },
  107. },
  108. methods: {
  109. // _setHeroInfo(post) {} from mixin
  110. // _clearHero(store) {} from mixin
  111. /**
  112. * Everytime the post object changes
  113. * we use this to set a new HERO
  114. * in vuex
  115. * @param {object} post
  116. */
  117. checkAndSetHero(post) {
  118. this._clearHero(this.$store)
  119. if (!post) throw `No post found. Cannot set hero.`
  120. this.$store.commit('SET_HERO', this._setHeroInfo(post))
  121. },
  122. /**
  123. * Date Object from unix strings from db
  124. */
  125. dateFrom: (unix, includeTime) => formatDate(unix, includeTime),
  126. async loadPostData() {
  127. this.loading = true
  128. /**
  129. * Conditionally load based on post type
  130. * which is derived from the route
  131. */
  132. // modules are NOT plural because module key
  133. const postType = this.type == 'blog' ? 'post' : this.type
  134. if (!this.$store.state[postType]) return
  135. const allPostsOfTypeInStore = this.$store.state[postType].all
  136. /**
  137. * Load posts if they're not already in state
  138. */
  139. // Find the single post from api if it's not already in state
  140. // Then add it to our list
  141. let singlePostData = allPostsOfTypeInStore.filter(
  142. post => post.slug == this.slug,
  143. )[0]
  144. // Look if it exists before you try and load everything!
  145. if (!singlePostData) {
  146. console.warn(
  147. 'Could not find single post in store; Fetching everything...',
  148. )
  149. const res = await this.$store.dispatch(
  150. `getAll${convertTitleCase(postType)}s`,
  151. { sortType: null, params: null },
  152. )
  153. singlePostData = res.filter(post => post.slug == this.slug)[0]
  154. }
  155. /**
  156. * At the point we MUST have singlePostData
  157. */
  158. try {
  159. this.checkAndSetHero(singlePostData)
  160. this.$store.dispatch(
  161. `getSingle${convertTitleCase(postType)}`,
  162. singlePostData.id,
  163. )
  164. } catch (err) {
  165. console.error(err)
  166. }
  167. this.loading = false
  168. },
  169. _setClick(el, cb) {
  170. if (el.addEventListener) {
  171. el.addEventListener('click', cb, false)
  172. } else {
  173. el.attachEvent('onclick', cb)
  174. }
  175. },
  176. },
  177. watch: {
  178. slug(newSlug, oldSlug) {
  179. // ONLY load post data when navigating TO a single page
  180. // OR when navigating TO a single page from a single page
  181. if ((newSlug && !oldSlug) || (newSlug && oldSlug)) {
  182. this._clearHero(this.$store)
  183. this.loadPostData()
  184. }
  185. },
  186. async singlePost(post) {
  187. console.log('---')
  188. if (!this.$el) return
  189. const section = this.$el.children[0].querySelector('section')
  190. await nextTick()
  191. const galleryBlocks = section.querySelectorAll('.wp-block-gallery')
  192. galleryBlocks.forEach((block, blockIndex) => {
  193. block.querySelectorAll('img').forEach(image => {
  194. image.dataset.gallery = blockIndex
  195. this._setClick(image, e => {
  196. this.activeGalleryIndex = e.target.dataset.gallery
  197. const activeGallery =
  198. post.galleries[this.activeGalleryIndex]
  199. console.log(post.galleries)
  200. console.log(activeGallery)
  201. if (!activeGallery.ids) return
  202. this.activeImageIndex = activeGallery.ids.indexOf(
  203. parseInt(e.target.dataset.id),
  204. )
  205. console.log(
  206. `opening gallery: ${this.activeGalleryIndex}.${this.activeImageIndex}`,
  207. )
  208. console.log(e.target.dataset)
  209. })
  210. })
  211. })
  212. },
  213. },
  214. created() {
  215. this.loadPostData()
  216. },
  217. beforeDestroy() {},
  218. }
  219. </script>
  220. <style lang="postcss">
  221. // prettier-ignore
  222. @import '../sss/variables.sss'
  223. @import '../sss/theme.sss'
  224. .page--single
  225. article
  226. background-color: white
  227. padding: $ms-0
  228. h1
  229. /* color: $cia_black */
  230. margin: 0 0 $ms--3 0
  231. > ul
  232. /* grid-gap: $ms-0 */
  233. list-style: none
  234. /* change to a 1/3 width of the article*/
  235. img.feature
  236. width: 20em
  237. /* wp-block-embed youtube link */
  238. .wp-block-embed, .is-type-video
  239. position: relative
  240. width: 100%
  241. padding-bottom: 56.25%
  242. margin-bottom: $ms-7
  243. &__wrapper
  244. display: contents
  245. /* TBD if kept- edit ot test */
  246. figcaption
  247. position: absolute
  248. top: 100%
  249. .wp-block-gallery
  250. margin: 0 0 0.5em 0
  251. grid-gap: $ms--5
  252. > .wp-block-image
  253. figcaption
  254. position: inherit
  255. background: none
  256. font-size: $ms--1
  257. color: $cia_black
  258. max-height: 9%
  259. overflow: visible
  260. padding: 0
  261. margin-bottom: 2.5em
  262. figcaption
  263. flex-direction: column
  264. /* iframe container 16:9 */
  265. [class^="iframe-container"]
  266. position: relative
  267. padding-bottom: 56.25%
  268. /* width: 100% */
  269. /* iframe container portrait */
  270. .iframe-container-v
  271. height: 100%
  272. padding-bottom: 125%
  273. iframe
  274. position: absolute
  275. top: 0
  276. left: 0
  277. width: 100%
  278. height: 100%
  279. /* separator styles */
  280. * hr
  281. margin: $ms-2 auto
  282. &.is-style
  283. &-default
  284. width: 15vw
  285. &-wide
  286. width: 50vw
  287. &-dots::before
  288. outline-style: none
  289. font-weight: bolder
  290. letter-spacing: $ms-8
  291. padding-left: $ms-8
  292. /* margin to indent marker */
  293. li
  294. margin: 0 0 $ms--2 $ms-4
  295. .related-artists
  296. ul li
  297. margin: 0 0 $ms-0 0
  298. .card
  299. padding: 0 0 $ms--2
  300. min-height: auto
  301. breadcrumb
  302. h5
  303. color: $cia_red
  304. //- end of article icon
  305. footer
  306. padding: $ms-6 0
  307. img
  308. height: $ms-3
  309. width: $ms-3
  310. @media (min-width: $medium)
  311. .page--single
  312. > article
  313. margin: 0 0.65em 0 0
  314. &.f-col
  315. flex-direction: row
  316. .wp-block-gallery
  317. .wp-block-image
  318. figcaption
  319. max-height: 5%
  320. margin-bottom: $ms-4
  321. </style>