Skip to content

Commit

Permalink
feat(new-article-path): Revise pathname of article detail page #4196
Browse files Browse the repository at this point in the history
for #4196
  • Loading branch information
tx0c authored and TomasC committed Mar 5, 2024
1 parent dc6a849 commit 8255200
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/pages/p/[shortHash].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ArticleDetailOuterByShortHash } from '~/views/ArticleDetail'

export default ArticleDetailOuterByShortHash
6 changes: 4 additions & 2 deletions src/views/ArticleDetail/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const articlePublicFragment = gql`
title
slug
mediaHash
shortHash
state
cover
summary
Expand Down Expand Up @@ -108,12 +109,13 @@ export const ARTICLE_AVAILABLE_TRANSLATIONS_BY_NODE_ID = gql`

export const ARTICLE_DETAIL_PUBLIC = gql`
query ArticleDetailPublic(
$mediaHash: String!
$mediaHash: String
$shortHash: String
$language: UserLanguage!
$includeTranslation: Boolean = false
$includeCanSuperLike: Boolean = true
) {
article(input: { mediaHash: $mediaHash }) {
article(input: { mediaHash: $mediaHash, shortHash: $shortHash }) {
...ArticlePublicArticle
}
}
Expand Down
256 changes: 256 additions & 0 deletions src/views/ArticleDetail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,234 @@ const ArticleDetail = ({
return <BaseArticleDetail article={article} privateFetched={privateFetched} />
}

const ArticleDetailByShortHash = ({
includeTranslation,
}: {
includeTranslation: boolean
}) => {
const { getQuery, router, routerLang } = useRoute()
const [needRefetchData, setNeedRefetchData] = useState(false)
const shortHash = getQuery('shortHash')
const viewer = useContext(ViewerContext)

// - `/:username:/:shortHash:`
const resultByHash = usePublicQuery<ArticleDetailPublicQuery>(
ARTICLE_DETAIL_PUBLIC,
{
variables: {
shortHash,
language: routerLang || UserLanguage.ZhHant,
includeTranslation,
},
// skip: !isQueryByHash,
}
)

const { data, client, refetch: refetchPublic } = resultByHash // : resultByNodeId
const loading = resultByHash.loading // || resultByNodeId.loading
const error = resultByHash.error // || resultByNodeId.error

const article = data?.article
const authorId = article?.author?.id
const isAuthor = viewer.id === authorId

/**
* fetch private data
*/
const [privateFetched, setPrivateFetched] = useState(false)
const loadPrivate = async () => {
if (!viewer.isAuthed || !article) {
return
}

await client.query({
query: ARTICLE_DETAIL_PRIVATE,
fetchPolicy: 'network-only',
variables: {
id: article?.id,
includeCanSuperLike: viewer.isCivicLiker,
},
})

setPrivateFetched(true)
}

useEffect(() => {
// reset state to private fetchable when URL query is changed
setPrivateFetched(false)

// refetch data when URL query is changed
;(async () => {
if (!needRefetchData) {
return
}
await refetchPublic()
await loadPrivate()
setNeedRefetchData(false)
})()
}, [shortHash])

// fetch private data when mediaHash of public data is changed
useEffect(() => {
loadPrivate()
}, [article?.mediaHash, viewer.id])

// shadow replace URL
const latestHash = article?.drafts?.filter(
(d) => d.publishState === 'published'
)[0]?.mediaHash
useEffect(() => {
if (!article || !latestHash) {
return
}

const newPath = toPath({
page: 'articleDetail',
article: { ...article, mediaHash: latestHash },
})

// parse current URL: router.asPath
const u = new URL(
`https://${process.env.NEXT_PUBLIC_SITE_DOMAIN}${router.asPath}`
)
const n = new URL(
`https://${process.env.NEXT_PUBLIC_SITE_DOMAIN}${newPath.href}`
)

// TODO: can remove this after 2024/2
const isNomadTags = article.tags?.some(
(tag) => tag.content === 'nomadmatters' || tag.content === '遊牧者計畫'
)
const hasReferral = u.searchParams.has(REFERRAL_QUERY_REFERRAL_KEY)
if (!hasReferral && isNomadTags && viewer.userName) {
u.searchParams.append(REFERRAL_QUERY_REFERRAL_KEY, viewer.userName)
}

