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.

single.vue 11KB

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