NEXT craftinamerica.org. Base setup for headless wordpress https://www.craftinamerica.org
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

single.vue 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. if (!this.$el) return
  188. const section = this.$el.children[0].querySelector('section')
  189. await nextTick()
  190. const galleryBlocks = section.querySelectorAll('.wp-block-gallery')
  191. galleryBlocks.forEach((block, blockIndex) => {
  192. block.querySelectorAll('img').forEach(image => {
  193. image.dataset.gallery = blockIndex
  194. this._setClick(image, e => {
  195. this.activeGalleryIndex = e.target.dataset.gallery
  196. const activeGallery =
  197. post.galleries[this.activeGalleryIndex]
  198. if (!activeGallery.ids) return
  199. this.activeImageIndex = activeGallery.ids.indexOf(
  200. parseInt(e.target.dataset.id),
  201. )
  202. })
  203. })
  204. })
  205. },
  206. },
  207. created() {
  208. this.loadPostData()
  209. },
  210. beforeDestroy() {},
  211. }
  212. </script>
  213. <style lang="postcss">
  214. // prettier-ignore
  215. @import '../sss/variables.sss'
  216. @import '../sss/theme.sss'
  217. .page--single
  218. article
  219. background-color: white
  220. padding: $ms-0
  221. h1
  222. /* color: $cia_black */
  223. margin: 0 0 $ms--3 0
  224. > ul
  225. /* grid-gap: $ms-0 */
  226. list-style: none
  227. /* change to a 1/3 width of the article*/
  228. img.feature
  229. width: 20em
  230. /* wp-block-embed youtube link */
  231. .wp-block-embed, .is-type-video
  232. position: relative
  233. width: 100%
  234. padding-bottom: 56.25%
  235. margin-bottom: $ms-7
  236. &__wrapper
  237. display: contents
  238. /* TBD if kept- edit ot test */
  239. figcaption
  240. position: absolute
  241. top: 100%
  242. .wp-block-gallery
  243. margin: 0 0 0.5em 0
  244. grid-gap: $ms--5
  245. > .wp-block-image
  246. figcaption
  247. position: inherit
  248. background: none
  249. font-size: $ms--1
  250. color: $cia_black
  251. max-height: 9%
  252. overflow: visible
  253. padding: 0
  254. margin-bottom: 2.5em
  255. figcaption
  256. flex-direction: column
  257. /* iframe container 16:9 */
  258. [class^="iframe-container"]
  259. position: relative
  260. padding-bottom: 56.25%
  261. /* width: 100% */
  262. /* iframe container portrait */
  263. .iframe-container-v
  264. height: 100%
  265. padding-bottom: 125%
  266. iframe
  267. position: absolute
  268. top: 0
  269. left: 0
  270. width: 100%
  271. height: 100%
  272. /* separator styles */
  273. * hr
  274. margin: $ms-2 auto
  275. &.is-style
  276. &-default
  277. width: 15vw
  278. &-wide
  279. width: 50vw
  280. &-dots::before
  281. outline-style: none
  282. font-weight: bolder
  283. letter-spacing: $ms-8
  284. padding-left: $ms-8
  285. /* margin to indent marker */
  286. li
  287. margin: 0 0 $ms--2 $ms-4
  288. .related-artists
  289. ul li
  290. margin: 0 0 $ms-0 0
  291. .card
  292. padding: 0 0 $ms--2
  293. min-height: auto
  294. breadcrumb
  295. h5
  296. color: $cia_red
  297. //- end of article icon
  298. footer
  299. padding: $ms-6 0
  300. img
  301. height: $ms-3
  302. width: $ms-3
  303. @media (min-width: $medium)
  304. .page--single
  305. > article
  306. margin: 0 0.65em 0 0
  307. &.f-col
  308. flex-direction: row
  309. .wp-block-gallery
  310. .wp-block-image
  311. figcaption
  312. max-height: 5%
  313. margin-bottom: $ms-4
  314. </style>