diff --git a/src/schemas/fragments/base.ts b/src/schemas/fragments/base.ts index 3ae673009..6700a645d 100755 --- a/src/schemas/fragments/base.ts +++ b/src/schemas/fragments/base.ts @@ -5,6 +5,7 @@ import EMOTION from '@/constant/emotion' import { titleCase } from '@/fmt' export const community = ` + id title slug index diff --git a/src/spec/article.d.ts b/src/spec/article.d.ts index fee94c58d..eb6ef0458 100755 --- a/src/spec/article.d.ts +++ b/src/spec/article.d.ts @@ -1,8 +1,11 @@ +import type { TColor } from './color' import type { TCommunity, TTag } from '.' import type { TUser, TAccount, TSimpleUser } from './account' import type { TID } from './utils' import type { TEmotion } from './emotion' +export type TArticleTitle = { isPinned?: boolean; viewerHasViewed?: boolean } & TColor + export type TCopyright = 'cc' | 'approve' | 'forbid' export type TArticleMeta = { diff --git a/src/spec/index.ts b/src/spec/index.ts index b8288c707..b9cdfb10d 100755 --- a/src/spec/index.ts +++ b/src/spec/index.ts @@ -145,6 +145,7 @@ export type { TArticleCatReject, TArticleState, TFAQSection, + TArticleTitle, } from './article' export type { diff --git a/src/widgets/ArticleCatState/State.tsx b/src/widgets/ArticleCatState/State.tsx index c3ec3b348..00da54e62 100755 --- a/src/widgets/ArticleCatState/State.tsx +++ b/src/widgets/ArticleCatState/State.tsx @@ -10,7 +10,6 @@ import { Trans } from '@/i18n' import { aliasGTDDoneState } from '@/fmt' import type { TProps as TArticleStateBadgeProps } from '.' - import { Wrapper, WipIcon, Text, TODOIcon, DoneIcon, RejectIcon } from './styles/state' type TProps = Pick diff --git a/src/widgets/ArticlePinLabel/index.tsx b/src/widgets/ArticlePinLabel/index.tsx index 3cbd54e51..6322cc811 100755 --- a/src/widgets/ArticlePinLabel/index.tsx +++ b/src/widgets/ArticlePinLabel/index.tsx @@ -7,6 +7,8 @@ import { FC, memo } from 'react' import { buildLog } from '@/logger' +import usePrimaryColor from '@/hooks/usePrimaryColor' + import { PinIcon } from './styles' /* eslint-disable-next-line */ @@ -20,7 +22,9 @@ export type TProps = { } } const ArticlePinLabel: FC = ({ article, top = 24, left = -30 }) => { - if (article.isPinned) return + const primaryColor = usePrimaryColor() + + if (article.isPinned) return return null } diff --git a/src/widgets/ArticlePinLabel/styles/index.ts b/src/widgets/ArticlePinLabel/styles/index.ts index 958ec6643..045c8fa9e 100755 --- a/src/widgets/ArticlePinLabel/styles/index.ts +++ b/src/widgets/ArticlePinLabel/styles/index.ts @@ -1,19 +1,19 @@ import styled from 'styled-components' -import css, { theme } from '@/css' +import type { TColor } from '@/spec' +import css, { rainbow } from '@/css' import PinSVG from '@/icons/Pin' import { pixelAdd } from '@/dom' -type TPos = { top: number; left: number } +type TPos = { top: number; left: number } & TColor export const PinIcon = styled(PinSVG)` - fill: ${theme('article.digest')}; + fill: ${({ $color }) => rainbow($color)}; position: absolute; ${css.size(18)}; top: ${({ top }) => pixelAdd(`${top}px`, -4)}; left: ${({ left }) => `${left}px`}; opacity: 0.8; - transform: rotate(-30deg); ` export const holder = 1 diff --git a/src/widgets/ArticleReadLabel/index.tsx b/src/widgets/ArticleReadLabel/index.tsx index 6f14c1079..a6f7420c0 100755 --- a/src/widgets/ArticleReadLabel/index.tsx +++ b/src/widgets/ArticleReadLabel/index.tsx @@ -5,6 +5,7 @@ import { FC } from 'react' import { observer } from 'mobx-react' +import type { TSpace } from '@/spec' import { buildLog } from '@/logger' import useAccount from '@/hooks/useAccount' @@ -14,24 +15,22 @@ import { ReadedLabel } from './styles' const log = buildLog('w:ArticleReadLabel:index') export type TProps = { - top?: number - left?: number article: { viewerHasViewed?: boolean isPinned?: boolean } -} -const ArticleReadLabel: FC = ({ article, top = 24, left = -30 }) => { - const accountInfo = useAccount() - const { isPinned, viewerHasViewed } = article + size?: number +} & TSpace - if (!accountInfo.isLogin || isPinned) return null +const ArticleReadLabel: FC = ({ article, size = 8, ...restProps }) => { + const accountInfo = useAccount() + const { viewerHasViewed } = article - const { markViewed } = accountInfo.customization + if (!accountInfo.isLogin) return null // return - if (markViewed && !viewerHasViewed) { - return + if (!viewerHasViewed) { + return } return null diff --git a/src/widgets/ArticleReadLabel/styles/index.ts b/src/widgets/ArticleReadLabel/styles/index.ts index 089a63440..2111db12e 100755 --- a/src/widgets/ArticleReadLabel/styles/index.ts +++ b/src/widgets/ArticleReadLabel/styles/index.ts @@ -1,29 +1,15 @@ import styled from 'styled-components' import css, { theme } from '@/css' -import PinSVG from '@/icons/Pin' -import { pixelAdd } from '@/dom' +import { WithMargin } from '@/widgets/Common' -type TPos = { top: number; left: number } - -export const ReadedLabel = styled.div` - background: ${theme('article.info')}; - ${css.circle(8)}; - position: absolute; - top: ${({ top }) => `${top}px`}; - left: ${({ left }) => `${left}px`}; - opacity: 0.5; +export const ReadedLabel = styled(WithMargin)<{ size: number }>` + ${({ size }) => css.circle(size)}; + background: ${theme('hint')}; + opacity: 0.6; ${css.media.mobile` ${css.circle(6)}; `}; ` -export const PinIcon = styled(PinSVG)` - fill: ${theme('article.digest')}; - position: absolute; - ${css.size(18)}; - top: ${({ top }) => pixelAdd(`${top}px`, -4)}; - left: ${({ left }) => `${left}px`}; - opacity: 0.8; - transform: rotate(-30deg); -` +export const holder = 1 diff --git a/src/widgets/ArticleSettingMenu/Menu/CatMenuItem.tsx b/src/widgets/ArticleSettingMenu/Menu/CatItem.tsx similarity index 89% rename from src/widgets/ArticleSettingMenu/Menu/CatMenuItem.tsx rename to src/widgets/ArticleSettingMenu/Menu/CatItem.tsx index f49401a03..4132caab0 100644 --- a/src/widgets/ArticleSettingMenu/Menu/CatMenuItem.tsx +++ b/src/widgets/ArticleSettingMenu/Menu/CatItem.tsx @@ -12,7 +12,7 @@ type TProps = { onClick: () => void } -const CatMenuItem: FC = ({ onClick }) => { +const CatItem: FC = ({ onClick }) => { const { article } = useViewingArticle() if (article.cat) { @@ -37,4 +37,4 @@ const CatMenuItem: FC = ({ onClick }) => { ) } -export default observer(CatMenuItem) +export default observer(CatItem) diff --git a/src/widgets/ArticleSettingMenu/Menu/PinItem.tsx b/src/widgets/ArticleSettingMenu/Menu/PinItem.tsx new file mode 100644 index 000000000..2fdfaa9d9 --- /dev/null +++ b/src/widgets/ArticleSettingMenu/Menu/PinItem.tsx @@ -0,0 +1,50 @@ +import { FC, useCallback, useEffect, useState } from 'react' +import { observer } from 'mobx-react' +import { useMutation } from 'urql' + +import { toast, updateViewingArticle } from '@/signal' +import { SpaceGrow } from '@/widgets/Common' +import useViewingArticle from '@/hooks/useViewingArticle' + +import S from '../schema' +import { Icon } from '../styles/icon' +import { MenuItem } from '../styles/menu' + +const PinItem: FC = () => { + const { article } = useViewingArticle() + const [result, pinPost] = useMutation(S.pinPost) + const [result2, undoPinPost] = useMutation(S.undoPinPost) + + const [pin, setPin] = useState(article.isPinned) + + useEffect(() => { + setPin(article.isPinned) + }, []) + + const handlePin = useCallback(() => { + const action = !pin + ? pinPost({ id: article.id, communityId: article.originalCommunity.id }) + : undoPinPost({ id: article.id, communityId: article.originalCommunity.id }) + + action.then((result) => { + if (result.error) { + toast('设置失败', 'error') + } else { + toast('设置完成') + setPin(!pin) + updateViewingArticle({ id: article.id, isPinned: !pin }) + } + }) + }, [pin, article, pinPost, undoPinPost]) + + return ( + + {pin ? : } + {pin ? '取消置顶' : '置顶'} + + {result.fetching && } + + ) +} + +export default observer(PinItem) diff --git a/src/widgets/ArticleSettingMenu/Menu/StateMenuItem.tsx b/src/widgets/ArticleSettingMenu/Menu/StateItem.tsx similarity index 92% rename from src/widgets/ArticleSettingMenu/Menu/StateMenuItem.tsx rename to src/widgets/ArticleSettingMenu/Menu/StateItem.tsx index d9c4d79a6..fe6405e63 100644 --- a/src/widgets/ArticleSettingMenu/Menu/StateMenuItem.tsx +++ b/src/widgets/ArticleSettingMenu/Menu/StateItem.tsx @@ -15,7 +15,7 @@ type TProps = { onClick: () => void } -const StateMenuItem: FC = ({ onClick }) => { +const StateItem: FC = ({ onClick }) => { const { article } = useViewingArticle() const bgColors = useKanbanBgColors() @@ -44,4 +44,4 @@ const StateMenuItem: FC = ({ onClick }) => { ) } -export default observer(StateMenuItem) +export default observer(StateItem) diff --git a/src/widgets/ArticleSettingMenu/Menu/index.tsx b/src/widgets/ArticleSettingMenu/Menu/index.tsx index 6c7f402cf..9c4470991 100644 --- a/src/widgets/ArticleSettingMenu/Menu/index.tsx +++ b/src/widgets/ArticleSettingMenu/Menu/index.tsx @@ -9,8 +9,9 @@ import type { TSubMenu } from '../spec' import { SUB_MENU_TYPE } from '../constant' import SubMenu from '../SubMenu' -import CatMenuItem from './CatMenuItem' -import StateMenuItem from './StateMenuItem' +import CatItem from './CatItem' +import PinItem from './PinItem' +import StateItem from './StateItem' import { Icon } from '../styles/icon' import { Wrapper, MenuItem, DangerMenuItem, ItemDivider } from '../styles/menu' @@ -67,8 +68,8 @@ const Menu: FC = ({ onSubMenuToggle, onClose }) => { - openSubMenu(SUB_MENU_TYPE.CATEGORY)} /> - openSubMenu(SUB_MENU_TYPE.STATE)} /> + openSubMenu(SUB_MENU_TYPE.CATEGORY)} /> + openSubMenu(SUB_MENU_TYPE.STATE)} /> openSubMenu(SUB_MENU_TYPE.TAG)}> = ({ onSubMenuToggle, onClose }) => { - - - 置顶 - + 关闭评论 diff --git a/src/widgets/ArticleSettingMenu/schema.ts b/src/widgets/ArticleSettingMenu/schema.ts index a3d6c2566..5a0a5712e 100644 --- a/src/widgets/ArticleSettingMenu/schema.ts +++ b/src/widgets/ArticleSettingMenu/schema.ts @@ -25,10 +25,29 @@ const setPostState = gql` } ` +const pinPost = gql` + mutation ($id: ID!, $communityId: ID!) { + pinPost(id: $id, communityId: $communityId) { + id + } + } +` + +const undoPinPost = gql` + mutation ($id: ID!, $communityId: ID!) { + undoPinPost(id: $id, communityId: $communityId) { + id + isPinned + } + } +` + const schema = { updateTitle, setPostCat, setPostState, + pinPost, + undoPinPost, } export default schema diff --git a/src/widgets/ArticleSettingMenu/styles/icon.ts b/src/widgets/ArticleSettingMenu/styles/icon.ts index 1eb8a1a7f..88051c2aa 100644 --- a/src/widgets/ArticleSettingMenu/styles/icon.ts +++ b/src/widgets/ArticleSettingMenu/styles/icon.ts @@ -3,9 +3,11 @@ import styled from 'styled-components' import type { TColor, TActive } from '@/spec' import { ARTICLE_CAT, ARTICLE_STATE } from '@/constant/gtd' -import css, { rainbow, theme } from '@/css' +import css, { animate, rainbow, theme } from '@/css' import PinSVG from '@/icons/Pin' +import UnPinSVG from '@/icons/UnPin' // +import SpinSVG from '@/icons/Spin' import EditSVG from '@/icons/EditPen' import CategorySVG from '@/icons/Category' import SlugSVG from '@/icons/Slug' @@ -107,7 +109,14 @@ export const Icon = { Pin: styled(commonIcon(PinSVG))` margin-top: 2px; `, - + UnPin: styled(commonIcon(UnPinSVG))` + ${css.size(15)}; + margin-left: -1px; + `, + Spin: styled(commonIcon(SpinSVG))` + ${css.size(11)}; + animation: ${animate.rotate360} 0.8s linear infinite; + `, [ARTICLE_CAT.FEATURE]: commonIcon(LightSVG), [ARTICLE_CAT.QUESTION]: commonIcon(QuestionSVG), [ARTICLE_CAT.BUG]: commonIcon(BugSVG), diff --git a/src/widgets/Icons/Spin.tsx b/src/widgets/Icons/Spin.tsx new file mode 100644 index 000000000..10b76e11f --- /dev/null +++ b/src/widgets/Icons/Spin.tsx @@ -0,0 +1,11 @@ +import { memo, SVGProps } from 'react' + +const SVG = (props: SVGProps) => { + return ( + + + + ) +} + +export default memo(SVG) diff --git a/src/widgets/Icons/UnPin.tsx b/src/widgets/Icons/UnPin.tsx new file mode 100644 index 000000000..965790010 --- /dev/null +++ b/src/widgets/Icons/UnPin.tsx @@ -0,0 +1,9 @@ +import { FC } from 'react' + +const SvgComponent: FC = (props) => ( + + + +) + +export default SvgComponent diff --git a/src/widgets/PostItem/QuoraLayout/DesktopView/Header.tsx b/src/widgets/PostItem/QuoraLayout/DesktopView/Header.tsx index 2ff806222..9a7708e21 100644 --- a/src/widgets/PostItem/QuoraLayout/DesktopView/Header.tsx +++ b/src/widgets/PostItem/QuoraLayout/DesktopView/Header.tsx @@ -6,10 +6,12 @@ import TimeAgo from 'timeago-react' import type { TPost } from '@/spec' import useViewingCommunity from '@/hooks/useViewingCommunity' import useBannerLayout from '@/hooks/useBannerLayout' +import usePrimaryColor from '@/hooks/usePrimaryColor' import { THREAD } from '@/constant/thread' import { BANNER_LAYOUT } from '@/constant/layout' import SIZE from '@/constant/size' +import ArticleReadLabel from '@/widgets/ArticleReadLabel' import Tooltip from '@/widgets/Tooltip' import { SpaceGrow, Space } from '@/widgets/Common' import TagsList from '@/widgets/TagsList' @@ -37,6 +39,8 @@ type TProps = { const Header: FC = ({ article, onClick }) => { const { slug } = useViewingCommunity() const bannerLayout = useBannerLayout() + const primaryColor = usePrimaryColor() + const { author, title, commentsCount, innerId, articleTags, insertedAt } = article return ( @@ -57,9 +61,17 @@ const Header: FC = ({ article, onClick }) => {
- e.preventDefault()} href={`/${slug}/${THREAD.POST}/${innerId}`}> + <Title + onClick={(e) => e.preventDefault()} + href={`/${slug}/${THREAD.POST}/${innerId}`} + isPinned={article.isPinned} + viewerHasViewed={article.viewerHasViewed} + $color={primaryColor} + > + <ArticleReadLabel article={article} right={8} top={1} size={7} /> {title} + {/* @ts-ignore */} diff --git a/src/widgets/PostItem/QuoraLayout/DesktopView/index.tsx b/src/widgets/PostItem/QuoraLayout/DesktopView/index.tsx index c991fc4d9..2f957afa3 100644 --- a/src/widgets/PostItem/QuoraLayout/DesktopView/index.tsx +++ b/src/widgets/PostItem/QuoraLayout/DesktopView/index.tsx @@ -4,7 +4,6 @@ import type { TPost } from '@/spec' import { previewArticle } from '@/signal' -import ArticleReadLabel from '@/widgets/ArticleReadLabel' import ArticlePinLabel from '@/widgets/ArticlePinLabel' import ViewingSign from '../../ViewingSign' @@ -21,9 +20,9 @@ type TProps = { const DesktopView: FC = ({ article }) => { return ( - - - + + +
previewArticle(article)} /> previewArticle(article)}>{article.digest} diff --git a/src/widgets/PostItem/ViewingSign.tsx b/src/widgets/PostItem/ViewingSign.tsx index da6ca2fa3..3c15d1897 100755 --- a/src/widgets/PostItem/ViewingSign.tsx +++ b/src/widgets/PostItem/ViewingSign.tsx @@ -8,9 +8,10 @@ import { Wrapper, ViewIcon } from './styles/viewing_sign' type TProps = { article: TPost top?: number + left?: number } -const ViewingSign: FC = ({ article, top = 30 }) => { +const ViewingSign: FC = ({ article, top = 30, left = -30 }) => { const viewingArticle = useViewing() // !viewingArticle means drawer closed @@ -21,7 +22,7 @@ const ViewingSign: FC = ({ article, top = 30 }) => { if (!(article.innerId === id && community === article.originalCommunitySlug)) return null return ( - + ) diff --git a/src/widgets/PostItem/styles/quora_layout/desktop_view/header.ts b/src/widgets/PostItem/styles/quora_layout/desktop_view/header.ts index f0621b875..a73de86c9 100644 --- a/src/widgets/PostItem/styles/quora_layout/desktop_view/header.ts +++ b/src/widgets/PostItem/styles/quora_layout/desktop_view/header.ts @@ -1,6 +1,7 @@ import styled from 'styled-components' -import css, { theme } from '@/css' +import type { TArticleTitle } from '@/spec' +import css, { rainbow, rainbowLight, theme } from '@/css' import DotDivider from '@/widgets/DotDivider' import { Wrapper as ItemWrapper } from '.' @@ -16,14 +17,35 @@ export const Main = styled.div` ${css.rowGrow('align-center')}; color: ${theme('article.title')}; ` -export const Title = styled.a` +export const Title = styled.a` + ${css.row('align-center')}; position: relative; text-decoration: none; - font-size: 15px; + font-size: ${({ isPinned }) => (isPinned ? '16px' : '15px;')}; font-weight: 600; letter-spacing: 0.03em; opacity: 0.85; - color: ${theme('article.title')}; + color: ${({ isPinned, $color }) => (isPinned ? rainbow($color) : theme('article.title'))}; + + &:before { + content: ''; + position: absolute; + display: ${({ isPinned }) => (isPinned ? 'block' : 'none')}; + left: 0; + bottom: 4px; + background: ${(props) => { + const { $color } = props + // @ts-ignore + const colorVal = rainbowLight($color)(props) + + return `linear-gradient(180deg, transparent 30%, ${colorVal} 0)` + }}; + opacity: 1; + width: 30%; + height: 10px; + border-radius: 3px; + z-index: -1; + } @media (max-width: 1450px) { ${css.cutRest('500px')}; @@ -37,11 +59,12 @@ export const Title = styled.a` ${ItemWrapper}:hover & { text-decoration: underline; - text-decoration-color: ${theme('hint')}; + text-decoration-color: ${({ isPinned, $color }) => + isPinned ? rainbow($color) : theme('hint')}; } &:hover { - color: ${theme('article.digest')}; + color: ${({ isPinned, $color }) => (isPinned ? rainbow($color) : theme('article.digest'))}; cursor: pointer; } diff --git a/src/widgets/PostItem/styles/viewing_sign.ts b/src/widgets/PostItem/styles/viewing_sign.ts index cd6e74e8c..e0ad9e8e6 100755 --- a/src/widgets/PostItem/styles/viewing_sign.ts +++ b/src/widgets/PostItem/styles/viewing_sign.ts @@ -3,14 +3,15 @@ import styled from 'styled-components' import css, { animate, theme } from '@/css' import ViewSVG from '@/icons/View' -export const Wrapper = styled.div<{ top: number }>` +type TWrapper = { top: number; left: number } +export const Wrapper = styled.div` ${css.row()}; position: absolute; - left: -30px; top: ${({ top }) => `${top}px`}; - animation: ${animate.fadeInRight} 0.25s linear; + left: ${({ left }) => `${left}px`}; + animation: ${animate.fadeInRight} 0.3s linear; ` export const ViewIcon = styled(ViewSVG)` ${css.size(10)}; - fill: ${theme('article.info')}; + fill: ${theme('hint')}; ` diff --git a/utils/themes/skins/day.ts b/utils/themes/skins/day.ts index e7027af9d..e8a76c1aa 100644 --- a/utils/themes/skins/day.ts +++ b/utils/themes/skins/day.ts @@ -89,8 +89,8 @@ const day = { cyanLight: '#00B5CC', cyanLightBg: '#e1fcff94', - blue: '#0073E3', - blueBg: '#d8e3fd54', + blue: '#5073C6', + blueBg: '#E7EDF7', purple: '#7d519e', purpleBg: '#f7d8fd38', diff --git a/utils/themes/skins/night.ts b/utils/themes/skins/night.ts index fec585897..54c48d09a 100644 --- a/utils/themes/skins/night.ts +++ b/utils/themes/skins/night.ts @@ -237,7 +237,7 @@ const night = { primary: primaryColor, border: '#414141', bg: '#2c2c2c', - fg: '#c1c1c1', + fg: 'white', ghost: '#9A9696', disabledFg: descText, hoverBg: lighten(primaryColor, 10),