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"