// hide all utm_ tracking code parameters
// copy all others
const rems = [
...u.searchParams, // uses .entries()
...n.searchParams,
].filter(([k, v]) => !k?.startsWith('utm_'))
const nsearch = rems.length > 0 ? `?${new URLSearchParams(rems)}` : ''
const nhref = `${n.pathname}${nsearch}${n.hash || u.hash}`

if (nhref !== router.asPath || routerLang) {
router.replace(nhref, undefined, { shallow: true, locale: false })
}
}, [latestHash])

// edit mode
const canEdit = isAuthor && !viewer.isInactive
const mode = getQuery(URL_QS.MODE_EDIT.key)
const [editMode, setEditMode] = useState(false)
const exitEditMode = () => {
if (!article) {
return
}

setNeedRefetchData(true)
const path = toPath({ page: 'articleDetail', article })
router.replace(path.href)
}

const onEditSaved = async () => {
setEditMode(false)
exitEditMode()

await refetchPublic()
loadPrivate()
}

useEffect(() => {
if (!canEdit || !article) {
return
}

setEditMode(mode === URL_QS.MODE_EDIT.value)
}, [mode, article])

/**
* Render:Loading
*/
if (loading) {
return (
<EmptyLayout>
<Spinner />
</EmptyLayout>
)
}

/**
* Render:Error
*/
if (error) {
return (
<EmptyLayout>
<QueryError error={error} />
</EmptyLayout>
)
}

/**
* Render:404
*/
if (!article) {
return (
<EmptyLayout>
<Throw404 />
</EmptyLayout>
)
}

/**
* Render:Archived/Banned
*/
if (article.state !== 'active' && !isAuthor) {
return (
<EmptyLayout>
<Error
message={
article.state === 'archived' ? (
<Translate
zh_hant="吶,作者親手掩蓋了這篇作品的痕跡,看看別的吧"
zh_hans="呐,作者亲手掩盖了这篇作品的痕迹,看看别的吧"
en="Hmm... It seems the author has hidden this work. Go see something else"
/>
) : article.state === 'banned' ? (
<Translate
zh_hant="該作品因違反社區約章,已被站方強制歸檔。"
zh_hans="该作品因违反社区约章,已被站方强制封存。"
en="This work is archived due to violation of community guidelines."
/>
) : null
}
>
<BackToHomeButton />
</Error>
</EmptyLayout>
)
}

/**
* Render:Edit Mode
*/
if (editMode) {
return (
<DynamicEditMode
article={article}
onCancel={exitEditMode}
onSaved={onEditSaved}
/>
)
}

/**
* Render:Article
*/
return <BaseArticleDetail article={article} privateFetched={privateFetched} />
}

const ArticleDetailOuter = () => {
const { getQuery, router, routerLang } = useRoute()
const mediaHash = getQuery('mediaHash')
Expand Down Expand Up @@ -661,4 +889,32 @@ const ArticleDetailOuter = () => {
return <ArticleDetail includeTranslation={includeTranslation} />
}

export const ArticleDetailOuterByShortHash = () => {
const { getQuery, routerLang } = useRoute()
const shortHash = getQuery('shortHash')

const resultByHash = usePublicQuery<ArticleAvailableTranslationsQuery>(
ARTICLE_AVAILABLE_TRANSLATIONS,
{ variables: { shortHash } }
)
const { data } = resultByHash // : resultByNodeId
const loading = resultByHash.loading // || resultByNodeId.loading
const includeTranslation =
!!routerLang &&
(data?.article?.availableTranslations || []).includes(routerLang)

/**
* Rendering
*/
if (loading) {
return (
<EmptyLayout>
<Spinner />
</EmptyLayout>
)
}

return <ArticleDetailByShortHash includeTranslation={includeTranslation} />
}

export default ArticleDetailOuter

0 comments on commit 8255200

Please sign in to comment.