Skip to content

Commit

Permalink
feat: share modal
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Jun 23, 2023
1 parent 571c049 commit c6ce103
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 34 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"mermaid": "10.2.3",
"next": "13.4.7",
"next-themes": "0.2.1",
"qrcode.react": "3.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-intersection-observer": "9.5.1",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions src/atoms/url.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { atom, useAtomValue } from 'jotai'

import { jotaiStore } from '~/lib/store'
import { useAggregationSelector } from '~/providers/root/aggregation-data-provider'
import { apiClient } from '~/utils/request'

export interface UrlConfig {
Expand All @@ -9,15 +10,22 @@ export interface UrlConfig {
webUrl: string
}

const appUrlAtom = atom<UrlConfig | null>(null)
const adminUrlAtom = atom<string | null>(null)

export const fetchAppUrl = async () => {
const { data } = await apiClient.proxy.options.url.get<{
data: UrlConfig
}>()

jotaiStore.set(appUrlAtom, data)
jotaiStore.set(adminUrlAtom, data.adminUrl)
}

export const getAppUrl = () => jotaiStore.get(appUrlAtom)
export const useAppUrl = () => useAtomValue(appUrlAtom)
export const getAppUrl = () => jotaiStore.get(adminUrlAtom)
export const useAppUrl = () => {
const url = useAggregationSelector((a) => a.url)
const adminUrl = useAtomValue(adminUrlAtom)
return {
adminUrl,
...url,
}
}
25 changes: 20 additions & 5 deletions src/components/widgets/note/NoteActionAside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import {
useCurrentNoteDataSelector,
} from '~/providers/note/CurrentNoteDataProvider'
import { useCurrentNoteId } from '~/providers/note/CurrentNoteIdProvider'
import { useModalStack } from '~/providers/root/modal-stack-provider'
import { isLikedBefore, setLikeId } from '~/utils/cookie'
import { clsxm } from '~/utils/helper'
import { apiClient } from '~/utils/request'

import { DonateButton } from '../shared/DonateButton'
import { ShareModal } from '../shared/ShareModal'

export const NoteActionAside: Component = ({ className }) => {
return (
Expand Down Expand Up @@ -107,6 +109,7 @@ const LikeButton = () => {

const ShareButton = () => {
const isClient = useIsClient()
const { present } = useModalStack()

if (!isClient) return null

Expand All @@ -120,16 +123,28 @@ const ShareButton = () => {
if (!note) return

const hasShare = 'share' in navigator

const title = '分享一片宝藏文章'
const url = urlBuilder(
routeBuilder(Routes.Note, {
id: note.nid.toString(),
}),
).href

const text = `嘿,我发现了一片宝藏文章「${note.title}」哩,快来看看吧!${url}`

if (hasShare)
navigator.share({
title: note.title,
text: note.text,
url: urlBuilder(
routeBuilder(Routes.Note, {
id: note.nid.toString(),
}),
).href,
url,
})
else {
present({
title: '分享此内容',
content: () => <ShareModal text={text} title={title} url={url} />,
})
}
}}
>
<i className="icon-[mingcute--share-forward-fill] text-[24px] opacity-80 duration-200 hover:text-uk-cyan-light hover:opacity-100" />
Expand Down
59 changes: 36 additions & 23 deletions src/components/widgets/post/PostActionAside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import { useIsClient } from '~/hooks/common/use-is-client'
import { routeBuilder, Routes } from '~/lib/route-builder'
import { toast } from '~/lib/toast'
import { urlBuilder } from '~/lib/url-builder'
import { getCurrentNoteData } from '~/providers/note/CurrentNoteDataProvider'
import { useCurrentNoteId } from '~/providers/note/CurrentNoteIdProvider'
import {
getCurrentPostData,
setCurrentPostData,
useCurrentPostDataSelector,
} from '~/providers/post/CurrentPostDataProvider'
import { useModalStack } from '~/providers/root/modal-stack-provider'
import { isLikedBefore, setLikeId } from '~/utils/cookie'
import { clsxm } from '~/utils/helper'
import { apiClient } from '~/utils/request'

import { DonateButton } from '../shared/DonateButton'
import { ShareModal } from '../shared/ShareModal'

export const PostActionAside: Component = ({ className }) => {
return (
Expand All @@ -40,12 +41,12 @@ const LikeButton = () => {
const [update] = useForceUpdate()

const id = useCurrentPostDataSelector((data) => data?.id)
const nid = useCurrentNoteId()

if (!id) return null
const handleLike = () => {
if (isLikedBefore(id)) return
if (!nid) return
apiClient.note.likeIt(id).then(() => {

apiClient.post.thumbsUp(id).then(() => {
setLikeId(id)
setCurrentPostData((draft) => {
draft.count.like += 1
Expand Down Expand Up @@ -105,32 +106,44 @@ const LikeButton = () => {
}

const ShareButton = () => {
const hasShare = 'share' in navigator
const isClient = useIsClient()
void useCurrentNoteId()
const note = getCurrentNoteData()?.data
const { present } = useModalStack()

if (!isClient) return null
if (!note) return null

if (!hasShare) {
return null
}

return (
<MotionButtonBase
aria-label="Share this post"
aria-label="Share This Post Button"
className="flex flex-col space-y-2"
onClick={() => {
navigator.share({
title: note.title,
text: note.text,
url: urlBuilder(
routeBuilder(Routes.Note, {
id: note.nid.toString(),
}),
).href,
})
const post = getCurrentPostData()

if (!post) return

const hasShare = 'share' in navigator

const title = '分享一片宝藏文章'
const url = urlBuilder(
routeBuilder(Routes.Post, {
slug: post.slug,
category: post.category.slug,
}),
).href

const text = `嘿,我发现了一片宝藏文章「${post.title}」哩,快来看看吧!${url}`

if (hasShare)
navigator.share({
title: post.title,
text: post.text,
url,
})
else {
present({
title: '分享此内容',
content: () => <ShareModal text={text} title={title} url={url} />,
})
}
}}
>
<i className="icon-[mingcute--share-forward-fill] text-[24px] opacity-80 duration-200 hover:text-uk-cyan-light hover:opacity-100" />
Expand Down
2 changes: 2 additions & 0 deletions src/components/widgets/post/PostMetaBar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client'

import type { PostModel } from '@mx-space/api-client'

import { MdiClockOutline } from '~/components/icons/clock'
Expand Down
84 changes: 84 additions & 0 deletions src/components/widgets/shared/ShareModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import dynamic from 'next/dynamic'
import type { FC } from 'react'

import {
IcBaselineTelegram,
MdiTwitter,
} from '~/components/icons/menu-collection'
import { toast } from '~/lib/toast'
import { getAggregationData } from '~/providers/root/aggregation-data-provider'

const QRCodeSVG = dynamic(
() => import('qrcode.react').then((module) => module.QRCodeSVG),
{ ssr: false },
)

const shareList = [
{
name: 'Twitter',
icon: <MdiTwitter className="text-[#1DA1F2]" />,
onClick: (data: ShareData) => {
window.open(
`https://twitter.com/intent/tweet?url=${data.url}&text=${
data.text
}&via=${getAggregationData()?.seo.title}`,
)
},
},
{
name: 'Telegram',
icon: <IcBaselineTelegram className="text-[#2AABEE]" />,
onClick: (data: ShareData) => {
window.open(
`https://telegram.me/share/url?url=${data.url}&text=${data.text}`,
)
},
},

{
name: 'Copy',
icon: <i className="icon-[mingcute--copy-fill]" />,
onClick: (data: ShareData) => {
navigator.clipboard.writeText(data.url)
toast('Copied to clipboard')
},
},
]

interface ShareData {
url: string
title: string
text: string
}

export const ShareModal: FC<ShareData> = ({ url, text, title }) => {
return (
<div className="relative grid grid-cols-[200px_auto] gap-5">
<div className="qrcode inline-block">
<QRCodeSVG
value={url}
className="aspect-square w-[200px]"
height={200}
width={200}
/>
</div>
<div className="share-options flex flex-col gap-2">
分享到...
<ul className="w-[200px] flex-col gap-2 [&>li]:flex [&>li]:items-center [&>li]:space-x-2">
{shareList.map(({ name, icon, onClick }) => (
<li
key={name}
className="flex cursor-pointer items-center space-x-2 rounded-md px-3 py-2 text-lg transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
aria-label={`Share to ${name}`}
role="button"
onClick={() => onClick({ url, text, title })}
>
{icon}
<span>{name}</span>
</li>
))}
</ul>
</div>
</div>
)
}
6 changes: 4 additions & 2 deletions src/lib/url-builder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { appConfig } from '~/app.config'
import { aggregationDataAtom } from '~/providers/root/aggregation-data-provider'

import { jotaiStore } from './store'

export function urlBuilder(path = '') {
return new URL(path, appConfig.site.url)
return new URL(path, jotaiStore.get(aggregationDataAtom)?.url.webUrl)
}
2 changes: 2 additions & 0 deletions src/providers/root/aggregation-data-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ export const useAggregationSelector = <T,>(
),
),
)

export const getAggregationData = () => jotaiStore.get(aggregationDataAtom)

0 comments on commit c6ce103

Please sign in to comment.