storipress/karbon

View on GitHub
packages/karbon/src/runtime/composables/page-meta.ts

Summary

Maintainability
A
1 hr
Test Coverage
import type { Ref } from 'vue'
import { useCurrentElement, whenever } from '@vueuse/core'
import type { EventName } from '../api/track'
import type { UseArticleReturn as Article, Resources } from '../types'
import { useSEO } from './seo'
import { useStaticState } from './storipress-payload'
import { getAllArticles } from './front-page'
import {
  computed,
  navigateTo,
  onBeforeUnmount,
  onMounted,
  onServerPrefetch,
  useArticleFilter,
  useDeskFeedLink,
  useNuxtApp,
  useRequestEvent,
  useSeoMeta,
} from '#imports'

export type PageType = 'article' | 'desk' | 'tag' | 'author'

export interface SetupPageInput<Type extends PageType> {
  /**
   * page type
   */
  type: Type
  /**
   * set to false to disable auto-generated SEO tags
   */
  seo?: boolean
}

export interface PageTypeMap {
  article: Article
  desk: Desk
  tag: Tag
  author: Author
}

export interface Desk {
  desks: Desk[]
  id: string
  name: string
  slug: string
}

export interface Tag {
  id: string
  name: string
}

export interface Author {
  avatar: string
  bio: string
  bioHTML: string
  created_at: string
  desks: Desk[]
  email: string
  first_name: string
  full_name: string
  name: string
  id: string
  last_name: string
  location: string
  socials: JSON
  updated_at: string
  website: string
}

const eventNameMap: Record<PageType, EventName> = {
  article: 'article.seen',
  desk: 'desk.seen',
  tag: 'tag.seen',
  author: 'author.seen',
}

export function setupPage<Type extends PageType>({ type, seo = true }: SetupPageInput<Type>): Ref<PageTypeMap[Type]> {
  const pageMeta = useResourcePageMeta()

  if (process.server && (!pageMeta.value || pageMeta.value.type !== type)) {
    navigateTo('/')
  }

  const meta = computed(() => {
    return pageMeta.value?.meta
  })

  if (type === 'article') {
    const { $paywall } = useNuxtApp()
    onMounted(async () => {
      await $paywall.mount()
      $paywall.enable()

      whenever(
        meta,
        ({ id, plan }) => {
          $paywall.setArticle({ id, plan })
        },
        { immediate: true, flush: 'sync' },
      )
    })
    onBeforeUnmount(() => {
      $paywall.disable()
    })
  }

  const el = useCurrentElement()
  onMounted(() => {
    if (el.value) {
      // @ts-expect-error inject value to element
      el.value.__spResource = meta.value
    }
    const { $tracker } = useNuxtApp()
    const input = {
      name: eventNameMap[type],
      target_id: '',
    }
    if (meta.value?.id) {
      input.target_id = meta.value.id
    }

    $tracker.withReferer(input)
  })

  if (seo && meta.value) {
    useSEO(meta.value, undefined, pageMeta.value?.type as Resources)
  }

  if (type === 'author') {
    const useArticleFilterTag = useArticleFilter()
    const nuxt = useNuxtApp()
    const authorId = meta.value.id

    onServerPrefetch(async () => {
      const source = (await getAllArticles()) ?? []
      const hasArticle = source.some(({ authors }) => {
        return authors.some((author) => author.id === authorId)
      })
      if (hasArticle) return

      nuxt.runWithContext(() => useSeoMeta({ robots: 'noindex' }))
    })

    return computed(() => {
      return {
        ...meta.value,
        bio: useArticleFilterTag(meta.value.bio),
        bioHTML: meta.value.bio,
      }
    })
  }

  return meta
}

export function setupArticlePage(seo?: boolean) {
  return setupPage({ type: 'article', seo })
}

export function setupDeskPage(seo?: boolean) {
  const meta = setupPage({ type: 'desk', seo })
  useDeskFeedLink(meta)
  return meta
}

export function setupTagPage(seo?: boolean) {
  return setupPage({ type: 'tag', seo })
}

export function setupAuthorPage(seo?: boolean) {
  return setupPage({ type: 'author', seo })
}

interface PageMeta {
  type: string
  route: string
  meta: any
}

export function useResourcePageMeta() {
  const getPageMeta = (): PageMeta => {
    const event = useRequestEvent()
    return event.context.__sp_page_meta
  }

  const pageMeta = useStaticState<PageMeta | undefined>('page-meta', getPageMeta)
  return pageMeta
}