diff --git a/src/components/ArtimentBody/FoldBox.tsx b/src/components/ArtimentBody/FoldBox.tsx index 5a81d39f4..6f2dbe64d 100644 --- a/src/components/ArtimentBody/FoldBox.tsx +++ b/src/components/ArtimentBody/FoldBox.tsx @@ -12,7 +12,11 @@ type TProps = { const FoldBox: FC = ({ fold, onFold, onExpand, mode }) => { return ( - (fold ? onExpand() : onFold())}> + (fold ? onExpand() : onFold())} + > {!fold && ( 折叠 diff --git a/src/components/ArtimentBody/styles/fold_box.ts b/src/components/ArtimentBody/styles/fold_box.ts index d81c90901..3d3595323 100755 --- a/src/components/ArtimentBody/styles/fold_box.ts +++ b/src/components/ArtimentBody/styles/fold_box.ts @@ -4,9 +4,10 @@ import Img from '@/Img' // import { theme } from '@/utils/themes' import css from '@/utils/css' -export const Wrapper = styled.div<{ fold: boolean }>` +type TWrapper = { fold: boolean; mode: 'article' | 'comment' } +export const Wrapper = styled.div` ${css.flex('align-both')}; - width: 100%; + width: ${({ mode }) => (mode === 'article' ? '100%' : '96%')}; margin-top: 28px; margin-bottom: 28px; padding: 5px 0; diff --git a/src/components/Buttons/FollowButton/FollowingBtn.tsx b/src/components/Buttons/FollowButton/FollowingBtn.tsx index 847ed31ec..7a7316bdf 100644 --- a/src/components/Buttons/FollowButton/FollowingBtn.tsx +++ b/src/components/Buttons/FollowButton/FollowingBtn.tsx @@ -1,7 +1,6 @@ import { FC, memo } from 'react' import { TSIZE_TSM } from '@/spec' -import { ICON } from '@/config' import { LavaLampLoading } from '@/components/dynamic' import Tooltip from '@/components/Tooltip' @@ -9,18 +8,25 @@ import Tooltip from '@/components/Tooltip' import { BtnWrapper, Popinfo, - CheckedIcon, + FollowingIcon, FollowingButton, } from '../styles/follow_button' type TProps = { size: TSIZE_TSM loading: boolean + followingOffset: number text: string onClick: () => void } -const FollowingBtn: FC = ({ size, loading, text, onClick }) => { +const FollowingBtn: FC = ({ + size, + loading, + followingOffset, + text, + onClick, +}) => { return ( <> {loading ? ( @@ -34,13 +40,14 @@ const FollowingBtn: FC = ({ size, loading, text, onClick }) => { > - + {text} diff --git a/src/components/Buttons/FollowButton/index.tsx b/src/components/Buttons/FollowButton/index.tsx index 6aed44f09..34b36277e 100644 --- a/src/components/Buttons/FollowButton/index.tsx +++ b/src/components/Buttons/FollowButton/index.tsx @@ -19,9 +19,10 @@ type TProps = { userId?: TID size?: TSIZE_TSM loading?: boolean - fakeLoading?: boolean + simuLoading?: boolean followText?: string followingText?: string + followingOffset?: number onFollow?: (userId: TID) => void onUndoFollow?: (userId: TID) => void } @@ -29,38 +30,40 @@ type TProps = { const FollowButton: FC = ({ userId, size = SIZE.SMALL, - fakeLoading = false, + simuLoading = true, loading = false, hasFollowed = false, followText = '关 注', followingText = '已关注', + followingOffset = 0, onFollow = log, onUndoFollow = log, }) => { - const [simuLoading, setSimuLoading] = useState(false) - const isLoading = fakeLoading ? simuLoading : loading + const [fakeLoading, setFakeLoading] = useState(false) + const isLoading = simuLoading ? fakeLoading : loading const handleFollow = useCallback(() => { - if (fakeLoading) { - setSimuLoading(true) - setTimeout(() => setSimuLoading(false), 1500) + if (simuLoading) { + setFakeLoading(true) + setTimeout(() => setFakeLoading(false), 1500) } onFollow(userId) - }, [fakeLoading, onFollow, userId]) + }, [simuLoading, onFollow, userId]) const handleUndoFollow = useCallback(() => { - if (fakeLoading) { - setSimuLoading(true) - setTimeout(() => setSimuLoading(false), 1500) + if (simuLoading) { + setFakeLoading(true) + setTimeout(() => setFakeLoading(false), 1500) } onUndoFollow(userId) - }, [fakeLoading, onUndoFollow, userId]) + }, [simuLoading, onUndoFollow, userId]) return ( <> {hasFollowed ? ( ` ${css.size(15)}; animation: ${animate.rotate360} 1s linear infinite; ` -export const CheckedIcon = styled(BtnIcon)` +export const FollowingIcon = styled(JoinEyeSVG)` fill: ${theme('baseColor.green')}; + ${css.size(15)}; + margin-right: 3px; + transform: scaleX(0.9); + margin-top: -1px; + ${BtnWrapper}:hover & { + fill: ${theme('thread.articleTitle')}; + } ` export const FollowedButton = styled(Button)` border-radius: 10px; ` -export const FollowingButton = styled(Button)` - color: ${theme('thread.articleTitle')}; - /* color: ${theme('baseColor.green')}; */ +export const FollowingButton = styled(Button)<{ followingOffset: number }>` + color: ${theme('baseColor.green')}; + font-weight: bold; border: none; - border-radius: 10px; - background: #003745; + border-radius: 8px; + margin-left: ${({ followingOffset }) => `${followingOffset}px` || 0}; + /* background: #034556; */ padding-top: 2px; padding-bottom: 2px; &:hover { - background: #003745; - /* border: 1px solid; */ + color: ${theme('thread.articleTitle')}; + background: #034556; } ` diff --git a/src/components/CommunityJoinSign/index.tsx b/src/components/CommunityJoinSign/index.tsx new file mode 100755 index 000000000..ffb6c812a --- /dev/null +++ b/src/components/CommunityJoinSign/index.tsx @@ -0,0 +1,55 @@ +/* + * CommunityJoinSign + */ + +import { FC, memo } from 'react' + +import { ICON_CMD } from '@/config' +import { buildLog } from '@/utils/logger' + +import FollowButton from '@/components/Buttons/FollowButton' + +import { + Wrapper, + PopContentWrapper, + PopHeader, + PopHeaderIcon, + PopHeaderText, + PopHighlight, +} from './styles' + +/* eslint-disable-next-line */ +const log = buildLog('c:VerifiedSign:index') + +const PopContent = () => { + return ( + + + + 官方认证 + +
+ 我们已通过各种渠道证实该社区为{' '} + communityTitle 官方开通 +
+
+ ) +} + +// type TProps = {} + +const CommunityJoinSign: FC = () => { + const hasFollowed = false + return ( + + + + ) +} + +export default memo(CommunityJoinSign) diff --git a/src/components/CommunityJoinSign/styles/index.ts b/src/components/CommunityJoinSign/styles/index.ts new file mode 100755 index 000000000..825599a48 --- /dev/null +++ b/src/components/CommunityJoinSign/styles/index.ts @@ -0,0 +1,35 @@ +import styled from 'styled-components' + +import Img from '@/Img' +import { theme } from '@/utils/themes' +import css from '@/utils/css' + +export const Wrapper = styled.div<{ hasFollowed: boolean }>` + ${css.flex('align-center')}; + margin-left: ${({ hasFollowed }) => (hasFollowed ? '-8px' : '5px')}; +` +export const PopContentWrapper = styled.div` + text-align: left; + width: 200px; + font-size: 13px !important; + line-height: 1.6; +` +export const PopHeader = styled.div` + ${css.flex('align-center')} + margin-bottom: 10px; +` +export const PopHeaderIcon = styled(Img)` + fill: ${theme('baseColor.green')}; + padding: 0; + margin-right: 4px; + ${css.size(14)}; +` +export const PopHeaderText = styled.div` + color: ${theme('baseColor.green')}; + font-size: 13px; + font-weight: bold; +` +export const PopHighlight = styled.span` + font-size: 14px; + font-weight: bold; +` diff --git a/src/containers/editor/PostEditor/tests/index.test.ts b/src/components/CommunityJoinSign/tests/index.test.ts similarity index 66% rename from src/containers/editor/PostEditor/tests/index.test.ts rename to src/components/CommunityJoinSign/tests/index.test.ts index 6753001c6..f411665e2 100755 --- a/src/containers/editor/PostEditor/tests/index.test.ts +++ b/src/components/CommunityJoinSign/tests/index.test.ts @@ -1,9 +1,9 @@ // import React from 'react' // import { shallow } from 'enzyme' -// import PostEditor from '../index' +// import VerifiedSign from '../index' -describe('TODO ', () => { +describe('TODO ', () => { it('Expect to have unit tests specified', () => { expect(true).toEqual(true) }) diff --git a/src/components/CommunityStatesPad/index.tsx b/src/components/CommunityStatesPad/index.tsx index ffaa8483f..a16b97e2c 100755 --- a/src/components/CommunityStatesPad/index.tsx +++ b/src/components/CommunityStatesPad/index.tsx @@ -40,13 +40,13 @@ const CommunityStatesPad: FC = ({ onShowSubscriberList = log, withoutFounding = true, }) => { - const { editorsCount, subscribersCount, viewerHasSubscribed } = community + const { editorsCount, subscribersCount } = community const { isMobile } = usePlatform() const contentsCount = getContentCount(community) return ( - + {!isMobile && 成员} ` ${css.flexColumn('align-end')}; - background-color: ${({ active }) => - active ? theme('banner.numberHoverBg') : ''}; padding: 0 5px; border-radius: 4px; diff --git a/src/components/EmotionSelector/Panel.tsx b/src/components/EmotionSelector/Panel.tsx index fdd7372c0..13d1c810e 100644 --- a/src/components/EmotionSelector/Panel.tsx +++ b/src/components/EmotionSelector/Panel.tsx @@ -1,8 +1,12 @@ import { FC, memo } from 'react' import { values } from 'ramda' +import type { TEmotion, TEmotionType } from '@/spec' + import { EMOTION } from '@/constant' import { ICON } from '@/config' + +import { isViewerEmotioned } from './helper' import { Wrapper, Item, EIcon, Name } from './styles/panel' const Trans = { @@ -14,15 +18,33 @@ const Trans = { pill: '药丸', } -const EmojiPanel: FC = () => { +type TProps = { + emotions: TEmotion[] + onAction?: (name: TEmotionType, hasEmotioned: boolean) => void +} + +const EmojiPanel: FC = ({ emotions, onAction }) => { return ( - {values(EMOTION).map((item) => ( - - - {Trans[item]} - - ))} + {values(EMOTION).map((name) => { + const viewerHasEmotioned = isViewerEmotioned(emotions, name) + + return ( + onAction(name, viewerHasEmotioned)} + > + + {Trans[name]} + + ) + })} ) } diff --git a/src/components/EmotionSelector/SelectedEmotions.tsx b/src/components/EmotionSelector/SelectedEmotions.tsx deleted file mode 100644 index 2cccea3ac..000000000 --- a/src/components/EmotionSelector/SelectedEmotions.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * EmojiSelector - */ - -import { FC, memo, Fragment } from 'react' -import { buildLog } from '@/utils/logger' -import { keys } from 'ramda' - -import type { TEmotion, TSimpleUser } from '@/spec' -import { titleCase } from '@/utils/helper' - -import UsersPanel from './UsersPanel' - -/* eslint-disable-next-line */ -const log = buildLog('c:SelectedEmotions:index') - -const getEmotionName = (item): string => { - const eCountKey = keys(item)[0] as string - return eCountKey.split('Count')[0] -} - -type TProps = { - emotions: TEmotion[] -} - -const SelectedEmotions: FC = ({ emotions }) => { - return ( - - {emotions.map((item) => { - const eName = getEmotionName(item) as string - const count = item[`${eName}Count`] as number - const users = item[`latest${titleCase(eName)}Users`] as TSimpleUser[] - - return ( - - ) - })} - - ) -} - -export default memo(SelectedEmotions) diff --git a/src/components/EmotionSelector/SelectedEmotions/EmotionIcon.tsx b/src/components/EmotionSelector/SelectedEmotions/EmotionIcon.tsx new file mode 100644 index 000000000..b73f47741 --- /dev/null +++ b/src/components/EmotionSelector/SelectedEmotions/EmotionIcon.tsx @@ -0,0 +1,15 @@ +import { FC, memo } from 'react' +import { ICON } from '@/config' + +import type { TEmotionType } from '@/spec' +import { EIcon } from '../styles/selected_emotions/emotion_icon' + +type TProps = { + name: TEmotionType +} + +const EmotionIcon: FC = ({ name }) => { + return +} + +export default memo(EmotionIcon) diff --git a/src/components/EmotionSelector/SelectedEmotions/EmotionUnit.tsx b/src/components/EmotionSelector/SelectedEmotions/EmotionUnit.tsx new file mode 100644 index 000000000..d1ada86e5 --- /dev/null +++ b/src/components/EmotionSelector/SelectedEmotions/EmotionUnit.tsx @@ -0,0 +1,59 @@ +/* + * EmojiSelector + */ + +import { FC, memo } from 'react' +import { buildLog } from '@/utils/logger' + +import type { TEmotion, TSimpleUser, TEmotionType } from '@/spec' + +import { titleCase } from '@/utils/helper' +import Tooltip from '@/components/Tooltip' +import AnimatedCount from '@/components/AnimatedCount' + +import EmotionIcon from './EmotionIcon' +import UsersPanel from './UsersPanel' +import { getEmotionName } from '../helper' + +import { Wrapper, Count } from '../styles/selected_emotions/emotion_unit' + +/* eslint-disable-next-line */ +const log = buildLog('c:EmotionUnit:index') + +type TProps = { + item: TEmotion + onAction?: (name: TEmotionType, hasEmotioned: boolean) => void +} + +const EmotionUnit: FC = ({ item, onAction }) => { + const name = getEmotionName(item) + const count = item[`${name}Count`] as number + const users = item[`latest${titleCase(name)}Users`] as TSimpleUser[] + const hasEmotioned = item[`viewerHas${titleCase(name)}ed`] as boolean + + return ( + + } + noPadding + > + onAction(name as TEmotionType, hasEmotioned)} + > + + + + + + + ) +} + +export default memo(EmotionUnit) diff --git a/src/components/EmotionSelector/SelectedEmotions/UsersPanel.tsx b/src/components/EmotionSelector/SelectedEmotions/UsersPanel.tsx new file mode 100644 index 000000000..7144b1c9b --- /dev/null +++ b/src/components/EmotionSelector/SelectedEmotions/UsersPanel.tsx @@ -0,0 +1,48 @@ +/* + * EmojiSelector + */ + +import { FC, memo } from 'react' +import { buildLog } from '@/utils/logger' + +import type { TSimpleUser, TEmotionType } from '@/spec' + +import EmotionIcon from './EmotionIcon' + +import { + Wrapper, + UsersWrapper, + Units, + Username, +} from '../styles/selected_emotions/users_panel' + +/* eslint-disable-next-line */ +const log = buildLog('c:UsersPanel:index') + +type TProps = { + name: TEmotionType + count: number + users: TSimpleUser[] + hasEmotioned: boolean +} + +const UsersPanel: FC = ({ name, count, users, hasEmotioned }) => { + const showUnit = users.length > count + + return ( + + + {users.slice(0, 5).map((u, index) => ( + + {u.nickname} + {users.length - 1 !== index ? ',' : ''} + + ))} + {showUnit && 等 {count} 人} + {' '} + + + ) +} + +export default memo(UsersPanel) diff --git a/src/components/EmotionSelector/SelectedEmotions/index.tsx b/src/components/EmotionSelector/SelectedEmotions/index.tsx new file mode 100644 index 000000000..9c7b9463e --- /dev/null +++ b/src/components/EmotionSelector/SelectedEmotions/index.tsx @@ -0,0 +1,32 @@ +/* + * EmojiSelector + */ + +import { FC, memo, Fragment } from 'react' +import { buildLog } from '@/utils/logger' + +import type { TEmotion, TEmotionType } from '@/spec' + +import { getEmotionName } from '../helper' +import EmotionUnit from './EmotionUnit' +/* eslint-disable-next-line */ +const log = buildLog('c:SelectedEmotions:index') + +type TProps = { + emotions: TEmotion[] + onAction?: (name: TEmotionType, hasEmotioned: boolean) => void +} + +const SelectedEmotions: FC = ({ emotions, onAction }) => { + return ( + + {emotions.map((item) => { + const name = getEmotionName(item) as string + + return + })} + + ) +} + +export default memo(SelectedEmotions) diff --git a/src/components/EmotionSelector/UsersPanel.tsx b/src/components/EmotionSelector/UsersPanel.tsx deleted file mode 100644 index 24ec3e9e2..000000000 --- a/src/components/EmotionSelector/UsersPanel.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * EmojiSelector - */ - -import { FC, memo } from 'react' -import { buildLog } from '@/utils/logger' - -import type { TSimpleUser } from '@/spec' -import { ICON } from '@/config' - -import Tooltip from '@/components/Tooltip' -import AnimatedCount from '@/components/AnimatedCount' - -import { - Wrapper, - EIcon, - PopHint, - PopUsers, - Count, - Units, - Username, -} from './styles/users_panel' - -/* eslint-disable-next-line */ -const log = buildLog('c:UsersPanel:index') - -type TProps = { - name: string - count: number - users: TSimpleUser[] -} - -const UsersPanel: FC = ({ name, count, users }) => { - const showUnit = users.length > count - - const emotionIcon = ( - - ) - - return ( - - - {users.slice(0, 5).map((u, index) => ( - - {u.nickname} - {users.length - 1 !== index ? ',' : ''} - - ))} - {showUnit && 等 {count} 人} - {' '} - {emotionIcon} - - } - noPadding - > - - {emotionIcon} - - - - - - ) -} - -export default memo(UsersPanel) diff --git a/src/components/EmotionSelector/helper.ts b/src/components/EmotionSelector/helper.ts new file mode 100644 index 000000000..ec33fa0a4 --- /dev/null +++ b/src/components/EmotionSelector/helper.ts @@ -0,0 +1,40 @@ +import { includes, reject, keys, values } from 'ramda' + +import { EMOTION } from '@/constant' +import type { TEmotion, TEmotionType } from '@/spec' +import { titleCase } from '@/utils/helper' + +export const getEmotionName = (item: TEmotion): TEmotionType => { + const eCountKey = keys(item)[0] as string + return eCountKey.split('Count')[0] as TEmotionType +} + +export const emotionsCoverter = (selectedEmotions: TEmotion): TEmotion[] => { + const converted = [] + values(EMOTION).forEach((emotion) => + converted.push({ + [`${emotion}Count`]: selectedEmotions[`${emotion}Count`], + [`latest${titleCase(emotion)}Users`]: + selectedEmotions[`latest${titleCase(emotion)}Users`], + [`viewerHas${titleCase(emotion)}ed`]: + selectedEmotions[`viewerHas${titleCase(emotion)}ed`], + }), + ) + + return reject((e) => includes(0, values(e)), converted) +} + +export const isViewerEmotioned = ( + emotions: TEmotion[], + name: TEmotionType, +): boolean => { + for (let i = 0; i < emotions.length; i += 1) { + const emotionUnit = emotions[i] + + if (emotionUnit[`viewerHas${titleCase(name)}ed`]) { + return true + } + } + + return false +} diff --git a/src/components/EmotionSelector/index.tsx b/src/components/EmotionSelector/index.tsx index 6637a6f8c..308d6b409 100755 --- a/src/components/EmotionSelector/index.tsx +++ b/src/components/EmotionSelector/index.tsx @@ -3,15 +3,13 @@ */ import { FC, memo } from 'react' -import { values, reject, includes } from 'ramda' -import type { TEmotion } from '@/spec' +import type { TEmotion, TEmotionType } from '@/spec' import { buildLog } from '@/utils/logger' -import { titleCase } from '@/utils/helper' -import { EMOTION } from '@/constant' import IconButton from '@/components/Buttons/IconButton' import Tooltip from '@/components/Tooltip' +import { emotionsCoverter } from './helper' import SelectedEmotions from './SelectedEmotions' import Panel from './Panel' import { Wrapper } from './styles' @@ -19,34 +17,26 @@ import { Wrapper } from './styles' /* eslint-disable-next-line */ const log = buildLog('c:EmotionSelector:index') -const emotionsCoverter = (selectedEmotions: TEmotion): TEmotion[] => { - const converted = [] - values(EMOTION).forEach((emotion) => - converted.push({ - [`${emotion}Count`]: selectedEmotions[`${emotion}Count`], - [`latest${titleCase(emotion)}Users`]: - selectedEmotions[`latest${titleCase(emotion)}Users`], - [`viewerHas${titleCase(emotion)}ed`]: - selectedEmotions[`viewerHas${titleCase(emotion)}ed`], - }), - ) - - return reject((e) => includes(0, values(e)), converted) -} - type TProps = { testid?: string emotions: TEmotion + onAction?: (name: TEmotionType, hasEmotioned: boolean) => void } const EmotionSelector: FC = ({ testid = 'emotion-selector', + onAction = log, emotions, }) => { + const validEmotions = emotionsCoverter(emotions) return ( - - } trigger="click" noPadding> + + } + trigger="click" + noPadding + > diff --git a/src/components/EmotionSelector/styles/panel.ts b/src/components/EmotionSelector/styles/panel.ts index 55c6c9764..92eed7ef6 100644 --- a/src/components/EmotionSelector/styles/panel.ts +++ b/src/components/EmotionSelector/styles/panel.ts @@ -1,5 +1,7 @@ import styled from 'styled-components' +import { includes } from 'ramda' +import type { TActive } from '@/spec' import Img from '@/Img' import css from '@/utils/css' import { theme } from '@/utils/themes' @@ -14,13 +16,15 @@ export const Item = styled.div<{ name: string }>` ${css.flexColumn('align-center', 'justify-center')}; margin-right: ${({ name }) => (name === 'pill' ? 0 : '15px')}; ` -export const EIcon = styled(Img)<{ name: string }>` - margin-top: ${({ name }) => (name === 'downvote' ? '2px' : 0)}; - ${({ name }) => - name === 'confused' || name === 'popcorn' ? css.size(21) : css.size(20)}; +type TEIcon = { name: string } & TActive +export const EIcon = styled(Img)` + margin-top: ${({ name }) => + includes(name, ['downvote', 'beer']) ? '2px' : 0}; + margin-bottom: ${({ name }) => (name === 'heart' ? '1px' : 0)}; + ${({ name }) => (name === 'confused' ? css.size(21) : css.size(20))}; - filter: saturate(0.6); - opacity: 0.9; + filter: ${({ $active }) => ($active ? 'saturate(1)' : 'saturate(0.6)')}; + opacity: ${({ $active }) => ($active ? 1 : 0.9)}; z-index: 1; ${Item}:hover & { @@ -29,13 +33,14 @@ export const EIcon = styled(Img)<{ name: string }>` opacity: 1; } ` -export const Name = styled.div` +export const Name = styled.div` font-size: 11px; margin-top: 5px; - color: ${theme('thread.articleDigest')}; + color: ${({ $active }) => + $active ? '#12999B' : theme('thread.articleTitle')}; ${Item}:hover & { cursor: pointer; - color: ${theme('thread.articleTitle')}; + color: #12999b; } ` diff --git a/src/components/EmotionSelector/styles/selected_emotions/emotion_icon.ts b/src/components/EmotionSelector/styles/selected_emotions/emotion_icon.ts new file mode 100644 index 000000000..f5323a097 --- /dev/null +++ b/src/components/EmotionSelector/styles/selected_emotions/emotion_icon.ts @@ -0,0 +1,15 @@ +import styled from 'styled-components' + +import Img from '@/Img' +import css from '@/utils/css' + +export const EIcon = styled(Img)<{ name: string }>` + margin-top: ${({ name }) => (name === 'downvote' ? '2px' : 0)}; + ${({ name }) => + name === 'confused' || name === 'popcorn' ? css.size(15) : css.size(14)}; + margin-right: 6px; + + filter: saturate(0.6); + opacity: 0.9; +` +export const holder = 1 diff --git a/src/components/EmotionSelector/styles/selected_emotions/emotion_unit.ts b/src/components/EmotionSelector/styles/selected_emotions/emotion_unit.ts new file mode 100644 index 000000000..aa6ed3884 --- /dev/null +++ b/src/components/EmotionSelector/styles/selected_emotions/emotion_unit.ts @@ -0,0 +1,27 @@ +import styled from 'styled-components' + +import type { TActive } from '@/spec' +import css from '@/utils/css' + +export const Wrapper = styled.div` + ${css.flex('align-center')}; + cursor: pointer; + margin-right: 14px; + margin-right: 5px; + padding: 0 5px; + border-radius: 5px; + margin-left: -5px; + background: ${({ $active }) => ($active ? '#00333D' : 'transparent')}; + + &:hover { + background: #023c4a; + } +` + +export const Count = styled.div` + opacity: 0.8; + + ${Wrapper}:hover & { + color: #00a59b; + } +` diff --git a/src/components/EmotionSelector/styles/selected_emotions/users_panel.ts b/src/components/EmotionSelector/styles/selected_emotions/users_panel.ts new file mode 100644 index 000000000..d17c6018e --- /dev/null +++ b/src/components/EmotionSelector/styles/selected_emotions/users_panel.ts @@ -0,0 +1,25 @@ +import styled from 'styled-components' + +import { theme } from '@/utils/themes' +import css from '@/utils/css' + +export const Wrapper = styled.div` + ${css.flex('align-center')}; + padding: 3px 5px; + padding-left: 10px; +` +export const UsersWrapper = styled.div` + ${css.flex('align-center')}; + font-size: 13px; +` +export const Username = styled.div` + color: ${theme('thread.articleTitle')}; + font-size: 13px; + margin-right: 5px; +` +export const Units = styled.div` + color: ${theme('thread.articleDigest')}; + margin-left: 3px; + margin-right: 3px; + font-size: 13px; +` diff --git a/src/components/EmotionSelector/styles/users_panel.ts b/src/components/EmotionSelector/styles/users_panel.ts deleted file mode 100644 index a927dd8dd..000000000 --- a/src/components/EmotionSelector/styles/users_panel.ts +++ /dev/null @@ -1,55 +0,0 @@ -import styled from 'styled-components' - -import Img from '@/Img' -import { theme } from '@/utils/themes' -import css from '@/utils/css' - -export const Wrapper = styled.div` - ${css.flex('align-center')}; - cursor: pointer; - margin-right: 14px; - margin-right: 5px; - padding: 0 5px; - border-radius: 5px; - margin-left: -5px; - - &:hover { - background: #023c4a; - } -` -export const EIcon = styled(Img)<{ name: string }>` - margin-top: ${({ name }) => (name === 'downvote' ? '2px' : 0)}; - ${({ name }) => - name === 'confused' || name === 'popcorn' ? css.size(15) : css.size(14)}; - margin-right: 6px; - - filter: saturate(0.6); - opacity: 0.9; -` -export const Count = styled.div` - opacity: 0.8; - - ${Wrapper}:hover & { - color: #00a59b; - } -` -export const PopHint = styled.div` - ${css.flex('align-center')}; - padding: 3px 5px; - padding-left: 10px; -` -export const PopUsers = styled.div` - ${css.flex('align-center')}; - font-size: 13px; -` -export const Username = styled.div` - color: ${theme('thread.articleTitle')}; - font-size: 13px; - margin-right: 5px; -` -export const Units = styled.div` - color: ${theme('thread.articleDigest')}; - margin-left: 3px; - margin-right: 3px; - font-size: 13px; -` diff --git a/src/components/ExpandIcon/index.js b/src/components/ExpandIcon/index.tsx similarity index 58% rename from src/components/ExpandIcon/index.js rename to src/components/ExpandIcon/index.tsx index 5975a289d..0d308a49c 100755 --- a/src/components/ExpandIcon/index.js +++ b/src/components/ExpandIcon/index.tsx @@ -4,10 +4,10 @@ * */ -import React, { useState } from 'react' -import T from 'prop-types' +import { FC, memo, useState, ReactNode } from 'react' + +import type { TSIZE_SM } from '@/spec' -// import { ICON_CMD } from '@/config' import { isString } from '@/utils/validator' import { buildLog } from '@/utils/logger' import { SIZE } from '@/constant' @@ -19,14 +19,24 @@ import { Wrapper, Icon, Text } from './styles' /* eslint-disable-next-line */ const log = buildLog('c:ExpandIcon:index') -const ExpandIcon = ({ - icon, +type TProps = { + content: ReactNode + text: string + icon?: ReactNode | string + hideOnClick?: boolean + hideTextOnInit?: boolean + size?: TSIZE_SM + type?: 'default' | 'green' +} + +const ExpandIcon: FC = ({ + icon = '', text, content, - hideOnClick, - type, - size, - hideTextOnInit, + hideOnClick = false, + type = 'default', + size = SIZE.MEDIUM, + hideTextOnInit = true, }) => { const [active, setActive] = useState(false) @@ -45,7 +55,7 @@ const ExpandIcon = ({ hideTextOnInit={hideTextOnInit} > {isString(icon) ? ( - + ) : ( {icon} )} @@ -62,21 +72,4 @@ const ExpandIcon = ({ ) } -ExpandIcon.propTypes = { - content: T.node.isRequired, - text: T.string.isRequired, - icon: T.oneOfType([T.string, T.node]).isRequired, - hideOnClick: T.oneOf([true, false]), - type: T.oneOf(['default', 'green']), - size: T.oneOf([SIZE.SMALL, SIZE.MEDIUM]), - hideTextOnInit: T.oneOf([true, false]), -} - -ExpandIcon.defaultProps = { - hideOnClick: false, - type: 'default', - size: SIZE.MEDIUM, - hideTextOnInit: true, -} - -export default React.memo(ExpandIcon) +export default memo(ExpandIcon) diff --git a/src/components/ExpandIcon/styles/index.ts b/src/components/ExpandIcon/styles/index.ts index 8aaea90f2..ad2861ac1 100755 --- a/src/components/ExpandIcon/styles/index.ts +++ b/src/components/ExpandIcon/styles/index.ts @@ -21,8 +21,8 @@ type TIcon = { type TText = { active: boolean - type: string - size: string + type?: string + size?: string hideTextOnInit: boolean } @@ -58,7 +58,7 @@ export const Icon = styled(Img)` active ? getActiveIconSize(size) : getNormalIconSize(size)}; ${Wrapper}:hover & { - fill: ${theme('thread.articleTitle')}; + /* fill: ${theme('thread.articleTitle')}; */ width: ${({ active, size }) => active ? getActiveIconSize(size) : getNormalIconSize(size)}; height: ${({ active, size }) => diff --git a/src/components/Header/UserAccount.tsx b/src/components/Header/UserAccount.tsx index e57032119..2459b25dd 100755 --- a/src/components/Header/UserAccount.tsx +++ b/src/components/Header/UserAccount.tsx @@ -34,17 +34,16 @@ const UserAccount: FC = () => { {account.isLogin ? ( - 使用 Github 登陆: + Github 登陆: {account.user.login} - 主页面板 ) => { + return ( + + + + + + + ) +} + +export default memo(JoinEye) diff --git a/src/components/Upvote/ArticleLayout.tsx b/src/components/Upvote/ArticleLayout.tsx index 5b4d9e8ce..1e00dcbf0 100644 --- a/src/components/Upvote/ArticleLayout.tsx +++ b/src/components/Upvote/ArticleLayout.tsx @@ -21,14 +21,14 @@ type TProps = { testid?: string count?: number viewerHasUpvoted?: boolean - alias?: string // 觉得很赞(default), 觉得很酷(works), 学到了(blog), 感兴趣(meetup), 有意思(Radar) + onAction?: (viewerHasUpvoted: boolean) => void } const Upvote: FC = ({ testid = 'upvote', count = 0, viewerHasUpvoted = false, - alias = '觉得很赞', + onAction = log, }) => { return ( @@ -36,6 +36,7 @@ const Upvote: FC = ({ diff --git a/src/components/Upvote/BlogListLayout.tsx b/src/components/Upvote/BlogListLayout.tsx index 28ade841a..b80d18d00 100644 --- a/src/components/Upvote/BlogListLayout.tsx +++ b/src/components/Upvote/BlogListLayout.tsx @@ -20,17 +20,19 @@ type TProps = { testid?: string count?: number viewerHasUpvoted?: boolean + onAction?: (viewerHasUpvoted: boolean) => void } const Upvote: FC = ({ testid = 'upvote', count = 0, viewerHasUpvoted = false, + onAction = log, }) => { return ( - + diff --git a/src/components/Upvote/CommentLayout.tsx b/src/components/Upvote/CommentLayout.tsx index 7729b8169..79797ccaa 100644 --- a/src/components/Upvote/CommentLayout.tsx +++ b/src/components/Upvote/CommentLayout.tsx @@ -20,22 +20,22 @@ type TProps = { testid?: string count?: number viewerHasUpvoted?: boolean - alias?: string // 觉得很赞(default), 觉得很酷(works), 学到了(blog), 感兴趣(meetup), 有意思(Radar) + onAction?: (viewerHasUpvoted: boolean) => void } const Upvote: FC = ({ testid = 'upvote', count = 0, viewerHasUpvoted = false, - alias = '觉得很赞', + onAction = log, }) => { return ( - + - + ) diff --git a/src/components/Upvote/DefaultLayout.tsx b/src/components/Upvote/DefaultLayout.tsx index 920b64caa..27718548f 100644 --- a/src/components/Upvote/DefaultLayout.tsx +++ b/src/components/Upvote/DefaultLayout.tsx @@ -26,6 +26,7 @@ type TProps = { viewerHasUpvoted?: boolean alias?: string // 觉得很赞(default), 觉得很酷(works), 学到了(blog), 感兴趣(meetup), 有意思(Radar) avatarList?: TUser[] + onAction?: (viewerHasUpvoted: boolean) => void } const Upvote: FC = ({ @@ -34,13 +35,14 @@ const Upvote: FC = ({ viewerHasUpvoted = false, avatarsRowLimit = 3, alias = '觉得很赞', + onAction = log, avatarList, }) => { const noOne = count === 0 return ( - + {!noOne && } diff --git a/src/components/Upvote/PostListLayout.tsx b/src/components/Upvote/PostListLayout.tsx index a5f21af02..2348a91e8 100644 --- a/src/components/Upvote/PostListLayout.tsx +++ b/src/components/Upvote/PostListLayout.tsx @@ -20,17 +20,19 @@ type TProps = { testid?: string count?: number viewerHasUpvoted?: boolean + onAction?: (viewerHasUpvoted: boolean) => void } const Upvote: FC = ({ testid = 'upvote', count = 0, viewerHasUpvoted = false, + onAction = log, }) => { return ( - + diff --git a/src/components/Upvote/UpvoteBtn.tsx b/src/components/Upvote/UpvoteBtn.tsx index a2067a893..702cc0b35 100644 --- a/src/components/Upvote/UpvoteBtn.tsx +++ b/src/components/Upvote/UpvoteBtn.tsx @@ -6,7 +6,7 @@ import { FC, memo, useState, useCallback } from 'react' -import type { TUser, TUpvoteLayout } from '@/spec' +import type { TUpvoteLayout } from '@/spec' import { buildLog } from '@/utils/logger' import { @@ -23,22 +23,21 @@ import { const log = buildLog('c:Upvote:index') type TProps = { - testid?: string type?: TUpvoteLayout - num?: number viewerHasUpvoted?: boolean - alias?: string - avatarList?: TUser[] + onAction: (viewerHasUpvoted: boolean) => void } const UpvoteBtn: FC = ({ type = 'default', viewerHasUpvoted = false, + onAction, }) => { const [showAnimation, setShowAnimation] = useState(false) const [num, setNum] = useState(0) const handleClick = useCallback(() => { + onAction(!viewerHasUpvoted) if (viewerHasUpvoted) return setNum(num + 1) @@ -47,7 +46,7 @@ const UpvoteBtn: FC = ({ setTimeout(() => setShowAnimation(false), 950) } - }, [showAnimation, viewerHasUpvoted, num]) + }, [showAnimation, viewerHasUpvoted, num, onAction]) return ( diff --git a/src/components/Upvote/WorksArticleLayout.tsx b/src/components/Upvote/WorksArticleLayout.tsx index a2d3959d0..34fcbd4ec 100644 --- a/src/components/Upvote/WorksArticleLayout.tsx +++ b/src/components/Upvote/WorksArticleLayout.tsx @@ -34,6 +34,7 @@ type TProps = { avatarsRowLimit?: number alias?: string // 觉得很赞(default), 觉得很酷(works), 学到了(blog), 感兴趣(meetup), 有意思(Radar) avatarList?: TUser[] + onAction?: (viewerHasUpvoted: boolean) => void } const Upvote: FC = ({ @@ -43,6 +44,7 @@ const Upvote: FC = ({ avatarsRowLimit = 3, alias = '觉得很酷', avatarList, + onAction = log, }) => { const noOne = count === 0 @@ -52,6 +54,7 @@ const Upvote: FC = ({ diff --git a/src/components/Upvote/WorksCardLayout.tsx b/src/components/Upvote/WorksCardLayout.tsx index 65b521c8a..403f4f599 100644 --- a/src/components/Upvote/WorksCardLayout.tsx +++ b/src/components/Upvote/WorksCardLayout.tsx @@ -20,19 +20,19 @@ type TProps = { testid?: string count?: number viewerHasUpvoted?: boolean - alias?: string // 觉得很赞(default), 觉得很酷(works), 学到了(blog), 感兴趣(meetup), 有意思(Radar) + onAction?: (viewerHasUpvoted: boolean) => void } const Upvote: FC = ({ testid = 'upvote', count = 0, viewerHasUpvoted = false, - alias = '觉得很赞', + onAction = log, }) => { return ( - + diff --git a/src/components/Upvote/index.tsx b/src/components/Upvote/index.tsx index c002c2b96..176ebc2b5 100755 --- a/src/components/Upvote/index.tsx +++ b/src/components/Upvote/index.tsx @@ -29,6 +29,7 @@ type TProps = { viewerHasUpvoted?: boolean alias?: string // 觉得很赞(default), 觉得很酷(works), 学到了(blog), 感兴趣(meetup), 有意思(Radar) avatarList?: TUser[] + onAction?: (did: boolean) => void } const Upvote: FC = ({ type = UPVOTE_LAYOUT.DEFAULT, ...restProps }) => { diff --git a/src/components/Upvote/styles/comment_layout.ts b/src/components/Upvote/styles/comment_layout.ts index 534932622..24003a422 100755 --- a/src/components/Upvote/styles/comment_layout.ts +++ b/src/components/Upvote/styles/comment_layout.ts @@ -12,6 +12,7 @@ export const Wrapper = styled.div.attrs(({ testid }: TTestable) => ({ }))` ${css.flexColumn('align-both')}; margin-left: -9px; + margin-top: 2px; ` export const UpWrapper = styled.div` margin-left: 7px; diff --git a/src/containers/content/ExploreContent/SubscribeBtn.tsx b/src/containers/content/ExploreContent/SubscribeBtn.tsx index 0537e9337..87a80b81a 100755 --- a/src/containers/content/ExploreContent/SubscribeBtn.tsx +++ b/src/containers/content/ExploreContent/SubscribeBtn.tsx @@ -35,7 +35,6 @@ const SubscribeBtn: FC = ({ hasFollowed={community.viewerHasSubscribed} onFollow={() => subscribe(community.id)} onUndoFollow={() => unSubscribe(community.id)} - fakeLoading /> ) } diff --git a/src/containers/digest/ArticleDigest/DesktopView/FixedHeader.tsx b/src/containers/digest/ArticleDigest/DesktopView/FixedHeader.tsx index f92891604..e7d1710c2 100644 --- a/src/containers/digest/ArticleDigest/DesktopView/FixedHeader.tsx +++ b/src/containers/digest/ArticleDigest/DesktopView/FixedHeader.tsx @@ -50,7 +50,7 @@ const FixedHeader: FC = ({ metric = METRIC.ARTICLE, testid = 'article-fixed-header', }) => { - const { upvotesCount, meta } = article + const { upvotesCount, meta, viewerHasUpvoted } = article const thread = meta.thread.toLowerCase() as TThread return ( @@ -60,7 +60,11 @@ const FixedHeader: FC = ({ - + diff --git a/src/containers/digest/ArticleDigest/DesktopView/PostLayout/OriginalCommunity.tsx b/src/containers/digest/ArticleDigest/DesktopView/PostLayout/OriginalCommunity.tsx index 6e607f622..90626e71d 100644 --- a/src/containers/digest/ArticleDigest/DesktopView/PostLayout/OriginalCommunity.tsx +++ b/src/containers/digest/ArticleDigest/DesktopView/PostLayout/OriginalCommunity.tsx @@ -1,11 +1,13 @@ import { FC, memo } from 'react' import type { TCommunity } from '@/spec' +import { HCN } from '@/constant' import FollowButton from '@/components/Buttons/FollowButton' import { Wrapper, + HomeLogo, Icon, Name, JoinDesc, @@ -16,12 +18,18 @@ type TProps = { } const OriginalCommunity: FC = ({ community }) => { + const hasFollowed = true + return ( - + {community.raw === HCN ? : } {community.title} {community.subscribersCount} 人加入 - + ) } diff --git a/src/containers/digest/ArticleDigest/styles/desktop_view/post_layout/original_community.ts b/src/containers/digest/ArticleDigest/styles/desktop_view/post_layout/original_community.ts index 8be75c2f8..fa67ff458 100644 --- a/src/containers/digest/ArticleDigest/styles/desktop_view/post_layout/original_community.ts +++ b/src/containers/digest/ArticleDigest/styles/desktop_view/post_layout/original_community.ts @@ -3,16 +3,19 @@ import styled from 'styled-components' import Img from '@/Img' import { theme } from '@/utils/themes' import css from '@/utils/css' +import SiteLogo from '@/icons/CPLogo' export const Wrapper = styled.nav` ${css.flexColumn('align-both')}; - margin-top: 2px; - /* margin-left: 8px; - + /* ${css.media.laptopL` margin-left: -40px; `} */ ` +export const HomeLogo = styled(SiteLogo)` + ${css.size(32)}; + fill: #007fa8; +` export const Icon = styled(Img)` ${css.size(32)}; ` @@ -25,5 +28,6 @@ export const Name = styled.div` export const JoinDesc = styled.div` color: ${theme('thread.articleDigest')}; font-size: 12px; + margin-top: 1px; margin-bottom: 10px; ` diff --git a/src/containers/digest/CommunityDigest/ClassicLayout/CommunityBrief.tsx b/src/containers/digest/CommunityDigest/ClassicLayout/CommunityBrief.tsx index 0d5682df9..9d0f50703 100644 --- a/src/containers/digest/CommunityDigest/ClassicLayout/CommunityBrief.tsx +++ b/src/containers/digest/CommunityDigest/ClassicLayout/CommunityBrief.tsx @@ -1,5 +1,4 @@ import { FC, memo } from 'react' -import dynamic from 'next/dynamic' import { contains } from 'ramda' import type { TCommunity } from '@/spec' @@ -9,6 +8,7 @@ import { HCN, NON_FILL_COMMUNITY } from '@/constant' import ExpandTexts from '../ExpandTexts' import SocialList from '../SocialList' +import CommunityJoinSign from '@/components/CommunityJoinSign' import { Wrapper, Logo, @@ -20,12 +20,6 @@ import { LogoHolder, } from '../styles/classic_layout/community_brief' -export const VerifiedSign = dynamic(() => import('@/components/VerifiedSign'), { - /* eslint-disable react/display-name */ - loading: () =>
, - ssr: false, -}) - const CommunityLogoHolder = `${ICON_CMD}/community_logo_holder.svg` type TProps = { @@ -51,7 +45,7 @@ const CommunityBrief: FC = ({ community, descExpand }) => { <TitleText>{community.title}</TitleText> - <VerifiedSign /> + {community.raw !== HCN && <CommunityJoinSign />} {/* {community.desc} */} diff --git a/src/containers/digest/CommunityDigest/ExpandTexts.tsx b/src/containers/digest/CommunityDigest/ExpandTexts.tsx index 6975ae881..b98d85c51 100644 --- a/src/containers/digest/CommunityDigest/ExpandTexts.tsx +++ b/src/containers/digest/CommunityDigest/ExpandTexts.tsx @@ -1,14 +1,7 @@ import { FC, memo } from 'react' -import { ICON } from '@/config' - -import { - Wrapper, - Normal, - Desc, - IconWrapper, - MoreIcon, -} from './styles/expand_texts' +import IconButton from '@/components/Buttons/IconButton' +import { Wrapper, Normal, Desc, IconWrapper } from './styles/expand_texts' import { toggleDescExpand } from './logic' const placeholder = '可能是最性感的开发者社区.' @@ -24,7 +17,7 @@ const ExpandTexts: FC = ({ descExpand, desc = placeholder }) => { {desc} - + diff --git a/src/containers/digest/CommunityDigest/styles/classic_layout/community_brief.ts b/src/containers/digest/CommunityDigest/styles/classic_layout/community_brief.ts index 65bb23fab..486335335 100644 --- a/src/containers/digest/CommunityDigest/styles/classic_layout/community_brief.ts +++ b/src/containers/digest/CommunityDigest/styles/classic_layout/community_brief.ts @@ -50,7 +50,7 @@ export const TitleWrapper = styled.div` ${css.flex('align-center')}; ` export const Title = styled.div<{ descExpand: boolean }>` - ${css.flex('align-baseline')}; + ${css.flex('align-center')}; font-size: ${({ descExpand }) => (descExpand ? '21px' : '18px')}; color: ${theme('banner.title')}; ` diff --git a/src/containers/digest/CommunityDigest/styles/expand_texts.ts b/src/containers/digest/CommunityDigest/styles/expand_texts.ts index 843fe722a..2065c65ff 100644 --- a/src/containers/digest/CommunityDigest/styles/expand_texts.ts +++ b/src/containers/digest/CommunityDigest/styles/expand_texts.ts @@ -1,6 +1,6 @@ import styled from 'styled-components' -import Img from '@/Img' +// import Img from '@/Img' import { theme } from '@/utils/themes' import css from '@/utils/css' @@ -36,16 +36,3 @@ export const Desc = styled.div` export const IconWrapper = styled.div` margin-left: 8px; ` -export const MoreIcon = styled(Img)` - fill: #2c8e8b; - ${css.size(16)}; - cursor: pointer; - opacity: 0.8; - margin-bottom: -2px; - - &:hover { - fill: #2c8e8b; - opacity: 1; - } - transition: all 0.25s; -` diff --git a/src/containers/editor/PostEditor/Editor.js b/src/containers/editor/PostEditor/Editor.js deleted file mode 100755 index db35f7aa9..000000000 --- a/src/containers/editor/PostEditor/Editor.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Editor based on Draft - */ - -import React from 'react' -// import T from 'prop-types' - -import ArticleEditToolbar from '@/components/ArticleEditToolbar' -import EditorFooter from './EditorFooter' - -import { Wrapper, TitleInput, FooterWrapper } from './styles/editor' - -import { - inputOnChange, - // bodyInputOnChange, - // onMention, - // onMentionSearch, - changeView, -} from './logic' - -const Editor = ({ thread, isEdit, editData, mentionList }) => { - const { title, body } = editData - - return ( - - inputOnChange('copyRight')} - onLinkAddrChange={() => inputOnChange('linkAddr')} - onPreview={() => changeView('PREVIEW_VIEW')} - /> - inputOnChange('title')} - /> -
- TODO: - - - -
- ) -} - -export default React.memo(Editor) diff --git a/src/containers/editor/PostEditor/EditorFooter.js b/src/containers/editor/PostEditor/EditorFooter.js deleted file mode 100755 index 7e0e208ad..000000000 --- a/src/containers/editor/PostEditor/EditorFooter.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react' -import { pluck } from 'ramda' - -import { ICON_CMD } from '@/config' - -import DocUploader from '@/containers/tool/DocUploader' -import Maybe from '@/components/Maybe' - -import { - Wrapper, - Item, - ItemTitle, - ItemIcon, - Divider, -} from './styles/editor_footer' - -import { insertCode, onUploadImageDone } from './logic' - -const CodeInputer = ({ divider }) => ( - <> - - - - - - 代码 - - -) - -const PicUploader = ({ divider }) => ( - <> - - - - - - - 上传图片 - - - -) - -const EditorFooter = ({ isEdit, editData }) => ( - - - - - -) - -export default React.memo(EditorFooter) - -/* - - - 投票 - - - - 设置 - - */ diff --git a/src/containers/editor/PostEditor/Header.js b/src/containers/editor/PostEditor/Header.js deleted file mode 100755 index 673719327..000000000 --- a/src/containers/editor/PostEditor/Header.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react' -import { isEmpty } from 'ramda' - -import AvatarsRow from '@/components/AvatarsRow' -import { ICON_CMD } from '@/config' - -import { - Wrapper, - UsageText, - RefUsersWrapper, - AtSignIcon, - RefUserList, - MarkdownIcon, - MarkDownHint, - BackToEditHint, -} from './styles/header' - -import { changeView } from './logic' - -const DoingText = ({ isEdit }) => { - return isEdit ? <>更新 : <>发布 -} - -const Header = ({ isEdit, curView, referUsers }) => { - switch (curView) { - case 'MARKDOWN_HELP_VIEW': - return ( - - Github Flavor Markdown - changeView('CREATE_VIEW')}> - - 返回编辑 - - - ) - - default: - return ( - - - - 帖子 - {!isEmpty(referUsers) && ( - - - - - - - )} - - changeView('MARKDOWN_HELP_VIEW')}> - - markdown 语法速查 - - - ) - } -} - -export default React.memo(Header) diff --git a/src/containers/editor/PostEditor/MarkDownHelper.js b/src/containers/editor/PostEditor/MarkDownHelper.js deleted file mode 100755 index 13c5ee868..000000000 --- a/src/containers/editor/PostEditor/MarkDownHelper.js +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react' -import { Remarkable } from 'remarkable' -import remarkableemoj from 'remarkable-emoji' -import mentions from 'remarkable-mentions' -import Prism from 'mastani-codehighlight' - -import { MENTION_USER_ADDR } from '@/config' -import MarkDownStyle from '@/containers/layout/ThemePalette/MarkDownStyle' - -import { Wrapper } from './styles/markdown_helper' - -const md = new Remarkable() -md.use(mentions({ url: MENTION_USER_ADDR })) -md.use(remarkableemoj) - -const MarkDownInfo = () => { - const IntroMD = `\`说明\`: 显示效果与下方实现代码一一对应 -# 这是一级标题 -\`\`\`text -# 这是一级标题 -\`\`\` -## 这是二级标题 -\`\`\`text -## 这是二级标题 -\`\`\` - -### 这是三级标题 -\`\`\`text -### 这是三级标题 -\`\`\` - -#### 这是四级标题 -\`\`\`text -#### 这是四级标题 -\`\`\` - -##### 这是五级标题 -\`\`\`text -##### 这是五级标题 -\`\`\` - -###### 这是六级标题 -\`\`\`text -###### 这是六级标题 -\`\`\` - -编程语言代码高亮: - \`\`\`js -console.log('hello mastani') - \`\`\` - -\`\`\`text -'''js -console.log('hello mastani') -''' -注意这里是反斜杠(\`), 如果是其他编程语言将'js'替换即可。 -\`\`\` - -> 这是引用 -\`\`\`text -> 这是引用 -\`\`\` - -At 某个用户: @mydearxym - -\`\`\`text -At 某个用户: @mydearxym -\`\`\` - -插入链接: [coderplanets.com](https://coderplanets.com) - -\`\`\`text -插入链接: [coderplanets.com](https://coderplanets.com) -\`\`\` - -## Emoji 参考 -` - return ( -
- ) -} - -/* eslint-enable react/no-danger */ -class MarkDownHelper extends React.Component { - componentDidMount() { - Prism.highlightAll() - } - - render() { - return ( - - - - - - ) - } -} - -export default React.memo(MarkDownHelper) diff --git a/src/containers/editor/PostEditor/Preview.js b/src/containers/editor/PostEditor/Preview.js deleted file mode 100755 index db296507a..000000000 --- a/src/containers/editor/PostEditor/Preview.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Editor based on Draft - */ - -import React from 'react' - -import Button from '@/components/Buttons/Button' -import MarkDownRender from '@/components/MarkDownRender' -import { Wrapper, Header, BackToEditBtn, PreviewHeader } from './styles/preview' - -/* eslint-disable react/no-danger */ -const Preview = ({ onBack, editData: { title, body }, contentDomId }) => ( - -
- - - -
- {title} - - -
-) -/* eslint-enable react/no-danger */ - -export default React.memo(Preview) diff --git a/src/containers/editor/PostEditor/RadarNote.js b/src/containers/editor/PostEditor/RadarNote.js deleted file mode 100755 index 76178060c..000000000 --- a/src/containers/editor/PostEditor/RadarNote.js +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react' - -import { ISSUE_ADDR } from '@/config' - -import { cutRest } from '@/utils/helper' -import { - Wrapper, - Title, - IconsWrapper, - Site, - SiteInfo, - SiteTitle, - SiteLink, - SiteIcon, - Footer, -} from './styles/radar_note' - -const whiteList = [ - { - title: '湾区日报', - link: 'https://wanqu.co/', - logo: - 'https://cps-oss.oss-cn-shanghai.aliyuncs.com/icons/radar_source/wanqu.png', - }, - { - title: 'solidot', - link: 'https://www.solidot.org/', - logo: - 'https://cps-oss.oss-cn-shanghai.aliyuncs.com/icons/radar_source/solidot.png', - }, - { - title: 'techcrunch', - link: 'https://techcrunch.cn/', - logo: - 'https://cps-oss.oss-cn-shanghai.aliyuncs.com/icons/radar_source/techcrunch.png', - }, -] - -const RadarNote = () => ( - - 感谢参与。目前为保证质量,只支持如下站点源转载信息: - - {whiteList.map((item) => ( - - - - {item.title} - - {cutRest(item.link, 18)} - - - - ))} - -
-
如果你有更好的新闻源或意见,欢迎参与社区讨论:
- - {cutRest(`${ISSUE_ADDR}/339`, 100)} - -
-
-) - -export default React.memo(RadarNote) diff --git a/src/containers/editor/PostEditor/index.js b/src/containers/editor/PostEditor/index.js deleted file mode 100755 index 17bc3efe1..000000000 --- a/src/containers/editor/PostEditor/index.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - * - * PostEditor - * - */ - -import React from 'react' -/* import T from 'prop-types' */ -import dynamic from 'next/dynamic' - -import { buildLog } from '@/utils/logger' -import { pluggedIn } from '@/utils/mobx' - -import ArticleEditFooter from '@/components/ArticleEditFooter' -import { ArticleContentLoading } from '@/components/Loading' -import Modal from '@/components/Modal' - -import Editor from './Editor' -import Preview from './Preview' -import Header from './Header' -import RadarNote from './RadarNote' - -import { Wrapper, ViewerWrapper } from './styles' - -import { - useInit, - changeView, - onPublish, - cancelPublish, - onRadarNoteCLose, -} from './logic' - -export const MarkDownHelper = dynamic(() => import('./MarkDownHelper'), { - /* eslint-disable react/display-name */ - loading: () => , - ssr: false, -}) - -/* eslint-disable-next-line */ -const log = buildLog('C:PostEditor') - -// const View = ({ curView, thread, copyRight, title, body, linkAddr }) => { -const View = ({ - curView, - thread, - isEdit, - editData, - mentionList, - contentDomId, -}) => { - if (curView === 'CREATE_VIEW' || curView === 'PREVIEW_VIEW') { - return ( - - - - - - changeView('CREATE_VIEW')} - /> - - - ) - } - return -} - -const PostEditorContainer = ({ postEditor: store, attachment }) => { - useInit(store, attachment) - - const { - copyRight, - thread, - curView, - publishing, - isEdit, - editData, - mentionListData, - referUsersData, - contentDomId, - showRadarNote, - } = store - - log('editData in views: ', editData) - - return ( - - - - -
- - - - ) -} - -export default pluggedIn(PostEditorContainer) diff --git a/src/containers/editor/PostEditor/logic.js b/src/containers/editor/PostEditor/logic.js deleted file mode 100755 index 08c9e3de0..000000000 --- a/src/containers/editor/PostEditor/logic.js +++ /dev/null @@ -1,239 +0,0 @@ -import { map, contains, repeat, isEmpty, slice, trim } from 'ramda' -import { useEffect } from 'react' - -import { EVENT, ERR, THREAD } from '@/constant' -import { - asyncSuit, - buildLog, - send, - countWords, - extractAttachments, - extractMentions, - updateEditing, - closeDrawer, - cast, - parseDomain, - errRescue, - BStore, -} from '@/utils' - -import { S, updatablePostFields } from './schema' - -/* eslint-disable-next-line */ -const log = buildLog('L:PostEditor') - -const { SR71, $solver, asyncRes, asyncErr } = asyncSuit -const sr71$ = new SR71() - -let store = null -let sub$ = null -let saveDraftTimmer = null - -export const changeView = (curView) => store.mark({ curView }) - -const getDigest = (body) => { - /* eslint-disable no-undef */ - const digestContainer = document.getElementById(store.contentDomId) - - /* eslint-enable no-undef */ - const innerImagesLength = extractAttachments(body).length - let digest = slice(0, 65, trim(digestContainer.innerText)) - - if (innerImagesLength > 0 && innerImagesLength <= 2) { - const imgDigest = `${repeat('[图片]', innerImagesLength)}` - digest = isEmpty(digest) ? imgDigest : `${digest}..${imgDigest}` - } else if (innerImagesLength > 2) { - const imgDigest = `${repeat('[图片]', 2)} x ${innerImagesLength}` - digest = isEmpty(digest) ? imgDigest : `${digest}..${imgDigest}` - } - - return digest -} - -export const onRadarNoteCLose = () => store.mark({ showRadarNote: false }) - -export const onPublish = () => { - if (!store.validator('general')) return false - - const { body } = store.editData - const { isEdit } = store - publishing() - - const digest = getDigest(body) - const length = countWords(body) - - const variables = { - ...store.editData, - communityId: store.viewing.community.id, - digest, - length, - mentionUsers: map((user) => ({ id: user.id }), store.referUsersData), - } - if (!isEmpty(store.labelsData.tags)) { - variables.tags = store.labelsData.tags - } - - log('onPublish labelsData: ', store.labelsData.tags) - log('onPublish variables: ', variables) - - if (isEdit) { - const args = cast(updatablePostFields, variables) - return sr71$.mutate(S.updatePost, args) - } - sr71$.mutate(S.createPost, variables) -} - -export const cancelPublish = () => { - cancelLoading() - // store.reset() - closeDrawer() -} - -export const onUploadImageDone = (url) => - send(EVENT.DRAFT_INSERT_SNIPPET, { data: `![](${url})` }) - -export const insertCode = () => { - const communityRaw = store.curCommunity.raw - const data = `\`\`\`${communityRaw}\n\n\`\`\`` - - send(EVENT.DRAFT_INSERT_SNIPPET, { data }) -} - -export const onMentionSearch = (name) => { - if (name?.length >= 2) { - sr71$.query(S.searchUsers, { name }) - } else { - store.mark({ mentionList: [] }) - } -} - -export const onMention = (user) => store.addReferUser(user) - -const openAttachment = (att) => { - if (!att) return false - // const { type } = att - // if (type === TYPE.DRAWER.POST_EDIT) loadPost(att.id) - store.updateEditing(att) - store.mark({ isEdit: true }) -} - -const doneCleanUp = () => { - closeDrawer() - store.reset() - cancelLoading() -} - -export const inputOnChange = (part, e) => updateEditing(store, part, e) -export const bodyInputOnChange = (content) => { - // draft.js will mis trigger onChange event with empty string. - // currently this is a bug: in edit can't update to empty. - if (!store) return false - if (store.isEdit && content === '') return false - store.mark({ extractMentions: extractMentions(content) }) - - updateEditing(store, 'body', content) -} - -const saveDraftIfNeed = (content) => { - if (isEmpty(content)) return false - const curDraftContent = BStore.get('recentDraft') - - if (curDraftContent !== content) BStore.set('recentDraft', content) -} - -const clearDraft = () => BStore.set('recentDraft', '') - -const publishing = (maybe = true) => store.mark({ publishing: maybe }) -const cancelLoading = () => store.mark({ publishing: false }) - -// ############################### -// Data & Error handlers -// ############################### - -const DataSolver = [ - { - match: asyncRes('createPost'), - action: () => { - store.toast('success', { - title: '帖子已发布', - msg: 'have a good day :)', - position: 'topCenter', - }) - - doneCleanUp() - clearDraft() - send(EVENT.REFRESH_POSTS) - }, - }, - { - match: asyncRes('updatePost'), - action: () => { - store.toast('success', { - title: '已更新', - msg: '.', - position: 'topCenter', - }) - - doneCleanUp() - send(EVENT.REFRESH_POSTS) - }, - }, - { - match: asyncRes('searchUsers'), - action: ({ searchUsers: { entries } }) => store.updateMentionList(entries), - }, -] - -const ErrSolver = [ - { - match: asyncErr(ERR.GRAPHQL), - action: () => cancelLoading(), - }, - { - match: asyncErr(ERR.TIMEOUT), - action: ({ details }) => { - cancelLoading() - errRescue({ type: ERR.TIMEOUT, details, path: 'PostEditor' }) - }, - }, - { - match: asyncErr(ERR.NETWORK), - action: () => { - cancelLoading() - errRescue({ type: ERR.NETWORK, path: 'PostEditor' }) - }, - }, -] - -const initDraftTimmer = () => { - // only save draft in create mode - if (store.isEdit) return false - if (saveDraftTimmer) clearInterval(saveDraftTimmer) - - saveDraftTimmer = setInterval( - () => saveDraftIfNeed(store.editPost.body), - 3000, - ) -} - -// ############################### -// init & uninit -// ############################### -export const useInit = (_store, attachment) => { - useEffect(() => { - // log('effect init') - store = _store - sub$ = sr71$.data().subscribe($solver(DataSolver, ErrSolver)) - openAttachment(attachment) - initDraftTimmer() - - return () => { - // log('effect uninit') - if (saveDraftTimmer) clearInterval(saveDraftTimmer) - - store.mark({ editPost: {}, isEdit: false }) - sr71$.stop() - sub$.unsubscribe() - } - }, [_store, attachment]) -} diff --git a/src/containers/editor/PostEditor/schema.ts b/src/containers/editor/PostEditor/schema.ts deleted file mode 100755 index b3eb10a7b..000000000 --- a/src/containers/editor/PostEditor/schema.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { gql } from '@urql/core' -import { F } from '@/schemas' - -const createPost = gql` - mutation createPost( - $title: String! - $body: String! - $digest: String! - $length: Int! - $linkAddr: String - $copyRight: String - $communityId: ID! - $tags: [Ids] - $mentionUsers: [Ids] - ) { - createPost( - title: $title - body: $body - digest: $digest - length: $length - linkAddr: $linkAddr - copyRight: $copyRight - communityId: $communityId - tags: $tags - mentionUsers: $mentionUsers - ) { - id - title - body - } - } -` - -const updatePost = gql` - mutation( - $id: ID! - $title: String - $body: String - $digest: String - $copyRight: String - $linkAddr: String - $tags: [Ids] - ) { - updatePost( - id: $id - title: $title - body: $body - digest: $digest - copyRight: $copyRight - linkAddr: $linkAddr - tags: $tags - ) { - id - title - body - copyRight - } - } -` - -const post = gql` - query($id: ID!) { - post(id: $id) { - articleTags { - ${F.tag} - } - } - } -` - -const searchUsers = gql` - query($name: String!) { - searchUsers(name: $name) { - entries { - ${F.author} - } - } - } -` - -export const updatablePostFields = [ - 'id', - 'title', - 'digest', - 'body', - 'copyRight', - 'linkAddr', - 'tags', - // TODO: 'mentionUsers', -] - -export const S = { - createPost, - updatePost, - post, - searchUsers, -} diff --git a/src/containers/editor/PostEditor/store.js b/src/containers/editor/PostEditor/store.js deleted file mode 100755 index 686b646d0..000000000 --- a/src/containers/editor/PostEditor/store.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - * PostEditor store - * - */ - -import { types as T, getParent } from 'mobx-state-tree' -import { - merge, - map, - uniq, - concat, - clone, - findIndex, - filter, - contains, -} from 'ramda' - -import { markStates, toJS } from '@/utils/mobx' -import { changeset } from '@/utils/validator' -import { Post, Mention } from '@/model' - -const PostEditor = T.model('PostEditor', { - editPost: T.optional(Post, {}), - - mentionList: T.optional(T.array(Mention), []), - // current "@user" in valid array format - referUsers: T.optional(T.array(Mention), []), - // current "@user" in string list - extractMentions: T.optional(T.array(T.string), []), - - curView: T.optional( - T.enumeration('curView', [ - 'MARKDOWN_HELP_VIEW', - 'EDIT_VIEW', - 'CREATE_VIEW', - 'PREVIEW_VIEW', - ]), - 'CREATE_VIEW', - ), - contentDomId: T.optional(T.string, 'post_editor_content_id'), - - publishing: T.optional(T.boolean, false), - // TODO: rename to isEditMode - isEdit: T.optional(T.boolean, false), - /* show radar note if radar source not supported */ - showRadarNote: T.optional(T.boolean, false), -}) - .views((self) => ({ - get root() { - return getParent(self) - }, - get curRoute() { - return self.root.curRoute - }, - get viewing() { - return toJS(self.root.viewing) - }, - get editData() { - return toJS(self.editPost) - }, - get curCommunity() { - return toJS(self.root.viewing.community) - }, - get thread() { - return self.root.viewing.activeThread - }, - get activeThread() { - return self.root.viewing.activeThread - }, - get labelsData() { - return self.root.labeler.labelsData - }, - get mentionListData() { - return toJS(self.mentionList) - }, - get referUsersData() { - const referUsers = toJS(self.referUsers) - const extractMentions = toJS(self.extractMentions) - return filter((user) => contains(user.name, extractMentions), referUsers) - }, - })) - .actions((self) => ({ - toast(type, options) { - self.root.toast(type, options) - }, - changesetErr(options) { - self.root.changesetErr(options) - }, - validator(type) { - switch (type) { - case 'general': { - const result = changeset(self.editData) - .exist({ title: '文章标题或内容' }, self.changesetErr) - .exist({ body: '文章标题或内容' }, self.changesetErr) - // .exist({ linkAddr: '原链接地址' }, self.changesetErr) - .startsWith( - { linkAddr: '原链接地址' }, - 'https://', - self.changesetErr, - self.editData.copyRight !== 'original', - ) - .done() - - return result.passed - } - default: { - return false - } - } - }, - updateEditing(sobj) { - const editPost = merge(self.editData, { ...sobj }) - return self.mark({ editPost }) - }, - reset() { - self.mark({ isEdit: false, mentionList: [] }) - self.editPost = { title: '', body: '' } - }, - updateMentionList(mentionArray) { - const curMentionList = clone(self.mentionList) - const uniqList = uniq(concat(curMentionList, mentionArray)) - const mentionList = map((m) => ({ ...m, name: m.nickname }), uniqList) - self.mentionList = mentionList - }, - addReferUser(user) { - const index = findIndex((u) => u.id === String(user.id), self.referUsers) - if (index === -1) { - self.referUsers.push({ - id: String(user.id), - name: user.name, - avatar: user.avatar, - }) - } - }, - mark(sobj) { - markStates(sobj, self) - }, - })) - -export default PostEditor diff --git a/src/containers/editor/PostEditor/styles/editor.ts b/src/containers/editor/PostEditor/styles/editor.ts deleted file mode 100755 index 5ce820867..000000000 --- a/src/containers/editor/PostEditor/styles/editor.ts +++ /dev/null @@ -1,43 +0,0 @@ -import styled from 'styled-components' -import Input from '@/components/Input' - -import { theme } from '@/utils/themes' -import css from '@/utils/css' - -export const Wrapper = styled.div` - ${css.flexColumn()}; - - padding: 20px; - background-color: ${theme('editor.contentBg')}; - min-height: 400px; - margin-top: 5px; - margin-left: 4%; - margin-right: 4%; - border-radius: 5px; -` -export const TitleInput = styled(Input)` - border-color: ${theme('editor.border')}; - border-bottom: 1px solid; - border-bottom-color: ${theme('editor.borderNormal')}; - - text-align: center; - height: 45px; - font-size: 1.6em; - color: ${theme('editor.title')}; - background: ${theme('editor.headerBg')}; - align-self: center; - width: 85%; - &:hover { - border-bottom: 1px solid; - border-bottom-color: ${theme('editor.borderActive')}; - } - &:focus { - box-shadow: none; - border-bottom: 1px solid; - border-bottom-color: ${theme('editor.borderActive')}; - } -` -export const FooterWrapper = styled.div` - ${css.flex('justify-center')}; - margin-top: 25px; -` diff --git a/src/containers/editor/PostEditor/styles/editor_footer.ts b/src/containers/editor/PostEditor/styles/editor_footer.ts deleted file mode 100755 index 8af46f0fa..000000000 --- a/src/containers/editor/PostEditor/styles/editor_footer.ts +++ /dev/null @@ -1,43 +0,0 @@ -import styled from 'styled-components' - -import Img from '@/Img' -import { theme } from '@/utils/themes' -import css from '@/utils/css' -import animate from '@/utils/animations' -// -export const Wrapper = styled.div` - ${css.flex('align-both')}; - flex-wrap: wrap; -` -export const Item = styled.div` - ${css.flex()}; - color: ${theme('editor.footer')}; - &:hover { - color: #51abb2; - animation: ${animate.pulse} 0.4s linear; - } -` -export const Divider = styled(Img)` - fill: ${theme('editor.footer')}; - ${css.size(10)}; - margin-left: 4px; - margin-right: 4px; -` -export const ItemTitle = styled.div` - cursor: pointer; - font-size: 1rem; - ${Item}:hover & { - color: ${theme('editor.footerHover')}; - } -` -export const ItemIcon = styled(Img)` - fill: ${theme('editor.content')}; - width: 17px; - height: 17px; - margin-right: 3px; - margin-top: 2px; - - ${Item}:hover & { - fill: ${theme('editor.footerHover')}; - } -` diff --git a/src/containers/editor/PostEditor/styles/header.ts b/src/containers/editor/PostEditor/styles/header.ts deleted file mode 100755 index 66847008a..000000000 --- a/src/containers/editor/PostEditor/styles/header.ts +++ /dev/null @@ -1,57 +0,0 @@ -import styled from 'styled-components' - -import Img from '@/Img' -import { theme } from '@/utils/themes' -import css from '@/utils/css' - -export const Wrapper = styled.div` - ${css.flex()}; - margin-left: 35px; - margin-right: 35px; - padding-top: 15px; - margin-bottom: 10px; -` - -export const UsageText = styled.div` - ${css.flexGrow('align-center')}; - color: ${theme('editor.content')}; - font-size: 1.3em; -` - -export const AtSignIcon = styled(Img)` - fill: ${theme('editor.content')}; - ${css.size(15)}; - margin-left: 5px; - margin-right: 3px; -` - -export const RefUsersWrapper = styled.div` - ${css.flex('align-center')}; -` -export const RefUserList = styled.div` - margin-top: -10px; -` -export const MarkDownHint = styled.div` - ${css.flex()}; - color: ${theme('editor.placeholder')}; - &:hover { - color: ${theme('editor.content')}; - cursor: pointer; - } - transition: color 0.3s; -` -export const MarkdownIcon = styled(Img)` - fill: #51abb2; - width: 20px; - height: 18px; - margin-right: 5px; - - ${MarkDownHint}:hover & { - fill: #618c92; - } -` -export const BackToEditHint = styled.div` - ${css.flex()}; - color: ${theme('editor.title')}; - cursor: pointer; -` diff --git a/src/containers/editor/PostEditor/styles/index.ts b/src/containers/editor/PostEditor/styles/index.ts deleted file mode 100755 index a2ca921f2..000000000 --- a/src/containers/editor/PostEditor/styles/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import styled from 'styled-components' - -import type { TActive } from '@/spec' -import Input from '@/components/Input' -import { theme } from '@/utils/themes' -import css from '@/utils/css' - -export const Wrapper = styled.div`` -export const ViewerWrapper = styled.div` - display: ${({ active }) => (active ? 'block' : 'none')}; -` -export const Header = styled.div` - ${css.flex()}; - margin-left: 35px; - margin-right: 35px; - padding-top: 15px; - margin-bottom: 10px; -` - -export const SourceLink = styled.div` - ${css.flex('justify-center')}; - width: 60%; -` -export const LinkInput = styled(Input)` - border: 1px solid; - border-color: ${theme('editor.border')}; - height: 20px; - line-height: 20px; - width: 50%; - font-size: 0.9em; - margin-top: -1px; - background: ${theme('editor.headerBg')}; - padding-left: 2px; - color: ${theme('editor.title')}; - - &:hover { - color: ${theme('editor.title')}; - border-color: ${theme('editor.headerBg')}; - border-bottom: 1px solid; - border-bottom-color: ${theme('editor.border')}; - } - &:focus { - color: ${theme('editor.title')}; - border-color: ${theme('editor.headerBg')}; - box-shadow: none; - border-bottom: 1px solid; - border-bottom-color: ${theme('editor.placeholder')}; - } -` -export const LinkLabel = styled.div` - font-size: 0.9em; - color: ${theme('editor.placeholder')}; - ${SourceLink}:hover & { - color: ${theme('editor.title')}; - } - transition: color 0.3s; -` diff --git a/src/containers/editor/PostEditor/styles/markdown_helper.ts b/src/containers/editor/PostEditor/styles/markdown_helper.ts deleted file mode 100755 index c650f2750..000000000 --- a/src/containers/editor/PostEditor/styles/markdown_helper.ts +++ /dev/null @@ -1,11 +0,0 @@ -import styled from 'styled-components' - -import { theme } from '@/utils/themes' - -export const Wrapper = styled.div` - background: ${theme('drawer.markdownHelperBg')}; - padding: 20px; - margin-left: 4%; - margin-right: 4%; -` -export const holder = 1 diff --git a/src/containers/editor/PostEditor/styles/preview.ts b/src/containers/editor/PostEditor/styles/preview.ts deleted file mode 100755 index f0bbe93a9..000000000 --- a/src/containers/editor/PostEditor/styles/preview.ts +++ /dev/null @@ -1,26 +0,0 @@ -import styled from 'styled-components' - -// BodyWrapper, BodyHeader, BackToEditBtn, PreviewHeader -import { theme } from '@/utils/themes' -import css from '@/utils/css' - -export { Wrapper } from './editor' - -export const Header = styled.div` - ${css.flex('justify-end')}; - margin-bottom: 10px; -` -export const PreviewHeader = styled.div` - color: ${theme('drawer.title')}; - margin-bottom: 15px; - padding-bottom: 10px; - text-align: center; - font-size: 1.5em; - align-self: center; - border-bottom: 1px solid; - border-bottom-color: ${theme('drawer.divider')}; - width: 80%; - min-height: 1.5em; -` - -export const BackToEditBtn = styled.div`` diff --git a/src/containers/editor/PostEditor/styles/radar_note.ts b/src/containers/editor/PostEditor/styles/radar_note.ts deleted file mode 100755 index fa0541a18..000000000 --- a/src/containers/editor/PostEditor/styles/radar_note.ts +++ /dev/null @@ -1,48 +0,0 @@ -import styled from 'styled-components' - -import Img from '@/Img' -import { theme } from '@/utils/themes' -import css from '@/utils/css' - -export const Wrapper = styled.div` - ${css.flexColumn()}; - padding: 20px; - padding-top: 30px; -` -export const Title = styled.div` - font-size: 1rem; - color: ${theme('thread.articleTitle')}; -` -export const IconsWrapper = styled.div` - ${css.flex('justify-around')}; - margin-top: 20px; - background: ${theme('bodyBg')}; - padding: 20px 12px; -` -export const Site = styled.div` - ${css.flex('')}; -` -export const SiteIcon = styled(Img)` - ${css.size(40)}; - margin-right: 10px; -` - -export const SiteInfo = styled.div` - ${css.flexColumn()}; -` -export const SiteTitle = styled.div` - color: ${theme('thread.articleTitle')}; -` -export const SiteLink = styled.a` - text-decoration: underline; - color: ${theme('thread.articleDigest')}; - transition: color 0.3s; - &:hover { - text-decoration: underline; - color: ${theme('banner.title')}; - } -` -export const Footer = styled.div` - color: ${theme('thread.articleDigest')}; - margin-top: 80px; -` diff --git a/src/containers/editor/PostEditor/test_mentions.js b/src/containers/editor/PostEditor/test_mentions.js deleted file mode 100755 index 057a24b18..000000000 --- a/src/containers/editor/PostEditor/test_mentions.js +++ /dev/null @@ -1,37 +0,0 @@ -const mentions = [ - { - id: '0', - nickname: 'Matthew-Russell', - avatar: - 'https://pbs.twimg.com/profile_images/517863945/mattsailing_400x400.jpg', - }, - { - id: '1', - nickname: 'Julian-Krispel-Samsel', - avatar: 'https://avatars2.githubusercontent.com/u/1188186?v=3&s=400', - }, - { - id: '2', - nickname: 'Jyoti-Puri', - avatar: 'https://avatars0.githubusercontent.com/u/2182307?v=3&s=400', - }, - { - id: '3', - nickname: 'Max-Stoiber', - avatar: - 'https://pbs.twimg.com/profile_images/763033229993574400/6frGyDyA_400x400.jpg', - }, - { - id: '4', - nickname: 'Nik-Graf', - avatar: 'https://avatars0.githubusercontent.com/u/223045?v=3&s=400', - }, - { - id: '5', - nickname: 'Pascal-Brandt', - avatar: - 'https://pbs.twimg.com/profile_images/688487813025640448/E6O6I011_400x400.png', - }, -] - -export default mentions diff --git a/src/containers/editor/PostEditor/tests/store.test.ts b/src/containers/editor/PostEditor/tests/store.test.ts deleted file mode 100755 index 02234f743..000000000 --- a/src/containers/editor/PostEditor/tests/store.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * PostEditor store test - * - */ - -// import PostEditor from '../index' - -it('TODO: store test PostEditor', () => { - expect(1 + 1).toBe(2) -}) diff --git a/src/containers/layout/GlobalLayout/index.tsx b/src/containers/layout/GlobalLayout/index.tsx index 74c820430..d5cecbd64 100755 --- a/src/containers/layout/GlobalLayout/index.tsx +++ b/src/containers/layout/GlobalLayout/index.tsx @@ -26,7 +26,7 @@ import SEO from './SEO' import { CustomScroller, Sidebar, Footer, ModeLine } from './dynamic' import { Wrapper, InnerWrapper, BodyWrapper, ContentWrapper } from './styles' -import { onPageScrollDirhange, childrenWithProps } from './logic' +import { useInit, onPageScrollDirhange, childrenWithProps } from './logic' const Addon = dynamic(() => import('./Addon'), { ssr: false, @@ -52,7 +52,8 @@ const GlobalLayoutContainer: FC = ({ }) => { const { isMobile } = usePlatform() // load debug graph - // useInit(store, { isMobile }) + console.log('## G useInit') + useInit(store, { isMobile }) const { sidebarPin, c11n, curCommunity } = store const { bannerLayout } = c11n diff --git a/src/containers/layout/GlobalLayout/logic.ts b/src/containers/layout/GlobalLayout/logic.ts index 07f343783..1d91f4ddb 100755 --- a/src/containers/layout/GlobalLayout/logic.ts +++ b/src/containers/layout/GlobalLayout/logic.ts @@ -1,6 +1,8 @@ import React, { ReactNode, useEffect } from 'react' +import PubSub from 'pubsub-js' import type { TScrollDirection } from '@/spec' +import { EVENT } from '@/constant' import { buildLog } from '@/utils/logger' import type { TStore } from './store' @@ -67,6 +69,8 @@ export const childrenWithProps = ( }) } +const handleAuthWarning = (option): void => store.authWarning(option) + // ############################### // init & uninit // ############################### @@ -83,5 +87,12 @@ export const useInit = (_store: TStore, extra): void => { const { online, isMobile } = extra store.mark({ online, isMobile }) + + PubSub.unsubscribe(EVENT.AUTH_WARNING) + PubSub.subscribe(EVENT.AUTH_WARNING, (e, opt) => handleAuthWarning(opt)) + + return () => { + PubSub.unsubscribe(EVENT.AUTH_WARNING) + } }, [_store, extra]) } diff --git a/src/containers/layout/GlobalLayout/store.ts b/src/containers/layout/GlobalLayout/store.ts index 1e83ac8fd..86a9277c4 100755 --- a/src/containers/layout/GlobalLayout/store.ts +++ b/src/containers/layout/GlobalLayout/store.ts @@ -43,6 +43,10 @@ const GlobalLayout = T.model('GlobalLayoutStore', { }, })) .actions((self) => ({ + authWarning(options): void { + const root = getParent(self) as TRootStore + root.authWarning(options) + }, openDoraemon(): void { const root = getParent(self) as TRootStore root.openDoraemon() diff --git a/src/containers/tool/ArticleSticker/RightSticker/DefaultSticker.tsx b/src/containers/tool/ArticleSticker/RightSticker/DefaultSticker.tsx index 69b8b8463..8bcde7838 100644 --- a/src/containers/tool/ArticleSticker/RightSticker/DefaultSticker.tsx +++ b/src/containers/tool/ArticleSticker/RightSticker/DefaultSticker.tsx @@ -7,7 +7,7 @@ import IconButton from '@/components/Buttons/IconButton' import Upvote from '@/components/Upvote' import { Wrapper } from '../styles/right_sticker/default_sticker' -import { collectArticle } from '../logic' +import { collectArticle, handleUpvote } from '../logic' type TProps = { show: boolean @@ -17,7 +17,12 @@ type TProps = { const ArticleSticker: FC = ({ show, article }) => { return ( - + { }) } +export const handleUpvote = (viewerHasUpvoted: boolean): void => { + if (!store.isLogin) return authWarn({ hideToast: true }) + + store.updateUpvote(viewerHasUpvoted) + const { id, meta } = store.viewingArticle + + viewerHasUpvoted + ? sr71$.mutate(S.getUpvoteSchema(meta.thread), { id }) + : sr71$.mutate(S.getUndoUpvoteSchema(meta.thread), { id }) +} + export const loadPagedCommentsParticipants = (): void => { const { viewingArticle: article } = store @@ -39,10 +50,15 @@ export const loadPagedCommentsParticipants = (): void => { thread: article.meta.thread, filter: { page: 1, size: 20 }, } - console.log('query args: ', args) + log('load comments query args: ', args) sr71$.query(S.pagedCommentsParticipants, args) } +// update the real upvoteCount after upvote action +const handleUovoteAfter = ({ upvotesCount }) => { + store.updateUpvoteCount(upvotesCount) +} + const DataSolver = [ { match: asyncRes('pagedCommentsParticipants'), @@ -50,6 +66,14 @@ const DataSolver = [ store.mark({ pagedCommentsParticipants }) }, }, + { + match: asyncRes('upvotePost'), + action: ({ upvotePost }) => handleUovoteAfter(upvotePost), + }, + { + match: asyncRes('undoUpvotePost'), + action: ({ undoUpvotePost }) => handleUovoteAfter(undoUpvotePost), + }, ] const ErrSolver = [] diff --git a/src/containers/tool/ArticleSticker/schema.ts b/src/containers/tool/ArticleSticker/schema.ts index 9316e0bae..6f9ba22c5 100644 --- a/src/containers/tool/ArticleSticker/schema.ts +++ b/src/containers/tool/ArticleSticker/schema.ts @@ -1,20 +1,45 @@ import { gql } from '@urql/core' import { F } from '@/schemas' +import { titleCase } from '@/utils/helper' const pagedCommentsParticipants = gql` -query($id: ID!, $thread: Thread, $filter: PagedFilter!) { - pagedCommentsParticipants(id: $id, thread: $thread, filter: $filter) { - entries { - ${F.author} + query($id: ID!, $thread: Thread, $filter: PagedFilter!) { + pagedCommentsParticipants(id: $id, thread: $thread, filter: $filter) { + entries { + ${F.author} + } + ${F.pagedCounts} + } - ${F.pagedCounts} - } -} ` +const getUpvoteSchema = (thread) => { + return gql` + mutation ($id: ID!) { + upvote${titleCase(thread)}(id: $id) { + id + upvotesCount + } + } + ` +} + +const getUndoUpvoteSchema = (thread) => { + return gql` + mutation ($id: ID!) { + undoUpvote${titleCase(thread)}(id: $id) { + id + upvotesCount + } + } + ` +} + const schema = { pagedCommentsParticipants, + getUpvoteSchema, + getUndoUpvoteSchema, } export default schema diff --git a/src/containers/tool/ArticleSticker/store.ts b/src/containers/tool/ArticleSticker/store.ts index b100ccd61..a6da1bcee 100755 --- a/src/containers/tool/ArticleSticker/store.ts +++ b/src/containers/tool/ArticleSticker/store.ts @@ -23,9 +23,13 @@ const ArticleSticker = T.model('ArticleSticker', { isLeftStickerLocked: T.optional(T.boolean, false), }) .views((self) => ({ + get isLogin(): boolean { + const root = getParent(self) as TRootStore + return root.account.isLogin + }, get viewingArticle(): TArticle { const root = getParent(self) as TRootStore - return root.viewing.viewingArticle + return toJS(root.viewing.viewingArticle) }, get activeThread(): TThread { const root = getParent(self) as TRootStore @@ -73,6 +77,14 @@ const ArticleSticker = T.model('ArticleSticker', { }, })) .actions((self) => ({ + updateUpvote(viewerHasUpvoted: boolean): void { + const root = getParent(self) as TRootStore + return root.viewing.updateUpvote(viewerHasUpvoted) + }, + updateUpvoteCount(count: number): void { + const root = getParent(self) as TRootStore + return root.viewing.updateUpvoteCount(count) + }, mark(sobj: Record): void { markStates(sobj, self) }, diff --git a/src/containers/tool/CollectionFolder/logic.ts b/src/containers/tool/CollectionFolder/logic.ts index 4591c825c..8bc9893fa 100755 --- a/src/containers/tool/CollectionFolder/logic.ts +++ b/src/containers/tool/CollectionFolder/logic.ts @@ -205,7 +205,8 @@ const ErrSolver = [ { match: asyncErr(ERR.GRAPHQL), action: ({ details }) => { - store.changesetErr({ title: '已经存在了', msg: details[0].detail }) + console.log('collection folder TODO') + // store.changesetErr({ title: '已经存在了', msg: details[0].detail }) markLoading(false) }, }, diff --git a/src/containers/tool/Drawer/Content/renderContent.tsx b/src/containers/tool/Drawer/Content/renderContent.tsx index bb91cad66..8e34a91bc 100644 --- a/src/containers/tool/Drawer/Content/renderContent.tsx +++ b/src/containers/tool/Drawer/Content/renderContent.tsx @@ -9,8 +9,6 @@ import { MailsViewer, // editors AccountEditor, - PostEditor, - RepoEditor, // utils C11NSettingPanel, } from '../dynamics' @@ -22,14 +20,8 @@ const renderContent = (type: string, attUser: TUser, mmType?) => { case TYPE.DRAWER.ACCOUNT_EDIT: return - case TYPE.DRAWER.POST_CREATE: - return - - case TYPE.DRAWER.POST_EDIT: - return - - case TYPE.DRAWER.REPO_CREATE: - return + // case TYPE.DRAWER.REPO_CREATE: + // return case TYPE.DRAWER.MAILS_VIEW: return diff --git a/src/containers/tool/Drawer/dynamics.tsx b/src/containers/tool/Drawer/dynamics.tsx index ba94cdb0d..7dfbcd354 100755 --- a/src/containers/tool/Drawer/dynamics.tsx +++ b/src/containers/tool/Drawer/dynamics.tsx @@ -41,15 +41,10 @@ export const AccountEditor = dynamic( editorConfig, ) -export const PostEditor = dynamic( - () => import('@/containers/editor/PostEditor'), - editorConfig, -) - -export const RepoEditor = dynamic( - () => import('@/containers/editor/RepoEditor'), - editorConfig, -) +// export const RepoEditor = dynamic( +// () => import('@/containers/editor/RepoEditor'), +// editorConfig, +// ) // utils export const C11NSettingPanel = dynamic( diff --git a/src/containers/unit/ArticleFooter/AuthorInfo/index.tsx b/src/containers/unit/ArticleFooter/AuthorInfo/index.tsx index 7a80781ba..e6c7ceba3 100644 --- a/src/containers/unit/ArticleFooter/AuthorInfo/index.tsx +++ b/src/containers/unit/ArticleFooter/AuthorInfo/index.tsx @@ -40,6 +40,7 @@ const AuthorInfo: FC = ({ testid = 'author-info', author }) => { string > + const hasFollowed = false return ( @@ -55,7 +56,12 @@ const AuthorInfo: FC = ({ testid = 'author-info', author }) => { src={author.avatar} fallback={} /> - + ) diff --git a/src/containers/unit/ArticleFooter/index.tsx b/src/containers/unit/ArticleFooter/index.tsx index 8c5819e3a..de1bb95e4 100755 --- a/src/containers/unit/ArticleFooter/index.tsx +++ b/src/containers/unit/ArticleFooter/index.tsx @@ -9,7 +9,6 @@ import { FC, useState } from 'react' import type { TCopyright } from '@/spec' import { buildLog } from '@/utils/logger' -import { joinUS } from '@/utils/helper' import { pluggedIn } from '@/utils/mobx' import CommunityTagSetter from '@/containers/tool/CommunityTagSetter' @@ -50,7 +49,7 @@ const ArticleFooterContainer: FC = ({ const [copyright, setCopyright] = useState('cc') return ( - joinUS()}> + diff --git a/src/containers/unit/Comments/Comment/DesktopView/DefaultLayout.tsx b/src/containers/unit/Comments/Comment/DesktopView/DefaultLayout.tsx index 203ea4823..2fa8dfb90 100644 --- a/src/containers/unit/Comments/Comment/DesktopView/DefaultLayout.tsx +++ b/src/containers/unit/Comments/Comment/DesktopView/DefaultLayout.tsx @@ -28,7 +28,7 @@ import { BadgePopContent, IndentLine, } from '../../styles/comment/desktop_view' -import { foldComment } from '../../logic' +import { foldComment, handleUpvote } from '../../logic' const getSelection = () => { const selectText = Global.getSelection().toString() @@ -59,7 +59,12 @@ const DefaultLayout: FC = ({ data, tobeDeleteId, isReply = false }) => { - + handleUpvote(data, did)} + /> {isArticleAuthorUpvoted && ( 作者顶过} diff --git a/src/containers/unit/Comments/Comment/Footer.tsx b/src/containers/unit/Comments/Comment/Footer.tsx index 61a1b7a24..e6250f5df 100755 --- a/src/containers/unit/Comments/Comment/Footer.tsx +++ b/src/containers/unit/Comments/Comment/Footer.tsx @@ -1,6 +1,6 @@ import { FC, memo } from 'react' -import type { TAccount, TComment } from '@/spec' +import type { TComment } from '@/spec' import DotDivider from '@/components/DotDivider' import { SpaceGrow } from '@/components/Common' @@ -9,18 +9,26 @@ import EmotionSelector from '@/components/EmotionSelector' import Actions from './Actions' import { Wrapper } from '../styles/comment/footer' +import { handleEmotion } from '../logic' type TProps = { data: TComment } -const Footer: FC = ({ data }) => ( - - - - - - -) +const Footer: FC = ({ data }) => { + return ( + + + handleEmotion(data, name, hasEmotioned) + } + /> + + + + + ) +} export default memo(Footer) diff --git a/src/containers/unit/Comments/logic.ts b/src/containers/unit/Comments/logic.ts index 2a24bb088..f1ef97825 100755 --- a/src/containers/unit/Comments/logic.ts +++ b/src/containers/unit/Comments/logic.ts @@ -1,12 +1,19 @@ import { useEffect } from 'react' import { curry, isEmpty, reject, equals } from 'ramda' -import type { TUser, TID } from '@/spec' +import type { TUser, TComment, TID, TEmotionType } from '@/spec' import { EVENT, ERR } from '@/constant' import asyncSuit from '@/utils/async' import BStore from '@/utils/bstore' -import { send, countWords, extractMentions, errRescue } from '@/utils/helper' +import { + send, + countWords, + extractMentions, + errRescue, + authWarn, + titleCase, +} from '@/utils/helper' import { buildLog } from '@/utils/logger' import { scrollIntoEle } from '@/utils/dom' @@ -37,7 +44,7 @@ export const loadComments = (): void => { mode, filter: { page: 1, size: 20 }, } - console.log('query args: ', args) + log('query args: ', args) store.mark({ loading: true }) sr71$.query(S.pagedComments, args) } @@ -77,7 +84,7 @@ export const previewReply = (data): void => { } export const openInputBox = (): void => { - if (!store.isLogin) return store.authWarning({ hideToast: true }) + if (!store.isLogin) return authWarn({ hideToast: true }) initDraftTimmer() store.mark({ @@ -149,7 +156,7 @@ export const openUpdateEditor = (data): void => }) export const openReplyEditor = (data): void => { - if (!store.isLogin) return store.authWarning({}) + if (!store.isLogin) return authWarn({ hideToast: true }) initDraftTimmer() store.mark({ @@ -188,24 +195,67 @@ export const onModeChange = (mode: TMode): void => { } /** - * toggle like action - * - * @param {object} comment - * @param {comment.id} string - * @returns + * toggle emotion action */ -export const toggleLikeComment = (comment): void => { - if (!store.isLogin) return store.authWarning({}) - log('likeComment: ', comment) +export const handleEmotion = ( + comment: TComment, + name: TEmotionType, + viewerHasEmotioned: boolean, +): void => { + if (!store.isLogin) return authWarn({ hideToast: true }) + + const { id } = comment + // console.log('handleEmotion comment: ', id) + // console.log('handleEmotion name: ', name) + // console.log('handleEmotion viewerHasEmotioned: ', viewerHasEmotioned) + const emotion = name.toUpperCase() + + // comment.emotions + if (viewerHasEmotioned) { + // instantFresh + const emotionInfo = { + // @ts-ignore + [`${name}Count`]: comment.emotions[`${name}Count`] - 1, + [`viewerHas${titleCase(name)}ed`]: false, + } + store.upvoteEmotion(comment, emotionInfo) + sr71$.mutate(S.undoEmotionToComment, { id, emotion }) + } else { + const emotionInfo = { + // @ts-ignore + [`${name}Count`]: comment.emotions[`${name}Count`] + 1, + [`viewerHas${titleCase(name)}ed`]: true, + } + store.upvoteEmotion(comment, emotionInfo) + // instantFresh + sr71$.mutate(S.emotionToComment, { id, emotion }) + } +} + +/** + * toggle upvote action + */ +export const handleUpvote = ( + comment: TComment, + viewerHasUpvoted: boolean, +): void => { + if (!store.isLogin) return authWarn({ hideToast: true }) + const { id, upvotesCount } = comment - if (comment.viewerHasLiked) { - return sr71$.mutate(S.undoLikeComment, { - id: comment.id, + if (viewerHasUpvoted) { + store.updateUpvote(comment, { + upvotesCount: upvotesCount + 1, + viewerHasUpvoted: !viewerHasUpvoted, + }) + sr71$.mutate(S.upvoteComment, { id }) + } else { + store.updateUpvote(comment, { + upvotesCount: upvotesCount - 1, + viewerHasUpvoted: !viewerHasUpvoted, }) + + sr71$.mutate(S.undoUpvoteComment, { id }) } - return sr71$.mutate(S.likeComment, { - id: comment.id, - }) } export const onUploadImageDone = (url: string): void => @@ -288,6 +338,7 @@ const DataSolver = [ match: asyncRes('pagedComments'), action: ({ pagedComments }) => { cancelLoading() + log('# pagedComments --> ', pagedComments) store.mark({ pagedComments, loading: false }) }, }, @@ -336,24 +387,32 @@ const DataSolver = [ }, }, { - match: asyncRes('likeComment'), - action: ({ likeComment }) => - store.updateOneComment(likeComment.id, likeComment), + match: asyncRes('upvoteComment'), + action: ({ upvoteComment }) => { + const { upvotesCount, viewerHasUpvoted } = upvoteComment + store.updateUpvote(upvoteComment, { upvotesCount, viewerHasUpvoted }) + }, }, { - match: asyncRes('undoLikeComment'), - action: ({ undoLikeComment }) => - store.updateOneComment(undoLikeComment.id, undoLikeComment), + match: asyncRes('undoUpvoteComment'), + action: ({ undoUpvoteComment }) => { + const { upvotesCount, viewerHasUpvoted } = undoUpvoteComment + store.updateUpvote(undoUpvoteComment, { upvotesCount, viewerHasUpvoted }) + }, }, { - match: asyncRes('dislikeComment'), - action: ({ dislikeComment }) => - store.updateOneComment(dislikeComment.id, dislikeComment), + match: asyncRes('emotionToComment'), + action: ({ emotionToComment }) => { + console.log('emotionToComment -> ', emotionToComment) + store.upvoteEmotion(emotionToComment, emotionToComment.emotions) + }, }, { - match: asyncRes('undoDislikeComment'), - action: ({ undoDislikeComment }) => - store.updateOneComment(undoDislikeComment.id, undoDislikeComment), + match: asyncRes('undoEmotionToComment'), + action: ({ undoEmotionToComment }) => { + console.log('undoEmotionToComment -> ', undoEmotionToComment) + store.upvoteEmotion(undoEmotionToComment, undoEmotionToComment.emotions) + }, }, { match: asyncRes('deleteComment'), diff --git a/src/containers/unit/Comments/schema.ts b/src/containers/unit/Comments/schema.ts index da33a6ac8..250e9d226 100755 --- a/src/containers/unit/Comments/schema.ts +++ b/src/containers/unit/Comments/schema.ts @@ -41,7 +41,7 @@ const createComment = gql` ` const updateComment = gql` - mutation($thread: CmsThread!, $id: ID!, $body: String!) { + mutation ($thread: CmsThread!, $id: ID!, $body: String!) { updateComment(thread: $thread, id: $id, body: $body) { id body @@ -70,49 +70,56 @@ const replyComment = gql` } ` const deleteComment = gql` - mutation($thread: CmsThread, $id: ID!) { + mutation ($thread: CmsThread, $id: ID!) { deleteComment(thread: $thread, id: $id) { id } } ` -const likeComment = gql` - mutation($thread: CmsThread, $id: ID!) { - likeComment(thread: $thread, id: $id) { +const upvoteComment = gql` + mutation ($id: ID!) { + upvoteComment(id: $id) { id - viewerHasLiked - likesCount + upvotesCount + viewerHasUpvoted + replyToId } } ` -const undoLikeComment = gql` - mutation($thread: CmsThread, $id: ID!) { - undoLikeComment(thread: $thread, id: $id) { +const undoUpvoteComment = gql` + mutation ($id: ID!) { + undoUpvoteComment(id: $id) { id - viewerHasLiked - likesCount + upvotesCount + viewerHasUpvoted + replyToId } } ` -const dislikeComment = gql` - mutation($thread: CmsThread, $id: ID!) { - dislikeComment(thread: $thread, id: $id) { +const emotionToComment = gql` + mutation ($id: ID!, $emotion: CommentEmotion!) { + emotionToComment(id: $id, emotion: $emotion) { id - viewerHasDisliked - dislikesCount + replyToId + emotions { + ${F.emotionQuery} + } } } ` -const undoDislikeComment = gql` - mutation($thread: CmsThread, $id: ID!) { - undoDislikeComment(thread: $thread, id: $id) { +const undoEmotionToComment = gql` + mutation ($id: ID!, $emotion: CommentEmotion!) { + undoEmotionToComment(id: $id, emotion: $emotion) { id - viewerHasDisliked - dislikesCount + replyToId + emotions { + ${F.emotionQuery} + } } } ` + const searchUsers = gql` query($name: String!) { searchUsers(name: $name) { @@ -129,11 +136,11 @@ const schema = { updateComment, replyComment, deleteComment, - likeComment, - undoLikeComment, - dislikeComment, - undoDislikeComment, searchUsers, + upvoteComment, + undoUpvoteComment, + emotionToComment, + undoEmotionToComment, } export default schema diff --git a/src/containers/unit/Comments/store.ts b/src/containers/unit/Comments/store.ts index 0f9193055..0ded0ca76 100755 --- a/src/containers/unit/Comments/store.ts +++ b/src/containers/unit/Comments/store.ts @@ -27,8 +27,10 @@ import type { TRoute, TID, TPagedComments, + TComment, + TEmotion, } from '@/spec' -import { TYPE } from '@/constant' +// import { TYPE } from '@/constant' import { markStates, toJS } from '@/utils/mobx' import { changeset } from '@/utils/validator' import { Comment, PagedComments, emptyPagi, Mention } from '@/model' @@ -164,10 +166,6 @@ const CommentsStore = T.model('CommentsStore', { }, })) .actions((self) => ({ - authWarning(options): void { - const root = getParent(self) as TRootStore - root.authWarning(options) - }, changesetErr(options): void { const root = getParent(self) as TRootStore root.changesetErr(options) @@ -225,6 +223,57 @@ const CommentsStore = T.model('CommentsStore', { // @ts-ignore self.pagedComments.entries[index] = merge(entries[index], comment) }, + updateUpvote(comment: TComment, info): void { + const { id, replyToId } = comment + const slf = self as TStore + const { entries } = slf.pagedCommentsData + + if (self.mode === MODE.REPLIES && replyToId) { + const parentIndex = findIndex(propEq('id', replyToId), entries) + if (parentIndex < 0) return + const parentComment = entries[parentIndex] + const replyIndex = findIndex(propEq('id', id), parentComment.replies) + if (replyIndex < 0) return + const replyComment = parentComment.replies[replyIndex] + self.pagedComments.entries[parentIndex].replies[replyIndex] = { + ...replyComment, + ...info, + } + } else { + // timeline & replies parent comment + const index = findIndex(propEq('id', id), entries) + + if (index < 0) return + // @ts-ignore + self.pagedComments.entries[index] = { ...entries[index], ...info } + } + }, + upvoteEmotion(comment: TComment, emotion: TEmotion): void { + const { id, replyToId } = comment + const slf = self as TStore + const { entries } = slf.pagedCommentsData + + if (self.mode === MODE.REPLIES && replyToId) { + const parentIndex = findIndex(propEq('id', replyToId), entries) + if (parentIndex < 0) return + const parentComment = entries[parentIndex] + const replyIndex = findIndex(propEq('id', id), parentComment.replies) + if (replyIndex < 0) return + const replyComment = parentComment.replies[replyIndex] + self.pagedComments.entries[parentIndex].replies[replyIndex].emotions = { + ...replyComment.emotions, + ...emotion, + } + } else { + const index = findIndex(propEq('id', id), entries) + if (index < 0) return + // @ts-ignore + self.pagedComments.entries[index].emotions = { + ...entries[index].emotions, + ...emotion, + } + } + }, mark(sobj: Record): void { markStates(sobj, self) }, diff --git a/src/containers/unit/Comments/styles/comment/desktop_view/index.ts b/src/containers/unit/Comments/styles/comment/desktop_view/index.ts index 47dbba42d..629abc82b 100644 --- a/src/containers/unit/Comments/styles/comment/desktop_view/index.ts +++ b/src/containers/unit/Comments/styles/comment/desktop_view/index.ts @@ -13,8 +13,7 @@ type TWrapper = { export const Wrapper = styled.div` position: relative; ${css.flex()}; - margin-left: 2px; - margin-right: 5px; + margin-left: 1px; padding-top: ${({ isPinned }) => (isPinned ? '24px' : '20px')}; position: relative; background: transparent; diff --git a/src/containers/unit/Footer/DesktopView/ArticleLayout.tsx b/src/containers/unit/Footer/DesktopView/ArticleLayout.tsx index 1355111ab..b7bf91b7b 100755 --- a/src/containers/unit/Footer/DesktopView/ArticleLayout.tsx +++ b/src/containers/unit/Footer/DesktopView/ArticleLayout.tsx @@ -1,8 +1,9 @@ import { FC, memo } from 'react' import type { TArticle, TC11NLayout, TMetric } from '@/spec' -import { ISSUE_ADDR, API_SERVER_ADDR } from '@/config' +import { ISSUE_ADDR, GITHUB, API_SERVER_ADDR } from '@/config' import { METRIC } from '@/constant' +import { joinUS } from '@/utils/helper' import TopInfo from './TopInfo' import BottomInfo from './BottomInfo' @@ -13,6 +14,7 @@ import { MainInfos, BaseInfo, Item, + NoLinkItem, } from '../styles/desktop_view/article_layout' type TProps = { @@ -38,19 +40,9 @@ const BriefView: FC = ({ metric, article, layout }) => { > 创建社区 - - 加入我们 - - - OpenSource + joinUS()}>加入群聊 + + Github = ({ metric, layout }) => { > 创建社区 - - 加入我们 - - - OpenSource + joinUS()}>加入群聊 + + Github { @@ -31,19 +33,9 @@ const CoolGuideLayout: FC = () => { > 创建社区 - - 加入我们 - - - OpenSource + joinUS()}>加入群聊 + + Github = ({ viewingArticle }) => { > 创建社区 - - 加入我们 - - - OpenSource + joinUS()}>加入群聊 + + Github ` ${css.flexColumn('align-end')}; diff --git a/src/containers/unit/Footer/styles/desktop_view/cool_guide_layout.ts b/src/containers/unit/Footer/styles/desktop_view/cool_guide_layout.ts index 05500b132..09225aead 100755 --- a/src/containers/unit/Footer/styles/desktop_view/cool_guide_layout.ts +++ b/src/containers/unit/Footer/styles/desktop_view/cool_guide_layout.ts @@ -4,6 +4,8 @@ import { METRIC } from '@/constant' import { theme } from '@/utils/themes' import css from '@/utils/css' +export { NoLinkItem } from './article_layout' + export const Wrapper = styled.div` ${css.flexColumn('align-start')}; width: 100%; diff --git a/src/containers/unit/Footer/styles/desktop_view/works_article_layout.ts b/src/containers/unit/Footer/styles/desktop_view/works_article_layout.ts index e7911b556..6d9c412c2 100755 --- a/src/containers/unit/Footer/styles/desktop_view/works_article_layout.ts +++ b/src/containers/unit/Footer/styles/desktop_view/works_article_layout.ts @@ -4,6 +4,8 @@ import { METRIC } from '@/constant' import { theme } from '@/utils/themes' import css from '@/utils/css' +export { NoLinkItem } from './article_layout' + export const Wrapper = styled.div` ${css.flexColumn('align-end')}; width: 100%; diff --git a/src/containers/user/UserLister/UserList.js b/src/containers/user/UserLister/UserList.js index e8c642693..85d958f4c 100755 --- a/src/containers/user/UserLister/UserList.js +++ b/src/containers/user/UserLister/UserList.js @@ -51,7 +51,6 @@ const UsersTable = ({ entries, accountId }) => ( userId={user.id} onFollow={onFollow} onUndoFollow={undoFollow} - fakeLoading /> ) : (
(本尊)
diff --git a/src/pages/community.tsx b/src/pages/community.tsx index b5a8bf834..7347e86f9 100755 --- a/src/pages/community.tsx +++ b/src/pages/community.tsx @@ -28,7 +28,6 @@ const fetchData = async (context, opt = {}) => { const { gqClient, userHasLogin } = ssrFetchPrepare(context, opt) const { community, thread } = ssrParseURL(context.req) - console.log('## parsed thread: ', thread) // query data const sessionState = gqClient.request(P.sessionState) const curCommunity = gqClient.request(P.community, { diff --git a/src/schemas/fragments/base.ts b/src/schemas/fragments/base.ts index 0eb949235..89c9ebb42 100755 --- a/src/schemas/fragments/base.ts +++ b/src/schemas/fragments/base.ts @@ -180,7 +180,7 @@ export const userContributes = ` totalCount ` -const emotionQuery = flatten( +export const emotionQuery = flatten( values(EMOTION).map((emotion) => { return [ `${emotion}Count`, @@ -213,6 +213,7 @@ const commentFields = ` isArticleAuthor viewerHasUpvoted repliesCount + replyToId insertedAt updatedAt ` diff --git a/src/schemas/fragments/index.ts b/src/schemas/fragments/index.ts index 15ccaa899..47f29d7e9 100755 --- a/src/schemas/fragments/index.ts +++ b/src/schemas/fragments/index.ts @@ -22,6 +22,7 @@ import { userBackgrounds, userContributes, comment, + emotionQuery, commentParent, pagedCounts, } from './base' @@ -47,6 +48,7 @@ const F = { userContributes, comment, + emotionQuery, commentParent, pagedCounts, } diff --git a/src/spec/article.ts b/src/spec/article.ts index 5a881b857..c97d76c0d 100644 --- a/src/spec/article.ts +++ b/src/spec/article.ts @@ -38,6 +38,7 @@ type TBaseArticle = { insertedAt?: string updatedAt?: string viewerHasViewed?: boolean + viewerHasUpvoted?: boolean commentsCount?: number articleTags?: TTag[] meta?: TArticleMeta @@ -121,6 +122,7 @@ export type TComment = { repliesCount?: number replies?: TComment[] replyTo?: TComment + replyToId?: TID upvotesCount?: number viewerHasUpvoted?: boolean isArticleAuthor?: boolean diff --git a/src/spec/index.ts b/src/spec/index.ts index 335a54680..91bc721ed 100644 --- a/src/spec/index.ts +++ b/src/spec/index.ts @@ -96,7 +96,7 @@ export type { export type { TAccountStore, TViewingStore } from './store' -export type { TEmotion } from './emotion' +export type { TEmotion, TEmotionType } from './emotion' export type TRoute = { communityPath?: string diff --git a/src/stores/Model/Comment.ts b/src/stores/Model/Comment.ts index 02a63ecc5..a82380505 100755 --- a/src/stores/Model/Comment.ts +++ b/src/stores/Model/Comment.ts @@ -4,7 +4,7 @@ import { values, reduce, merge } from 'ramda' import { EMOTION } from '@/constant' import { titleCase } from '@/utils/helper' -import { User, SimpleUser } from './User' +import { SimpleUser } from './User' import { pagiFields, timestampFields } from './helper/common' @@ -46,6 +46,7 @@ const commentBaseFields = () => { meta: T.optional(CommentMeta, {}), repliesCount: T.optional(T.number, 0), + replyToId: T.maybeNull(T.string), viewerHasUpvoted: T.maybeNull(T.boolean), ...timestampFields(), diff --git a/src/stores/Model/User.ts b/src/stores/Model/User.ts index 797ca87e1..f5c52f79b 100755 --- a/src/stores/Model/User.ts +++ b/src/stores/Model/User.ts @@ -109,7 +109,7 @@ export const User = T.model('User', { workBackgrounds: T.optional(T.array(WorkBackground), []), sex: T.maybeNull(T.string), // social - social: T.optional(UserSocial, {}), + // social: T.optional(UserSocial, {}), fromGithub: T.optional(T.boolean, false), /* fromWeixin: T.optional(T.boolean, false), */ /* subscribedCommunities: T.optional(pagedCommunities, {}), */ diff --git a/src/stores/RootStore/index.ts b/src/stores/RootStore/index.ts index e9885c3db..a39db729b 100755 --- a/src/stores/RootStore/index.ts +++ b/src/stores/RootStore/index.ts @@ -44,7 +44,7 @@ import { // footer FooterStore, // viewers - RepoViewerStore, + // RepoViewerStore, CommentsStore, MailsViewerStore, @@ -52,8 +52,7 @@ import { DoraemonStore, DrawerStore, SidebarStore, - PostEditorStore, - RepoEditorStore, + // RepoEditorStore, AccountEditorStore, MailBoxStore, DocUploaderStore, @@ -121,8 +120,7 @@ const rootStore = T.model({ sidebar: T.optional(SidebarStore, { menuItems: [] }), drawer: T.optional(DrawerStore, { visible: false }), doraemon: T.optional(DoraemonStore, {}), - postEditor: T.optional(PostEditorStore, {}), - repoEditor: T.optional(RepoEditorStore, {}), + // repoEditor: T.optional(RepoEditorStore, {}), accountEditor: T.optional(AccountEditorStore, {}), mailBox: T.optional(MailBoxStore, {}), docUploader: T.optional(DocUploaderStore, {}), @@ -162,7 +160,6 @@ const rootStore = T.model({ cashier: T.optional(CashierStore, {}), // viewers (for drawer usage) - repoViewer: T.optional(RepoViewerStore, {}), mailsViewer: T.optional(MailsViewerStore, {}), // user page diff --git a/src/stores/RootStore/index2.ts b/src/stores/RootStore/index2.ts index 886491a6c..1c224bd3e 100755 --- a/src/stores/RootStore/index2.ts +++ b/src/stores/RootStore/index2.ts @@ -45,7 +45,6 @@ import { // footer FooterStore, // viewers - RepoViewerStore, CommentsStore, MailsViewerStore, @@ -53,8 +52,6 @@ import { DoraemonStore, DrawerStore, SidebarStore, - PostEditorStore, - RepoEditorStore, AccountEditorStore, MailBoxStore, DocUploaderStore, @@ -144,7 +141,6 @@ import { // // toolbox // import DocUploaderStore from '@/containers/tool/DocUploader/store' -// import PostEditorStore from '@/containers/editor/PostEditor/store' // import RepoEditorStore from '@/containers/editor/RepoEditor/store' // import CommentsStore from '@/containers/unit/Comments/store' // import AccountEditorStore from '@/containers/editor/AccountEditor/store' @@ -210,8 +206,6 @@ const rootStore = T.model({ drawer: T.optional(DrawerStore, { visible: false }), // gzip + 16kb // NOTE: 危险 doraemon: T.optional(DoraemonStore, {}), - postEditor: T.optional(PostEditorStore, {}), - repoEditor: T.optional(RepoEditorStore, {}), accountEditor: T.optional(AccountEditorStore, {}), mailBox: T.optional(MailBoxStore, {}), docUploader: T.optional(DocUploaderStore, {}), @@ -234,7 +228,6 @@ const rootStore = T.model({ girlVerifier: T.optional(GirlVerifierStore, {}), cashier: T.optional(CashierStore, {}), // viewers (for drawer usage) - repoViewer: T.optional(RepoViewerStore, {}), mailsViewer: T.optional(MailsViewerStore, {}), // user page userPublished: T.optional(UserPublishedStore, {}), diff --git a/src/stores/ViewingStore/index.ts b/src/stores/ViewingStore/index.ts index fd7f967e3..4faddb43f 100755 --- a/src/stores/ViewingStore/index.ts +++ b/src/stores/ViewingStore/index.ts @@ -66,6 +66,23 @@ const ViewingStore = T.model('ViewingStore', { const { mark, viewingThread } = self as TStore mark({ [viewingThread]: {}, viewingThread: null }) }, + updateUpvote(viewerHasUpvoted: boolean): void { + const { currentThread } = self as TStore + + if (viewerHasUpvoted) { + self[currentThread].upvotesCount += 1 + self[currentThread].viewerHasUpvoted = true + } else { + self[currentThread].upvotesCount -= 1 + self[currentThread].viewerHasUpvoted = false + } + }, + updateUpvoteCount(count: number): void { + const { currentThread } = self as TStore + if (self[currentThread].upvotesCount !== count) { + self[currentThread].upvotesCount = count + } + }, updateViewingIfNeed(type, sobj): void { const { updateViewingUser } = self as TStore diff --git a/src/stores/index.ts b/src/stores/index.ts index 886d6c65f..f2c3e0652 100755 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -55,8 +55,7 @@ export { default as TagsBarStore } from '@/containers/unit/TagsBar/store' // toolbox export { default as DocUploaderStore } from '@/containers/tool/DocUploader/store' -export { default as PostEditorStore } from '@/containers/editor/PostEditor/store' -export { default as RepoEditorStore } from '@/containers/editor/RepoEditor/store' +// export { default as RepoEditorStore } from '@/containers/editor/RepoEditor/store' export { default as CommentsStore } from '@/containers/unit/Comments/store' export { default as AccountEditorStore } from '@/containers/editor/AccountEditor/store' diff --git a/utils/async/methods.js b/utils/async/methods.js index 05d28598e..b6ee56093 100755 --- a/utils/async/methods.js +++ b/utils/async/methods.js @@ -23,13 +23,14 @@ const doQuery = (query, variables) => { const doMutate = (mutation, variables) => { return gqClient - .mutate(mutation, variables) + .mutation(mutation, variables) .toPromise() .then((res) => { + if (res.error) return formatGraphErrors(res.error) + return res.data // once login user has mutation to server // then clear all the cache store in Apollo client. // client.clearStore() - return res.data }) .catch(formatGraphErrors) } diff --git a/utils/constant/emotion.ts b/utils/constant/emotion.ts index 0a22a3043..7c4be1d8c 100644 --- a/utils/constant/emotion.ts +++ b/utils/constant/emotion.ts @@ -1,12 +1,12 @@ -// import type { TMetric } from '@/spec' +import type { TEmotionType } from '@/spec' const EMOTION = { - DOWNVOTE: 'downvote', - BEER: 'beer', - HEART: 'heart', - CONFUSED: 'confused', - POPCORN: 'popcorn', - PILL: 'pill', + DOWNVOTE: 'downvote' as TEmotionType, + BEER: 'beer' as TEmotionType, + HEART: 'heart' as TEmotionType, + CONFUSED: 'confused' as TEmotionType, + POPCORN: 'popcorn' as TEmotionType, + PILL: 'pill' as TEmotionType, } export default EMOTION diff --git a/utils/constant/event.ts b/utils/constant/event.ts index cdc68b6c9..aea4f7cd7 100755 --- a/utils/constant/event.ts +++ b/utils/constant/event.ts @@ -6,6 +6,7 @@ const EVENT = { // error ERR_RESCUE: 'ERR_RESCUE', + AUTH_WARNING: 'AUTH_WARNING', LOGOUT: 'LOGOUT', // drawer DRAWER: { diff --git a/utils/constant/svg.ts b/utils/constant/svg.ts index e8083f804..9f81cf4aa 100755 --- a/utils/constant/svg.ts +++ b/utils/constant/svg.ts @@ -19,6 +19,7 @@ const SVG = { // utils MORE: 'more', MOREL: 'more-l', + JOIN_EYE: 'JoinEye', } export default SVG diff --git a/utils/helper.ts b/utils/helper.ts index 0e71c7b5c..9a1b64d61 100755 --- a/utils/helper.ts +++ b/utils/helper.ts @@ -58,10 +58,12 @@ export const sortByIndex = (source: TSORTABLE_ITEMS): TSORTABLE_ITEMS => sort((a, b) => a.index - b.index, source) /* eslint-disable */ -const log = (...args) => (data) => { - console.log.apply(null, args.concat([data])) - return data -} +const log = + (...args) => + (data) => { + console.log.apply(null, args.concat([data])) + return data + } /* eslint-enable */ // reference: https://blog.carbonfive.com/2017/12/20/easy-pipeline-debugging-with-curried-console-log/ @@ -190,6 +192,8 @@ export const setTag = (): void => { send(EVENT.SET_TAG, {}) } +export const authWarn = (option): void => send(EVENT.AUTH_WARNING, option) + /** * send preview article singal to Drawer */ diff --git a/utils/ssr.js b/utils/ssr.js index f2af377e2..a17f9eaec 100755 --- a/utils/ssr.js +++ b/utils/ssr.js @@ -42,6 +42,8 @@ export const ssrBaseStates = (resp) => { export const ssrFetchPrepare = (context, opt) => { const token = ssrFetchToken(context, opt) + console.log('# fetched token: ', token) + const gqClient = makeGQClient(token) const userHasLogin = !!token @@ -176,5 +178,7 @@ export const validCommunityFilters = [ 'read', ] -export const parseTheme = (sessionState) => - sessionState.user ? sessionState.user.customization.theme : DEFAULT_THEME +export const parseTheme = (sessionState) => { + // return sessionState.user ? sessionState.user.customization.theme : DEFAULT_THEME + return DEFAULT_THEME +}