diff --git a/.eslintignore b/.eslintignore
index 0089d399e..6a55c1634 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -4,4 +4,5 @@ next-env.d.ts
*.json
*.lock
*.hbs
-*.svg
\ No newline at end of file
+*.svg
+*.png
\ No newline at end of file
diff --git a/config/config.json b/config/config.json
index 0b9af18f0..6940a5db8 100755
--- a/config/config.json
+++ b/config/config.json
@@ -53,6 +53,7 @@
"DEFAULT_USER_AVATAR": "https://cps-oss.oss-cn-shanghai.aliyuncs.com/icons/cmd/alien_user3.svg",
"GRAPHQL_ENDPOINT": "https://api.coderplanets.com/graphiql",
"SITE_URL": "https://coderplanets.com",
+ "SITE_URL_SHORT": "https://cper.co",
"GITHUB": "https://github.com/coderplanets",
"GITHUB_WEB_ADDR": "https://github.com/coderplanets/coderplanets_web",
"GITHUB_SERVER_ADDR": "https://github.com/coderplanets/coderplanets_server",
diff --git a/config/index.ts b/config/index.ts
index 2ffa2fb21..b98071c7f 100755
--- a/config/index.ts
+++ b/config/index.ts
@@ -26,6 +26,7 @@ export const {
DEFAULT_ICON,
DEFAULT_USER_AVATAR,
SITE_URL,
+ SITE_URL_SHORT,
GITHUB,
GITHUB_WEB_ADDR,
GITHUB_SERVER_ADDR,
diff --git a/package.json b/package.json
index 983baf0b6..32f3f5144 100644
--- a/package.json
+++ b/package.json
@@ -103,11 +103,13 @@
"promise-timeout": "^1.3.0",
"prop-types": "^15.5.10",
"pubsub-js": "^1.9.3",
+ "qrcode-react": "^0.1.16",
"ramda": "0.26.1",
"react": "17.0.2",
"react-animation": "^1.2.2",
"react-calendar-heatmap": "1.8.1",
"react-content-loader": "3.4.2",
+ "react-copy-to-clipboard": "^5.0.3",
"react-dom": "17.0.2",
"react-highlight-words": "^0.16.0",
"react-lazy-load-image-component": "1.5.0",
diff --git a/public/icons/static/article/clipboard.svg b/public/icons/static/article/clipboard.svg
new file mode 100644
index 000000000..9ee973149
--- /dev/null
+++ b/public/icons/static/article/clipboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/icons/static/shape/link.svg b/public/icons/static/shape/link.svg
new file mode 100644
index 000000000..4680935bd
--- /dev/null
+++ b/public/icons/static/shape/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/icons/static/social/QQ-share.png b/public/icons/static/social/QQ-share.png
new file mode 100644
index 000000000..b892ebc9a
Binary files /dev/null and b/public/icons/static/social/QQ-share.png differ
diff --git a/public/icons/static/social/embed-share.svg b/public/icons/static/social/embed-share.svg
new file mode 100644
index 000000000..e4919bfeb
--- /dev/null
+++ b/public/icons/static/social/embed-share.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/icons/static/social/facebook-share.png b/public/icons/static/social/facebook-share.png
new file mode 100644
index 000000000..845647191
Binary files /dev/null and b/public/icons/static/social/facebook-share.png differ
diff --git a/public/icons/static/social/mail-share.svg b/public/icons/static/social/mail-share.svg
new file mode 100644
index 000000000..dff09d495
--- /dev/null
+++ b/public/icons/static/social/mail-share.svg
@@ -0,0 +1,2 @@
+
\ No newline at end of file
diff --git a/public/icons/static/social/twitter-share.png b/public/icons/static/social/twitter-share.png
new file mode 100644
index 000000000..04836ff75
Binary files /dev/null and b/public/icons/static/social/twitter-share.png differ
diff --git a/public/icons/static/social/wechat-share.png b/public/icons/static/social/wechat-share.png
new file mode 100644
index 000000000..a1df1b7c6
Binary files /dev/null and b/public/icons/static/social/wechat-share.png differ
diff --git a/public/icons/static/social/weibo-share.png b/public/icons/static/social/weibo-share.png
new file mode 100644
index 000000000..d9f63df94
Binary files /dev/null and b/public/icons/static/social/weibo-share.png differ
diff --git a/src/components/Buttons/CopyButton/Animate.tsx b/src/components/Buttons/CopyButton/Animate.tsx
new file mode 100644
index 000000000..94d7040ca
--- /dev/null
+++ b/src/components/Buttons/CopyButton/Animate.tsx
@@ -0,0 +1,40 @@
+import { FC, memo, useEffect, useState } from 'react'
+
+import { ICON } from '@/config'
+import { IconButton } from '@/components/Buttons'
+import { CopyToClipboard } from 'react-copy-to-clipboard'
+
+import { AnimateOnChange } from 'react-animation'
+import { CopyedHintIcon } from '../styles/copy_button'
+
+type TProps = {
+ value: string
+}
+
+const CopyButton: FC = ({ value }) => {
+ const [done, setDone] = useState(false)
+
+ useEffect(() => {
+ if (done) {
+ setTimeout(() => setDone(false), 2000)
+ }
+ }, [done])
+
+ return (
+
+ {!done && (
+ setDone(true)}>
+
+
+ )}
+ {done && }
+
+ )
+}
+
+export default memo(CopyButton)
diff --git a/src/components/Buttons/CopyButton/index.tsx b/src/components/Buttons/CopyButton/index.tsx
new file mode 100644
index 000000000..54b3ce0ae
--- /dev/null
+++ b/src/components/Buttons/CopyButton/index.tsx
@@ -0,0 +1,27 @@
+import { FC, memo } from 'react'
+import dynamic from 'next/dynamic'
+
+import IconButton from '../IconButton'
+import { Wrapper } from '../styles/copy_button'
+
+const AnimatedCopyButton = dynamic(() => import('./Animate'), {
+ /* eslint-disable react/display-name */
+ loading: () => {
+ return
+ },
+ ssr: false,
+})
+
+type TProps = {
+ value: string
+}
+
+const CopyButton: FC = ({ value }) => {
+ return (
+
+
+
+ )
+}
+
+export default memo(CopyButton)
diff --git a/src/components/Buttons/index.tsx b/src/components/Buttons/index.tsx
index c5e3f7570..f558c7df6 100755
--- a/src/components/Buttons/index.tsx
+++ b/src/components/Buttons/index.tsx
@@ -9,3 +9,4 @@ export { default as NotifyButton } from './NotifyButton'
export { default as FollowButton } from './FollowButton'
export { default as YesOrNoButtons } from './YesOrNoButtons'
export { default as IconButton } from './IconButton'
+export { default as CopyButton } from './CopyButton'
diff --git a/src/components/Buttons/styles/copy_button/index.ts b/src/components/Buttons/styles/copy_button/index.ts
new file mode 100644
index 000000000..1f670fb28
--- /dev/null
+++ b/src/components/Buttons/styles/copy_button/index.ts
@@ -0,0 +1,13 @@
+import styled from 'styled-components'
+
+import { css, theme } from '@/utils'
+import Img from '@/Img'
+
+export const Wrapper = styled.div`
+ ${css.flex('align-center')};
+`
+export const CopyedHintIcon = styled(Img)`
+ fill: ${theme('baseColor.green')};
+ ${css.size(20)};
+ margin-right: 3px;
+`
diff --git a/src/components/Input/styles/textarea.ts b/src/components/Input/styles/textarea.ts
index 2afc041de..c523ee425 100644
--- a/src/components/Input/styles/textarea.ts
+++ b/src/components/Input/styles/textarea.ts
@@ -17,7 +17,7 @@ export const Wrapper = styled(TextareaAutosize).attrs(
color: ${theme('form.text')};
min-height: 56px;
padding: 6px 10px;
- background-color: #06303b;
+ background-color: #0b2631;
border: 1px solid;
border-color: ${theme('editor.border')};
resize: none;
diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx
index 76e87a12a..03e58233d 100755
--- a/src/components/Modal/index.tsx
+++ b/src/components/Modal/index.tsx
@@ -40,7 +40,7 @@ const Modal: FC = ({
onClose = log,
mode = 'default',
background = 'default',
- offsetTop = '13%',
+ offsetTop = '20%',
}) => {
const { Portal } = usePortal()
const [visibleOnPage, setVisibleOnPage] = useState(false)
diff --git a/src/components/Modal/styles/index.ts b/src/components/Modal/styles/index.ts
index 677a30545..4022ced59 100755
--- a/src/components/Modal/styles/index.ts
+++ b/src/components/Modal/styles/index.ts
@@ -34,8 +34,7 @@ export const Wrapper = styled.div`
max-height: 81vh;
box-shadow: -5px 6px 37px -8px rgba(0, 0, 0, 0.42);
/* border: 1px solid; */
- border-left: 2px solid;
- border-right: 2px solid;
+ border-top: 3px solid;
border-color: ${({ mode }) =>
mode === 'default' ? theme('modal.border') : theme('baseColor.red')};
animation: ${animate.zoomIn} 0.2s linear;
diff --git a/src/containers/layout/GlobalLayout/dynamic.tsx b/src/containers/layout/GlobalLayout/dynamic.tsx
index 92cafb957..d78474728 100644
--- a/src/containers/layout/GlobalLayout/dynamic.tsx
+++ b/src/containers/layout/GlobalLayout/dynamic.tsx
@@ -8,6 +8,12 @@ export const Doraemon = dynamic(() => import('@/containers/tool/Doraemon'), {
ssr: false,
})
+export const Share = dynamic(() => import('@/containers/tool/Share'), {
+ /* eslint-disable react/display-name */
+ loading: () => ,
+ ssr: false,
+})
+
export const AbuseReport = dynamic(
() => import('@/containers/tool/AbuseReport'),
{
diff --git a/src/containers/layout/GlobalLayout/index.tsx b/src/containers/layout/GlobalLayout/index.tsx
index 5e8872f65..24c977d6b 100755
--- a/src/containers/layout/GlobalLayout/index.tsx
+++ b/src/containers/layout/GlobalLayout/index.tsx
@@ -22,7 +22,15 @@ import CustomScroller from '@/components/CustomScroller'
import type { TStore } from './store'
import SEO from './SEO'
-import { AbuseReport, Doraemon, ErrorBox, Footer, ErrorPage } from './dynamic'
+
+import {
+ AbuseReport,
+ Doraemon,
+ ErrorBox,
+ Footer,
+ ErrorPage,
+ Share,
+} from './dynamic'
import { Wrapper, InnerWrapper, BodyWrapper, ContentWrapper } from './styles'
@@ -95,6 +103,7 @@ const GlobalLayoutContainer: FC = ({
{!noSidebar && bannerLayout !== C11N.HOLY_GRAIL && }
+
= ({ show, article }) => {
/>
shareTo()}
size={20}
- mLeft={4}
+ mLeft={5}
mTop={15}
mRight={0}
/>
diff --git a/src/containers/tool/ArticleSticker/styles/index.ts b/src/containers/tool/ArticleSticker/styles/index.ts
index 53c06629a..e8f332568 100755
--- a/src/containers/tool/ArticleSticker/styles/index.ts
+++ b/src/containers/tool/ArticleSticker/styles/index.ts
@@ -10,7 +10,7 @@ export const Wrapper = styled.div.attrs(({ testid }: TTestable) => ({
}))`
${css.flexColumn('align-center', 'justify-start')};
${({ metric }) => css.fitStickerWidth(metric)};
- min-height: 68vh;
+ min-height: 60vh;
`
export const InnerWrapper = styled.div`
${css.flexColumn('justify-between')}
@@ -75,6 +75,6 @@ export const GoTopWrapper = styled.div`
${css.flex('align-both')};
opacity: ${({ show }) => (show ? 1 : 0)};
position: absolute;
- bottom: -50px;
+ bottom: -100px;
width: 100%;
`
diff --git a/src/containers/tool/Share/IFrameBoard.tsx b/src/containers/tool/Share/IFrameBoard.tsx
new file mode 100644
index 000000000..11dcef43d
--- /dev/null
+++ b/src/containers/tool/Share/IFrameBoard.tsx
@@ -0,0 +1,29 @@
+import { FC, memo } from 'react'
+
+import { CopyButton } from '@/components/Buttons'
+
+import {
+ Wrapper,
+ Header,
+ Title,
+ CodeWrapper,
+ Inputer,
+} from './styles/iframe_board'
+
+const IFrameBoard: FC = () => {
+ const code =
+ ''
+ return (
+
+
+
+
+
+
+ )
+}
+
+export default memo(IFrameBoard)
diff --git a/src/containers/tool/Share/InfoPanel.tsx b/src/containers/tool/Share/InfoPanel.tsx
new file mode 100644
index 000000000..44eb91623
--- /dev/null
+++ b/src/containers/tool/Share/InfoPanel.tsx
@@ -0,0 +1,43 @@
+import { FC, memo } from 'react'
+
+import { SITE_SHARE_TYPE } from './constant'
+
+import LinkBoard from './LinkBoard'
+import IFrameBoard from './IFrameBoard'
+import WechatBoard from './WechatBoard'
+
+import { Wrapper } from './styles/info_panel'
+import type { TLinksData } from './store'
+
+type TProps = {
+ type: string
+ linksData: TLinksData
+}
+
+const InfoPanel: FC = ({ type, linksData }) => {
+ switch (type) {
+ case SITE_SHARE_TYPE.EMBED: {
+ return (
+
+
+
+ )
+ }
+ case SITE_SHARE_TYPE.WECHAT: {
+ return (
+
+
+
+ )
+ }
+ default: {
+ return (
+
+
+
+ )
+ }
+ }
+}
+
+export default memo(InfoPanel)
diff --git a/src/containers/tool/Share/LinkBoard.tsx b/src/containers/tool/Share/LinkBoard.tsx
new file mode 100644
index 000000000..854d16c1a
--- /dev/null
+++ b/src/containers/tool/Share/LinkBoard.tsx
@@ -0,0 +1,59 @@
+import { FC, memo, Fragment, useState } from 'react'
+
+import { CopyButton } from '@/components/Buttons'
+import type { TLinksData } from './store'
+
+import {
+ Header,
+ TabWrapper,
+ TabName,
+ BoxWrapper,
+ Inputer,
+} from './styles/link_board'
+
+type TProps = {
+ linksData: TLinksData
+}
+
+const LinkBoard: FC = ({ linksData }) => {
+ const [activeTab, setActiveTab] = useState('link')
+
+ return (
+
+
+
+ setActiveTab('link')}
+ >
+ URL
+
+ setActiveTab('html')}
+ >
+ HTML
+
+ setActiveTab('md')}
+ >
+ MD
+
+ setActiveTab('orgMode')}
+ >
+ OrgMode
+
+
+
+
+
+
+
+
+ )
+}
+
+export default memo(LinkBoard)
diff --git a/src/containers/tool/Share/Platforms.tsx b/src/containers/tool/Share/Platforms.tsx
new file mode 100644
index 000000000..43167d4af
--- /dev/null
+++ b/src/containers/tool/Share/Platforms.tsx
@@ -0,0 +1,86 @@
+import { FC, memo } from 'react'
+
+import { ICON } from '@/config'
+
+import { SHARE_TYPE } from './constant'
+import {
+ Wrapper,
+ InnerWrapper,
+ Media,
+ Logo,
+ LogoWrapper,
+ Title,
+} from './styles/platform'
+import { toPlatform } from './logic'
+
+// 链接分享(包含 md),
+// Embed, twitter, 微信,微博,facebook, QQ 群,QQ 空间,邮箱
+
+const medias = [
+ {
+ id: '0',
+ title: '链接',
+ logo: `${ICON}/shape/link.svg`,
+ small: true,
+ type: SHARE_TYPE.LINKS,
+ },
+ {
+ id: '1',
+ title: '嵌入',
+ logo: `${ICON}/social/embed-share.svg`,
+ small: true,
+ type: SHARE_TYPE.EMBED,
+ },
+ {
+ id: '2',
+ title: 'Twitter',
+ logo: `${ICON}/social/twitter-share.png`,
+ type: SHARE_TYPE.TWITTER,
+ },
+ {
+ id: '3',
+ title: '微信',
+ logo: `${ICON}/social/wechat-share.png`,
+ bg: 'white',
+ type: SHARE_TYPE.WECHAT,
+ },
+ {
+ id: '4',
+ title: '邮箱',
+ logo: `${ICON}/social/mail-share.svg`,
+ small: true,
+ type: SHARE_TYPE.EMAIL,
+ },
+ {
+ id: '6',
+ title: '微博',
+ logo: `${ICON}/social/weibo-share.png`,
+ type: SHARE_TYPE.WEIBO,
+ },
+ {
+ id: '7',
+ title: 'Facebook',
+ logo: `${ICON}/social/facebook-share.png`,
+ bg: 'white',
+ type: SHARE_TYPE.FACEBOOK,
+ },
+]
+
+const Platforms: FC = () => {
+ return (
+
+
+ {medias.map((item) => (
+ toPlatform(item.type)}>
+
+
+
+ {item.title}
+
+ ))}
+
+
+ )
+}
+
+export default memo(Platforms)
diff --git a/src/containers/tool/Share/WechatBoard.tsx b/src/containers/tool/Share/WechatBoard.tsx
new file mode 100644
index 000000000..79c513f46
--- /dev/null
+++ b/src/containers/tool/Share/WechatBoard.tsx
@@ -0,0 +1,26 @@
+import { FC, memo } from 'react'
+
+import QRCode from 'qrcode-react'
+import {
+ Wrapper,
+ QRCodeWrapper,
+ DescTitle,
+ DescWrapper,
+} from './styles/wechat_board'
+
+const WechatBoard: FC = () => {
+ return (
+
+
+
+
+
+ 分享到微信
+ 打开微信 > 发现 > 扫一扫
+ 即可将本文分享到微信。
+
+
+ )
+}
+
+export default memo(WechatBoard)
diff --git a/src/containers/tool/Share/constant.ts b/src/containers/tool/Share/constant.ts
new file mode 100644
index 000000000..6fb4b2b97
--- /dev/null
+++ b/src/containers/tool/Share/constant.ts
@@ -0,0 +1,19 @@
+export const SITE_SHARE_TYPE = {
+ EMBED: 'embed',
+ LINKS: 'links',
+ WECHAT: 'wechat',
+}
+
+export const OUTSIDE_SHARE_TYPE = {
+ TWITTER: 'twitter',
+ EMAIL: 'email',
+ WECHAT: 'wechat',
+ // QQ: 'qq',
+ WEIBO: 'weibo',
+ FACEBOOK: 'facebook',
+}
+
+export const SHARE_TYPE = {
+ ...SITE_SHARE_TYPE,
+ ...OUTSIDE_SHARE_TYPE,
+}
diff --git a/src/containers/tool/Share/index.tsx b/src/containers/tool/Share/index.tsx
new file mode 100755
index 000000000..a4e4db04d
--- /dev/null
+++ b/src/containers/tool/Share/index.tsx
@@ -0,0 +1,42 @@
+/*
+ * Share
+ */
+
+import { FC, Fragment } from 'react'
+
+import { pluggedIn, buildLog } from '@/utils'
+
+import Modal from '@/components/Modal'
+
+import Platforms from './Platforms'
+import InfoPanel from './InfoPanel'
+
+import type { TStore } from './store'
+import { Wrapper } from './styles'
+import { useInit, close } from './logic'
+
+/* eslint-disable-next-line */
+const log = buildLog('C:Share')
+
+type TProps = {
+ share?: TStore
+ testid?: string
+}
+
+const ShareContainer: FC = ({ share: store, testid }) => {
+ useInit(store)
+ const { show, siteShareType, linksData } = store
+
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+export default pluggedIn(ShareContainer) as FC
diff --git a/src/containers/tool/Share/logic.ts b/src/containers/tool/Share/logic.ts
new file mode 100755
index 000000000..171772f0c
--- /dev/null
+++ b/src/containers/tool/Share/logic.ts
@@ -0,0 +1,102 @@
+import { useEffect } from 'react'
+// import { } from 'ramda'
+
+import { buildLog, asyncSuit } from '@/utils'
+import { EVENT } from '@/constant'
+import { SHARE_TYPE } from './constant'
+
+// import S from './schma'
+import type { TStore } from './store'
+
+const { SR71, $solver, asyncRes } = asyncSuit
+
+const sr71$ = new SR71({
+ receive: EVENT.SHARE,
+})
+
+let store: TStore | undefined
+let sub$ = null
+
+/* eslint-disable-next-line */
+const log = buildLog('L:Share')
+
+type TShareParam = {
+ url?: string
+ title?: string
+ text?: string
+ subject?: string
+ body?: string
+}
+const openShareWindow = (platformUrl: string, param: TShareParam): void => {
+ const safeParam = []
+
+ /* eslint-disable */
+ for (const i in param) {
+ safeParam.push(`${i}=${encodeURIComponent(param[i] || '')}`)
+ }
+ /* eslint-enable */
+ const targetUrl = `${platformUrl}?${safeParam.join('&')}`
+
+ window.open(targetUrl, '_blank', 'height=500, width=600')
+}
+
+export const toPlatform = (type: string): void => {
+ const { shareData } = store
+ const { url, title, digest } = shareData
+
+ switch (type) {
+ case SHARE_TYPE.TWITTER: {
+ const param = { url, text: title }
+
+ return openShareWindow('https://twitter.com/intent/tweet', param)
+ // return openShareWindow('https://twitter.com/share', param)
+ }
+
+ case SHARE_TYPE.EMAIL: {
+ const param = { subject: title, body: `${url}\n\n${digest}` }
+ return openShareWindow('mailto:', param)
+ }
+
+ case SHARE_TYPE.FACEBOOK: {
+ const param = { url, title }
+ return openShareWindow('https://facebook.com/sharer/sharer.php', param)
+ }
+
+ case SHARE_TYPE.WEIBO: {
+ const param = { url, title }
+ return openShareWindow('https://service.weibo.com/share/share.php', param)
+ }
+
+ default: {
+ return store.mark({ siteShareType: type })
+ }
+ }
+}
+
+export const close = (): void => {
+ store.mark({ show: false })
+}
+
+// ###############################
+// init & uninit handlers
+// ###############################
+
+const DataResolver = [
+ {
+ match: asyncRes(EVENT.SHARE),
+ action: () => store.mark({ show: true }),
+ },
+]
+
+export const useInit = (_store: TStore): void => {
+ useEffect(() => {
+ store = _store
+
+ if (!sub$) {
+ sub$ = sr71$.data().subscribe($solver(DataResolver, []))
+ }
+
+ log('useInit: ', store)
+ // return () => store.reset()
+ }, [_store])
+}
diff --git a/src/containers/tool/Share/schema.ts b/src/containers/tool/Share/schema.ts
new file mode 100755
index 000000000..0035db982
--- /dev/null
+++ b/src/containers/tool/Share/schema.ts
@@ -0,0 +1,23 @@
+import gql from 'graphql-tag'
+
+const simpleMutation = gql`
+ mutation($id: ID!) {
+ post(id: $id) {
+ id
+ }
+ }
+`
+const simpleQuery = gql`
+ query($filter: filter!) {
+ post(id: $id) {
+ id
+ }
+ }
+`
+
+const schema = {
+ simpleMutation,
+ simpleQuery,
+}
+
+export default schema
diff --git a/src/containers/tool/Share/store.ts b/src/containers/tool/Share/store.ts
new file mode 100755
index 000000000..69c31a23b
--- /dev/null
+++ b/src/containers/tool/Share/store.ts
@@ -0,0 +1,89 @@
+/*
+ * Share store
+ */
+
+import { types as T, getParent, Instance } from 'mobx-state-tree'
+import { values } from 'ramda'
+
+import { SITE_URL_SHORT } from '@/config'
+import type { TArticle, TCommunity, TRootStore, TThread } from '@/spec'
+import { markStates, buildLog, stripMobx } from '@/utils'
+
+import { SITE_SHARE_TYPE } from './constant'
+
+/* eslint-disable-next-line */
+const log = buildLog('S:Share')
+
+export type TShareData = {
+ url: string
+ title: string
+ digest: string
+}
+
+export type TLinksData = {
+ link: string
+ html: string
+ md: string
+ orgMode: string
+}
+
+const Share = T.model('Share', {
+ show: T.optional(T.boolean, false),
+ siteShareType: T.optional(
+ T.enumeration(values(SITE_SHARE_TYPE)),
+ SITE_SHARE_TYPE.LINKS,
+ ),
+})
+ .views((self) => ({
+ get viewingArticle(): TArticle {
+ const root = getParent(self) as TRootStore
+ return root.viewing.viewingArticle
+ },
+ get viewingThread(): TThread {
+ const root = getParent(self) as TRootStore
+ return root.viewing.viewingThread
+ },
+ get shareData(): TShareData {
+ const slf = self as TStore
+
+ const { linksData } = slf
+ const articleTitle = slf.viewingArticle.title
+ const articleDigest = slf.viewingArticle.digest
+
+ return {
+ url: linksData.link,
+ title: articleTitle,
+ digest: articleDigest,
+ }
+ },
+ get linksData(): TLinksData {
+ const slf = self as TStore
+
+ const articleId = slf.viewingArticle.id
+ const articleTitle = slf.viewingArticle.title
+ const thread = 'post' // TODO: use articles' own thread
+
+ const link = `${SITE_URL_SHORT}/${thread}/${articleId}`
+
+ return {
+ link,
+ html: `${articleTitle}`,
+ md: `[${articleTitle}](${link})`,
+ orgMode: `[[${link}][${articleTitle}]]`,
+ }
+ },
+ get curCommunity(): TCommunity {
+ const root = getParent(self) as TRootStore
+
+ return stripMobx(root.viewing.community)
+ },
+ }))
+ .actions((self) => ({
+ mark(sobj: Record): void {
+ markStates(sobj, self)
+ },
+ }))
+
+export type TStore = Instance
+
+export default Share
diff --git a/src/containers/tool/Share/styles/iframe_board.ts b/src/containers/tool/Share/styles/iframe_board.ts
new file mode 100644
index 000000000..e273d4ea7
--- /dev/null
+++ b/src/containers/tool/Share/styles/iframe_board.ts
@@ -0,0 +1,22 @@
+import styled from 'styled-components'
+
+import Input from '@/components/Input'
+import { css, theme } from '@/utils'
+
+export const Wrapper = styled.div`
+ height: 100%;
+`
+export const Header = styled.div`
+ ${css.flex('align-center', 'justify-between')};
+ margin-bottom: 15px;
+`
+export const Title = styled.div`
+ color: ${theme('thread.articleTitle')};
+ font-size: 14px;
+`
+export const CodeWrapper = styled.div`
+ color: ${theme('thread.articleDigest')};
+ font-size: 12px;
+ margin-left: -5px;
+`
+export const Inputer = styled(Input)``
diff --git a/src/containers/tool/Share/styles/index.ts b/src/containers/tool/Share/styles/index.ts
new file mode 100755
index 000000000..088a64365
--- /dev/null
+++ b/src/containers/tool/Share/styles/index.ts
@@ -0,0 +1,14 @@
+import styled from 'styled-components'
+
+import type { TTestable } from '@/spec'
+import { css } from '@/utils'
+
+type TWrapper = { type: string } & TTestable
+
+export const Wrapper = styled.div.attrs(({ testid }: TTestable) => ({
+ 'data-test-id': testid,
+}))`
+ ${css.flexColumn('justify-between')};
+ height: auto;
+`
+export const Title = styled.div``
diff --git a/src/containers/tool/Share/styles/info_panel.ts b/src/containers/tool/Share/styles/info_panel.ts
new file mode 100644
index 000000000..e61b53d3f
--- /dev/null
+++ b/src/containers/tool/Share/styles/info_panel.ts
@@ -0,0 +1,15 @@
+import styled from 'styled-components'
+
+import { theme } from '@/utils'
+
+import { getInfoPanelHeight } from './metric'
+
+export const Wrapper = styled.div<{ type: string }>`
+ padding: 20px 40px;
+ width: 100%;
+ height: ${({ type }) => getInfoPanelHeight(type)};
+ background: #0a313e;
+ color: ${theme('thread.articleTitle')};
+ transition: all 0.1s;
+`
+export const holder = 1
diff --git a/src/containers/tool/Share/styles/link_board.ts b/src/containers/tool/Share/styles/link_board.ts
new file mode 100644
index 000000000..18f6b10de
--- /dev/null
+++ b/src/containers/tool/Share/styles/link_board.ts
@@ -0,0 +1,34 @@
+import styled from 'styled-components'
+
+import type { TActive } from '@/spec'
+import { css, theme } from '@/utils'
+
+import Input from '@/components/Input'
+
+export const Header = styled.div`
+ ${css.flex('justify-between', 'align-center')};
+`
+export const TabWrapper = styled.div`
+ ${css.flex('align-end')};
+`
+export const TabName = styled.div`
+ font-size: 14px;
+ color: ${({ $active }) =>
+ $active ? theme('thread.articleTitle') : theme('thread.articleDigest')};
+ margin-right: 12px;
+ font-weight: ${({ $active }) => ($active ? 'bold' : 'normal')};
+
+ &:hover {
+ color: ${theme('thread.articleTitle')};
+ font-weight: ${({ $active }) => ($active ? 'bold' : 'normal')};
+ cursor: pointer;
+ }
+
+ transition: color 0.2s;
+`
+export const BoxWrapper = styled.div`
+ ${css.flex('align-center')};
+ margin-top: 8px;
+ margin-left: -8px;
+`
+export const Inputer = styled(Input)``
diff --git a/src/containers/tool/Share/styles/metric.ts b/src/containers/tool/Share/styles/metric.ts
new file mode 100644
index 000000000..bd2c9033d
--- /dev/null
+++ b/src/containers/tool/Share/styles/metric.ts
@@ -0,0 +1,20 @@
+import { SITE_SHARE_TYPE } from '../constant'
+
+//
+export const getInfoPanelHeight = (type: string): string => {
+ switch (type) {
+ case SITE_SHARE_TYPE.WECHAT: {
+ return '160px'
+ }
+
+ case SITE_SHARE_TYPE.EMBED: {
+ return '160px'
+ }
+
+ default: {
+ return '110px'
+ }
+ }
+}
+
+export const holder = 1
diff --git a/src/containers/tool/Share/styles/platform.ts b/src/containers/tool/Share/styles/platform.ts
new file mode 100644
index 000000000..a165c8aa3
--- /dev/null
+++ b/src/containers/tool/Share/styles/platform.ts
@@ -0,0 +1,52 @@
+import styled from 'styled-components'
+
+import { css, theme } from '@/utils'
+import Img from '@/Img'
+
+export const Wrapper = styled.div`
+ padding: 20px 20px;
+ width: 100%;
+ min-height: 220px;
+ background: ${theme('modal.bg')};
+ filter: drop-shadow(3px 3px 6px #002a34);
+ transition: min-height 0.2s;
+`
+export const InnerWrapper = styled.div`
+ ${css.flex()};
+ flex-wrap: wrap;
+`
+export const Media = styled.div`
+ ${css.size(80)};
+ ${css.flexColumn('align-both')};
+ margin-bottom: 18px;
+ cursor: pointer;
+`
+export const LogoWrapper = styled.div<{ bg: string | undefined }>`
+ ${css.circle(38)};
+ ${css.flex('align-both')};
+ background: ${({ bg }) => bg || '#164651'};
+ margin-bottom: 5px;
+`
+export const Logo = styled(Img)<{ small: boolean }>`
+ fill: ${theme('thread.articleTitle')};
+ display: block;
+ width: ${({ small }) => (small ? '24px' : '40px')};
+ height: ${({ small }) => (small ? '24px' : '40px')};
+ border-radius: 100%;
+ filter: saturate(0.6);
+ opacity: 0.8;
+
+ ${Media}:hover & {
+ filter: saturate(0.8);
+ opacity: 1;
+ }
+`
+export const Title = styled.div`
+ color: ${theme('thread.articleTitle')};
+ font-weight: 600;
+ font-size: 14px;
+
+ ${Media}:hover & {
+ color: #129698;
+ }
+`
diff --git a/src/containers/tool/Share/styles/wechat_board.ts b/src/containers/tool/Share/styles/wechat_board.ts
new file mode 100644
index 000000000..2755deb6c
--- /dev/null
+++ b/src/containers/tool/Share/styles/wechat_board.ts
@@ -0,0 +1,21 @@
+import styled from 'styled-components'
+
+import { css, theme } from '@/utils'
+
+export const Wrapper = styled.div`
+ ${css.flex('align-center', 'justify-center')};
+ height: 100%;
+ color: ${theme('thread.articleTitle')};
+`
+export const QRCodeWrapper = styled.div`
+ width: 160px;
+`
+export const DescWrapper = styled.div`
+ color: ${theme('thread.articleDigest')};
+ font-size: 13px;
+`
+export const DescTitle = styled.div`
+ color: ${theme('thread.articleTitle')};
+ font-size: 15px;
+ margin-bottom: 10px;
+`
diff --git a/src/containers/tool/Share/tests/index.test.js b/src/containers/tool/Share/tests/index.test.js
new file mode 100755
index 000000000..b43664abd
--- /dev/null
+++ b/src/containers/tool/Share/tests/index.test.js
@@ -0,0 +1,10 @@
+// import React from 'react'
+// import { shallow } from 'enzyme'
+
+// import Share from '../index'
+
+describe('TODO ', () => {
+ it('Expect to have unit tests specified', () => {
+ expect(true).toEqual(true)
+ })
+})
diff --git a/src/containers/tool/Share/tests/store.test.js b/src/containers/tool/Share/tests/store.test.js
new file mode 100755
index 000000000..bcd522473
--- /dev/null
+++ b/src/containers/tool/Share/tests/store.test.js
@@ -0,0 +1,10 @@
+/*
+ * Share store test
+ *
+ */
+
+// import Share from '../index'
+
+it('TODO: store test Share', () => {
+ expect(1 + 1).toBe(2)
+})
diff --git a/src/spec/article.ts b/src/spec/article.ts
index 1e2f7e90a..bde9e64e0 100644
--- a/src/spec/article.ts
+++ b/src/spec/article.ts
@@ -7,6 +7,7 @@ export type TCopyright = 'cc' | 'approve' | 'forbid'
type TBaseArticle = {
id?: TID
title?: string
+ digest?: string
body?: string
views?: number
pin?: boolean
diff --git a/src/stores/RootStore/index.ts b/src/stores/RootStore/index.ts
index f609eb369..a561b8f77 100755
--- a/src/stores/RootStore/index.ts
+++ b/src/stores/RootStore/index.ts
@@ -88,6 +88,7 @@ import {
CoolGuideContentStore,
// GEN: IMPORT SUBSTORE
+ ShareStore,
ArticleContentStore,
ArticleViewerStore,
ArticlesThreadStore,
@@ -201,6 +202,7 @@ const rootStore = T.model({
coolGuideContent: T.optional(CoolGuideContentStore, {}),
// GEN: PLUG SUBSTORE TO ROOTSTORE
+ share: T.optional(ShareStore, {}),
articleContent: T.optional(ArticleContentStore, {}),
articleViewer: T.optional(ArticleViewerStore, {}),
articlesThread: T.optional(ArticlesThreadStore, {}),
diff --git a/src/stores/ViewingStore/index.ts b/src/stores/ViewingStore/index.ts
index da1e12f59..469672c4f 100755
--- a/src/stores/ViewingStore/index.ts
+++ b/src/stores/ViewingStore/index.ts
@@ -66,8 +66,9 @@ const ViewingStore = T.model('ViewingStore', {
},
get viewingArticle(): TArticle {
- if (!self.viewingThread) return {}
- return stripMobx(self[self.viewingThread])
+ const curThread = self.viewingThread || self.activeThread
+ if (!curThread) return {}
+ return stripMobx(self[curThread])
},
}))
.actions((self) => ({
diff --git a/src/stores/index.ts b/src/stores/index.ts
index 94bc43d4a..2f4587efc 100755
--- a/src/stores/index.ts
+++ b/src/stores/index.ts
@@ -94,6 +94,7 @@ export { default as CommunityEditorStore } from '@/containers/editor/CommunityEd
export { default as WorksEditorStore } from '@/containers/editor/WorksEditor/store'
// GEN: EXPORT CONTAINERS STORE HERE
+export { default as ShareStore } from '@/containers/tool/Share/store'
export { default as ArticleContentStore } from '@/containers/content/ArticleContent/store'
export { default as ArticleViewerStore } from '@/containers/viewer/ArticleViewer/store'
export { default as ArticlesThreadStore } from '@/containers/thread/ArticlesThread/store'
diff --git a/utils/constant/event.ts b/utils/constant/event.ts
index 866e75a40..0e5cdf7d0 100755
--- a/utils/constant/event.ts
+++ b/utils/constant/event.ts
@@ -15,6 +15,9 @@ const EVENT = {
CONTENT_DRAGABLE: 'CONTENT_DRAGABLE',
},
+ // share
+ SHARE: 'SHARE',
+
// report
REPORT: 'REPORT',
// new end
diff --git a/utils/helper.ts b/utils/helper.ts
index 3c1736c77..81ccf5033 100755
--- a/utils/helper.ts
+++ b/utils/helper.ts
@@ -146,6 +146,11 @@ export const send = (msg: string, data = {}): void => {
export const closeDrawer = (type = ''): void =>
send(EVENT.DRAWER.CLOSE, { type })
+/**
+ * share articles
+ */
+export const shareTo = (): void => send(EVENT.SHARE, {})
+
/**
* report content
*/
diff --git a/utils/index.ts b/utils/index.ts
index 1bbb9ffdf..ba4bb4e29 100755
--- a/utils/index.ts
+++ b/utils/index.ts
@@ -28,6 +28,7 @@ export {
countWords,
joinUS,
closeDrawer,
+ shareTo,
report,
errRescue,
debounce,
diff --git a/utils/themes/skins/solarized_dark.ts b/utils/themes/skins/solarized_dark.ts
index 04fb8bb98..742abf6fb 100755
--- a/utils/themes/skins/solarized_dark.ts
+++ b/utils/themes/skins/solarized_dark.ts
@@ -288,7 +288,7 @@ const solarizedDark = {
},
modal: {
bg: bannerBg,
- border: primaryColor,
+ border: '#1d5171',
innerSelectBg: '#03323e',
},
form: {
diff --git a/yarn.lock b/yarn.lock
index a3808b636..7dbca9cc8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3575,7 +3575,7 @@ copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.npm.taobao.org/copy-descriptor/download/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
-copy-to-clipboard@^3.2.0:
+copy-to-clipboard@^3, copy-to-clipboard@^3.2.0:
version "3.3.1"
resolved "https://registry.npm.taobao.org/copy-to-clipboard/download/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
dependencies:
@@ -9613,6 +9613,16 @@ q@^1.5.1:
version "1.5.1"
resolved "https://registry.npm.taobao.org/q/download/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
+qr.js@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.npm.taobao.org/qr.js/download/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
+
+qrcode-react@^0.1.16:
+ version "0.1.16"
+ resolved "https://registry.npm.taobao.org/qrcode-react/download/qrcode-react-0.1.16.tgz#d064999d510ffc3e55a9ca3ffcf6c203c69f1517"
+ dependencies:
+ qr.js "0.0.0"
+
qs@6.7.0:
version "6.7.0"
resolved "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@@ -9737,6 +9747,13 @@ react-content-loader@3.4.2:
version "3.4.2"
resolved "https://registry.npm.taobao.org/react-content-loader/download/react-content-loader-3.4.2.tgz#187fbec2aa389635e4613faa046713f196173f40"
+react-copy-to-clipboard@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npm.taobao.org/react-copy-to-clipboard/download/react-copy-to-clipboard-5.0.3.tgz?cache=0&sync_timestamp=1610493190400&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-copy-to-clipboard%2Fdownload%2Freact-copy-to-clipboard-5.0.3.tgz#2a0623b1115a1d8c84144e9434d3342b5af41ab4"
+ dependencies:
+ copy-to-clipboard "^3"
+ prop-types "^15.5.8"
+
react-dom@17.0.2:
version "17.0.2"
resolved "https://registry.nlark.com/react-dom/download/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"