From 0bd060d21d0dd4eedc3ef827fe54f00c864f83cf Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 2 Nov 2021 12:57:23 +0800 Subject: [PATCH 01/11] refactor(job-workflow): editor for job publish re-org --- server/routes.js | 5 ++ .../editor/ArticleEditor/Footer.tsx | 25 ++++--- .../ArticleEditor/PublishRules/JobRules.tsx | 31 ++++++++ .../PostRules.tsx} | 7 +- .../ArticleEditor/PublishRules/index.tsx | 25 +++++++ .../editor/ArticleEditor/TitleInput/index.tsx | 5 +- src/containers/editor/ArticleEditor/index.tsx | 15 ++-- src/containers/editor/ArticleEditor/logic.ts | 7 +- src/containers/editor/ArticleEditor/schema.ts | 28 ++++++++ src/containers/editor/ArticleEditor/spec.ts | 6 ++ src/containers/editor/ArticleEditor/store.ts | 29 +++++++- src/pages/publish/job.tsx | 70 +++++++++++++++++++ src/pages/update/post/[id].tsx | 2 +- 13 files changed, 228 insertions(+), 27 deletions(-) create mode 100644 src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx rename src/containers/editor/ArticleEditor/{PublishRules.tsx => PublishRules/PostRules.tsx} (86%) create mode 100644 src/containers/editor/ArticleEditor/PublishRules/index.tsx create mode 100644 src/containers/editor/ArticleEditor/spec.ts create mode 100755 src/pages/publish/job.tsx diff --git a/server/routes.js b/server/routes.js index 21e309008..27880937f 100644 --- a/server/routes.js +++ b/server/routes.js @@ -115,6 +115,11 @@ router.route('/update/post/:id').get((req, res) => { return renderAndCache({ req, res, path: `/update/post/${id}` }) }) +// 创建新帖子 +router.route('/publish/job').get((req, res) => { + return renderAndCache({ req, res, page: '/publish/job' }) +}) + // 创建新博客 router.route('/publish/blog').get((req, res) => { return renderAndCache({ req, res, page: '/publish/blog' }) diff --git a/src/containers/editor/ArticleEditor/Footer.tsx b/src/containers/editor/ArticleEditor/Footer.tsx index 8865495a3..c60af29db 100644 --- a/src/containers/editor/ArticleEditor/Footer.tsx +++ b/src/containers/editor/ArticleEditor/Footer.tsx @@ -6,9 +6,10 @@ import type { TTag, TCommunity, TSubmitState, + TArticleThread, } from '@/spec' -import { THREAD } from '@/constant' +import { ARTICLE_THREAD } from '@/constant' import TagsList from '@/widgets/TagsList' import SubmitButton from '@/widgets/Buttons/SubmitButton' @@ -21,6 +22,7 @@ import { Wrapper, ArticleFooter, PublishFooter } from './styles/footer' import { editOnChange, onPublish, onCancel, setWordsCountState } from './logic' type TProps = { + thread: TArticleThread mode: TEditMode body: string tags: TTag[] @@ -31,6 +33,7 @@ type TProps = { } const Footer: FC = ({ + thread, mode, body, tags, @@ -47,7 +50,7 @@ const Footer: FC = ({ mLeft={0} size="medium" community={community} - thread={THREAD.POST} + thread={ARTICLE_THREAD.POST} withSetter={mode === 'publish'} /> = ({ onChange={setWordsCountState} min={40} /> - editOnChange(v, 'isQuestion')} - > - 求助 / 提问 - + {thread === ARTICLE_THREAD.POST && ( + editOnChange(v, 'isQuestion')} + > + 求助 / 提问 + + )} { + return ( + +
    + 规则与边界 +
  • + 找/换工作对于双方来讲都有着很高的试错和时间成本,真实和坦诚是沟通的前提。 +
  • +
  • + 如果你所属的公司超过 100 人,并且如果你不是 CEO 或要招聘 + CEO,只需要描述你所在组的情况即可,这样会节省很多不必要沟通成本。 +
  • +
  • + 请选择合适的标签,城市,技术等标签也会同步本帖到相关的子社区中, + 便于有意向的人分类查找。 +
  • +
  • + 标签能提供的信息(比如薪资,城市,技术方向等)在帖子的标题中不必重复。 +
  • +
  • 请不要在一篇帖子里同时招聘多种职位,这会被视作猎头行为。
  • +
  • with all due respect, 猎头勿扰,谢谢理解。
  • +
+
+ ) +} + +export default memo(PublishRules) diff --git a/src/containers/editor/ArticleEditor/PublishRules.tsx b/src/containers/editor/ArticleEditor/PublishRules/PostRules.tsx similarity index 86% rename from src/containers/editor/ArticleEditor/PublishRules.tsx rename to src/containers/editor/ArticleEditor/PublishRules/PostRules.tsx index 212e2b552..27f6b3f84 100644 --- a/src/containers/editor/ArticleEditor/PublishRules.tsx +++ b/src/containers/editor/ArticleEditor/PublishRules/PostRules.tsx @@ -1,12 +1,12 @@ import { FC, memo } from 'react' -import { Wrapper, Title, Ul, Li } from './styles/publish_rules' +import { Wrapper, Title, Ul, Li } from '../styles/publish_rules' const PublishRules: FC = () => { return (
    - 发帖须知 + 规则与边界
  • 友善是一切有意义讨论的基础和前提。Don't be an asshole.
  • 如果是寻求解答,请务必提供上下文、需求场景等必要信息, 并勾选发布上方的 @@ -19,7 +19,8 @@ const PublishRules: FC = () => {
  • 卖课,领资料,公众号引流等一月禁言起步。多次发布,停止服务。
  • - 请尊重自己和他人的时间,不要发布无意义的烂梗 / 黑话之类的 trash talk。 + 请尊重自己和他人的时间,不要发布无意义的烂梗 / 黑话, 以及排版糟糕的 + Trash Talk
  • 严禁侵权,勿议国是,不搞沸腾。
  • 如有其他疑问或建议反馈,请发布到 /feetback#publish。
  • diff --git a/src/containers/editor/ArticleEditor/PublishRules/index.tsx b/src/containers/editor/ArticleEditor/PublishRules/index.tsx new file mode 100644 index 000000000..99a59a4d8 --- /dev/null +++ b/src/containers/editor/ArticleEditor/PublishRules/index.tsx @@ -0,0 +1,25 @@ +import { FC, memo } from 'react' + +import type { TArticleThread } from '@/spec' +import { ARTICLE_THREAD } from '@/constant' + +import PostRules from './PostRules' +import JobRules from './JobRules' + +type TProps = { + thread: TArticleThread +} + +const PublishRules: FC = ({ thread }) => { + switch (thread) { + case ARTICLE_THREAD.JOB: { + return + } + + default: { + return + } + } +} + +export default memo(PublishRules) diff --git a/src/containers/editor/ArticleEditor/TitleInput/index.tsx b/src/containers/editor/ArticleEditor/TitleInput/index.tsx index e0beb6bc6..c8aadf607 100644 --- a/src/containers/editor/ArticleEditor/TitleInput/index.tsx +++ b/src/containers/editor/ArticleEditor/TitleInput/index.tsx @@ -5,14 +5,15 @@ import { editOnChange } from '../logic' type TProps = { title: string + placeholder: string } -const TitleInput: FC = ({ title }) => { +const TitleInput: FC = ({ title, placeholder }) => { return ( editOnChange(e, 'title')} /> diff --git a/src/containers/editor/ArticleEditor/index.tsx b/src/containers/editor/ArticleEditor/index.tsx index 7e004b472..9b968ba41 100755 --- a/src/containers/editor/ArticleEditor/index.tsx +++ b/src/containers/editor/ArticleEditor/index.tsx @@ -4,7 +4,7 @@ import { FC } from 'react' -import type { TMetric, TEditMode } from '@/spec' +import type { TMetric } from '@/spec' import { METRIC } from '@/constant' import { buildLog } from '@/utils/logger' @@ -32,17 +32,16 @@ type TProps = { testid?: string articleEditor?: TStore metric?: TMetric - mode?: TEditMode } const ArticleEditorContainer: FC = ({ testid = 'article-editor', articleEditor: store, metric = METRIC.ARTICLE_EDITOR, - mode = 'publish', }) => { - useInit(store, mode) + useInit(store) const { + mode, title, body, copyRight, @@ -50,6 +49,8 @@ const ArticleEditorContainer: FC = ({ communityData, submitState, tagsData, + texts, + thread, } = store const initEditor = mode === 'publish' || body !== '{}' @@ -65,15 +66,17 @@ const ArticleEditorContainer: FC = ({ /> )} - + {initEditor && ( editOnChange(JSON.stringify(v), 'body')} onLinkChange={(v) => editOnChange(v, 'linkAddr')} + placeholder={texts.holder.body} /> )}
    = ({
    - +
    diff --git a/src/containers/editor/ArticleEditor/logic.ts b/src/containers/editor/ArticleEditor/logic.ts index 8fd4184a3..f0226891f 100755 --- a/src/containers/editor/ArticleEditor/logic.ts +++ b/src/containers/editor/ArticleEditor/logic.ts @@ -143,15 +143,14 @@ const ErrSolver = [ }, ] -export const useInit = (_store: TStore, mode: TEditMode): void => { +export const useInit = (_store: TStore): void => { useEffect(() => { store = _store - store.mark({ mode }) sub$ = sr71$.data().subscribe($solver(DataSolver, ErrSolver)) log('useInit: ', store) loadCommunity() - if (mode === 'update') loadArticle() + if (store.mode === 'update') loadArticle() // return () => store.reset() return () => { @@ -160,5 +159,5 @@ export const useInit = (_store: TStore, mode: TEditMode): void => { sub$ = null store.reset() } - }, [_store, mode]) + }, [_store]) } diff --git a/src/containers/editor/ArticleEditor/schema.ts b/src/containers/editor/ArticleEditor/schema.ts index 5025a112c..1023c4e99 100755 --- a/src/containers/editor/ArticleEditor/schema.ts +++ b/src/containers/editor/ArticleEditor/schema.ts @@ -1,6 +1,7 @@ import { gql } from '@urql/core' import { F } from '@/schemas' +// post const createPost = gql` mutation ( $title: String! @@ -50,6 +51,32 @@ const updatePost = gql` } ` +const createJob = gql` + mutation ( + $title: String! + $body: String! + $communityId: ID! + $company: String! + $companyLink: String + $articleTags: [Id] + ) { + createJob( + title: $title + body: $body + communityId: $communityId + company: $company + companyLink: $companyLink + articleTags: $articleTags + ) { + id + title + meta { + thread + } + } + } +` + // viewer_has_subscribed const community = gql` query ($raw: String) { @@ -96,6 +123,7 @@ const schema = { post, createPost, updatePost, + createJob, community, } diff --git a/src/containers/editor/ArticleEditor/spec.ts b/src/containers/editor/ArticleEditor/spec.ts new file mode 100644 index 000000000..4ed62940c --- /dev/null +++ b/src/containers/editor/ArticleEditor/spec.ts @@ -0,0 +1,6 @@ +export type TTexts = { + holder: { + title: string + body: string + } +} diff --git a/src/containers/editor/ArticleEditor/store.ts b/src/containers/editor/ArticleEditor/store.ts index 89e43479c..4359b411d 100755 --- a/src/containers/editor/ArticleEditor/store.ts +++ b/src/containers/editor/ArticleEditor/store.ts @@ -15,10 +15,13 @@ import type { TArticleThread, TSubmitState, } from '@/spec' -import { markStates, toJS } from '@/utils/mobx' +import { ARTICLE_THREAD } from '@/constant' +import { markStates, toJS } from '@/utils/mobx' import { Community, Tag } from '@/model' +import type { TTexts } from './spec' + const ArticleEditor = T.model('ArticleEditor', { mode: T.optional(T.enumeration(['publish', 'update']), 'publish'), title: T.optional(T.string, ''), @@ -56,6 +59,30 @@ const ArticleEditor = T.model('ArticleEditor', { get tagsData(): TTag[] { return toJS(self.articleTags) }, + get texts(): TTexts { + const slf = self as TStore + const { thread } = slf + + switch (thread) { + case ARTICLE_THREAD.JOB: { + return { + holder: { + title: '// 职位标题', + body: "// 职位描述('Tab' 键插入富文本)", + }, + } + } + + default: { + return { + holder: { + title: '// 帖子标题', + body: "// 帖子内容('Tab' 键插入富文本)", + }, + } + } + } + }, get editingData() { const tagsIds = toJS(self.articleTags).map((t) => t.id) const baseFields = [ diff --git a/src/pages/publish/job.tsx b/src/pages/publish/job.tsx new file mode 100755 index 000000000..83a568d3f --- /dev/null +++ b/src/pages/publish/job.tsx @@ -0,0 +1,70 @@ +import { GetServerSideProps } from 'next' +import { Provider } from 'mobx-react' +import { METRIC, ARTICLE_THREAD } from '@/constant' +import { + articlePublishSEO, + ssrFetchPrepare, + ssrBaseStates, + refreshIfneed, + ssrError, +} from '@/utils' +import { P } from '@/schemas' + +import { useStore } from '@/stores/init' +import GlobalLayout from '@/containers/layout/GlobalLayout' +import ArticleEditor from '@/containers/editor/ArticleEditor' + +const fetchData = async (context, opt = {}) => { + const { gqClient } = ssrFetchPrepare(context, opt) + const sessionState = gqClient.request(P.sessionState) + + return { + ...(await sessionState), + } +} + +/** + * TODO: know-why + * + * 即使不需要这里的数据初始化 store, 这里也必须要放一个 getserverSideProps, + * 否则 Provider 在 URL 中包含 param 的时候会报错,十分诡异 。。 + * 大概率是 mobx-react 的问题, 真尼玛坑爹。 + */ +export const getServerSideProps: GetServerSideProps = async (context) => { + let resp + try { + resp = await fetchData(context) + const { sessionState } = resp + refreshIfneed(sessionState, '/publish/blog', context) + } catch (e) { + return ssrError(context, 'fetch', 500) + } + + const initProps = { + ...ssrBaseStates(resp), + viewing: { + activeThread: ARTICLE_THREAD.JOB, + }, + } + + return { props: { errorCode: null, ...initProps } } +} + +export const PublishJobPage = (props) => { + const store = useStore(props) + const seoConfig = articlePublishSEO() + + return ( + + + + + + ) +} + +export default PublishJobPage diff --git a/src/pages/update/post/[id].tsx b/src/pages/update/post/[id].tsx index fd0c91e22..a4a82c728 100755 --- a/src/pages/update/post/[id].tsx +++ b/src/pages/update/post/[id].tsx @@ -61,7 +61,7 @@ export const UpdatePostPage = (props) => { seoConfig={seoConfig} noSidebar > - + ) From 4c3d67f831486ab9e5c21bd32c29835be495e7f5 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 2 Nov 2021 14:56:45 +0800 Subject: [PATCH 02/11] refactor(job-workflow): wip --- .../editor/ArticleEditor/AddOn/JobAddOn.tsx | 20 ++++++++ .../AddOn/PostAddOn.tsx} | 18 +++---- .../editor/ArticleEditor/AddOn/index.tsx | 27 ++++++++++ .../ArticleEditor/PublishRules/JobRules.tsx | 2 +- src/containers/editor/ArticleEditor/index.tsx | 3 +- .../ArticleEditor/styles/addon/job_addon.ts | 50 +++++++++++++++++++ .../ArticleEditor/styles/addon/post_addon.ts | 41 +++++++++++++++ src/containers/editor/RichEditor/Options.tsx | 23 +++++++++ .../editor/RichEditor/Options/WorksLayout.tsx | 24 --------- .../editor/RichEditor/Options/index.tsx | 28 ----------- .../editor/RichEditor/RealEditor.tsx | 14 ++---- .../editor/RichEditor/styles/options.ts | 35 +------------ src/widgets/Icons/Laptop.tsx | 18 +++++++ 13 files changed, 195 insertions(+), 108 deletions(-) create mode 100644 src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx rename src/containers/editor/{RichEditor/Options/ArticleLayout.tsx => ArticleEditor/AddOn/PostAddOn.tsx} (72%) create mode 100644 src/containers/editor/ArticleEditor/AddOn/index.tsx create mode 100644 src/containers/editor/ArticleEditor/styles/addon/job_addon.ts create mode 100644 src/containers/editor/ArticleEditor/styles/addon/post_addon.ts create mode 100644 src/containers/editor/RichEditor/Options.tsx delete mode 100644 src/containers/editor/RichEditor/Options/WorksLayout.tsx delete mode 100644 src/containers/editor/RichEditor/Options/index.tsx create mode 100644 src/widgets/Icons/Laptop.tsx diff --git a/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx b/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx new file mode 100644 index 000000000..692907afa --- /dev/null +++ b/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx @@ -0,0 +1,20 @@ +import { FC, memo } from 'react' + +import { + Wrapper, + LaptopIcon, + CompanyInput, + LinkInput, +} from '../styles/addon/job_addon' + +const JobAddOn: FC = () => { + return ( + + + + + + ) +} + +export default memo(JobAddOn) diff --git a/src/containers/editor/RichEditor/Options/ArticleLayout.tsx b/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx similarity index 72% rename from src/containers/editor/RichEditor/Options/ArticleLayout.tsx rename to src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx index 48ff4a2ff..fff90e391 100644 --- a/src/containers/editor/RichEditor/Options/ArticleLayout.tsx +++ b/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx @@ -1,12 +1,14 @@ -import { FC, memo, useState, useCallback } from 'react' +import { FC, memo, useState } from 'react' import { isURL } from '@/utils/validator' -import { SpaceGrow } from '@/widgets/Common' import Checker from '@/widgets/Checker' -import Menu from '../Menu' - -import { Wrapper, LinkWrapper, LinkInput, ErrorHint } from '../styles/options' +import { + Wrapper, + LinkWrapper, + LinkInput, + ErrorHint, +} from '../styles/addon/post_addon' type TProps = { onLinkChange: (link: string) => void @@ -27,9 +29,6 @@ const Header: FC = ({ onLinkChange }) => { { - console.log('v.target.value: ', v.target.value) - console.log('isURL: ', isURL(v.target.value)) - if (!isURL(v.target.value)) { setInvalid(true) } else { @@ -43,9 +42,6 @@ const Header: FC = ({ onLinkChange }) => { {invalid && 无效地址} )} - - - ) } diff --git a/src/containers/editor/ArticleEditor/AddOn/index.tsx b/src/containers/editor/ArticleEditor/AddOn/index.tsx new file mode 100644 index 000000000..9749d6192 --- /dev/null +++ b/src/containers/editor/ArticleEditor/AddOn/index.tsx @@ -0,0 +1,27 @@ +import { FC, memo } from 'react' + +import type { TArticleThread } from '@/spec' +import { ARTICLE_THREAD } from '@/constant' + +import JobAddOn from './JobAddOn' +import PostAddOn from './PostAddOn' + +import { editOnChange } from '../logic' + +type TProps = { + thread: TArticleThread +} + +const Addon: FC = ({ thread }) => { + switch (thread) { + case ARTICLE_THREAD.JOB: { + return + } + + default: { + return editOnChange(v, 'linkAddr')} /> + } + } +} + +export default memo(Addon) diff --git a/src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx b/src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx index f0d38acbc..69f65d7c2 100644 --- a/src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx +++ b/src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx @@ -22,7 +22,7 @@ const PublishRules: FC = () => { 标签能提供的信息(比如薪资,城市,技术方向等)在帖子的标题中不必重复。
  • 请不要在一篇帖子里同时招聘多种职位,这会被视作猎头行为。
  • -
  • with all due respect, 猎头勿扰,谢谢理解。
  • +
  • With all due respect, 猎头勿扰,
) diff --git a/src/containers/editor/ArticleEditor/index.tsx b/src/containers/editor/ArticleEditor/index.tsx index 9b968ba41..86769b928 100755 --- a/src/containers/editor/ArticleEditor/index.tsx +++ b/src/containers/editor/ArticleEditor/index.tsx @@ -15,6 +15,7 @@ import RichEditor from '@/containers/editor/RichEditor' import CommunityBadgeSelector from '@/widgets/CommunityBadgeSelector' import TitleInput from './TitleInput' +import AddOn from './AddOn' import Footer from './Footer' import PublishRules from './PublishRules' @@ -71,7 +72,7 @@ const ArticleEditorContainer: FC = ({ editOnChange(JSON.stringify(v), 'body')} - onLinkChange={(v) => editOnChange(v, 'linkAddr')} + addon={} placeholder={texts.holder.body} /> )} diff --git a/src/containers/editor/ArticleEditor/styles/addon/job_addon.ts b/src/containers/editor/ArticleEditor/styles/addon/job_addon.ts new file mode 100644 index 000000000..2e59c0a8a --- /dev/null +++ b/src/containers/editor/ArticleEditor/styles/addon/job_addon.ts @@ -0,0 +1,50 @@ +import styled from 'styled-components' + +import Input from '@/widgets/Input' +import { theme } from '@/utils/themes' +import css from '@/utils/css' +// import animate from '@/utils/animations' + +import LaptopSVG from '@/icons/Laptop' + +export const Wrapper = styled.div` + ${css.flex('align-center')}; +` +export const LaptopIcon = styled(LaptopSVG)` + ${css.size(32)}; + fill: ${theme('thread.articleDigest')}; + margin-right: 2px; + + ${Wrapper}:hover & { + fill: ${theme('thread.articleTitle')}; + } + transition: fill 0.2s; +` +export const CompanyInput = styled(Input)` + border: none; + color: #139c9e; + background: none; + height: 26px; + + &::placeholder { + color: #4e7074; + } +` +export const LinkInput = styled(Input)` + display: block; + border: none; + border-left: 2px solid; + border-right: 2px solid; + border-radius: 5px; + border-color: #1b4d53; + background: none; + color: #486368; + padding: 2px 8px; + height: 20px; + font-size: 13px; + + &::placeholder { + color: #4a7073; + font-size: 12px; + } +` diff --git a/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts b/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts new file mode 100644 index 000000000..afbd87d3a --- /dev/null +++ b/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts @@ -0,0 +1,41 @@ +import styled from 'styled-components' + +import Input from '@/widgets/Input' +import { theme } from '@/utils/themes' +import css from '@/utils/css' +import animate from '@/utils/animations' + +export const Wrapper = styled.div` + ${css.flex('align-center')}; +` +export const LinkWrapper = styled.div` + position: relative; + width: 260px; + margin-left: 15px; +` +export const LinkInput = styled(Input)` + display: block; + border: none; + border-left: 2px solid; + border-right: 2px solid; + border-radius: 5px; + border-color: #1b4d53; + background: none; + color: #486368; + padding: 2px 8px; + height: 20px; + font-size: 13px; + + &::placeholder { + color: #4a7073; + font-size: 12px; + } +` +export const ErrorHint = styled.div` + position: absolute; + font-size: 13px; + right: -68px; + top: 1px; + color: ${theme('baseColor.red')}; + animation: ${animate.breath} 2.5s linear infinite; +` diff --git a/src/containers/editor/RichEditor/Options.tsx b/src/containers/editor/RichEditor/Options.tsx new file mode 100644 index 000000000..d33a5205b --- /dev/null +++ b/src/containers/editor/RichEditor/Options.tsx @@ -0,0 +1,23 @@ +import { FC, memo, ReactNode } from 'react' + +import { SpaceGrow } from '@/widgets/Common' + +import Menu from './Menu' + +import { Wrapper } from './styles/options' + +type TProps = { + addon: ReactNode +} + +const Options: FC = ({ addon }) => { + return ( + + {addon} + + + + ) +} + +export default memo(Options) diff --git a/src/containers/editor/RichEditor/Options/WorksLayout.tsx b/src/containers/editor/RichEditor/Options/WorksLayout.tsx deleted file mode 100644 index 849296efc..000000000 --- a/src/containers/editor/RichEditor/Options/WorksLayout.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { FC, memo } from 'react' - -import Checker from '@/widgets/Checker' -import Menu from '../Menu' - -import { Wrapper } from '../styles/options' - -const Header: FC = () => { - return ( - - toggleTemplate(checked)} - > - 使用模板 - - - - ) -} - -export default memo(Header) diff --git a/src/containers/editor/RichEditor/Options/index.tsx b/src/containers/editor/RichEditor/Options/index.tsx deleted file mode 100644 index 45c47de1e..000000000 --- a/src/containers/editor/RichEditor/Options/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { FC, memo } from 'react' - -import ArticleLayout from './ArticleLayout' -import WorksLayout from './WorksLayout' - -type TProps = { - type: 'article' | 'works' | 'job' | 'comment' | 'radar' - onLinkChange: (link: string) => void - onUseTemplateChange: (use: boolean) => void -} - -const Options: FC = ({ type, onLinkChange, onUseTemplateChange }) => { - switch (type) { - case 'comment': { - return null - } - - case 'works': { - return - } - - default: { - return - } - } -} - -export default memo(Options) diff --git a/src/containers/editor/RichEditor/RealEditor.tsx b/src/containers/editor/RichEditor/RealEditor.tsx index 8bfd9270e..24acdb016 100644 --- a/src/containers/editor/RichEditor/RealEditor.tsx +++ b/src/containers/editor/RichEditor/RealEditor.tsx @@ -4,7 +4,7 @@ * */ -import { FC } from 'react' +import { FC, ReactNode } from 'react' import RichEditor from '@groupher/react-editor' @@ -27,10 +27,9 @@ type TProps = { placeholder?: string data?: string type?: 'article' | 'works' | 'job' | 'comment' | 'radar' + addon?: ReactNode reinitKey?: string onChange?: (json) => void - onLinkChange?: (link: string) => void - onUseTemplateChange?: (use: boolean) => void } const RichEditorContainer: FC = ({ @@ -40,8 +39,7 @@ const RichEditorContainer: FC = ({ type = 'article', reinitKey = '', onChange = log, - onLinkChange = log, - onUseTemplateChange = log, + addon =
, }) => { useInit(store) @@ -49,11 +47,7 @@ const RichEditorContainer: FC = ({ return ( - + ) => { + return ( + + + + ) +} + +export default memo(Laptop) From cf83ff5d17704a2631f895c5285290ebd4d189d1 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 2 Nov 2021 16:24:30 +0800 Subject: [PATCH 03/11] refactor(job-workflow): editor addon re-org --- .../editor/ArticleEditor/AddOn/JobAddOn.tsx | 37 +++++++++++++++++-- .../editor/ArticleEditor/AddOn/PostAddOn.tsx | 16 +++++--- .../editor/ArticleEditor/AddOn/index.tsx | 11 +++--- .../editor/ArticleEditor/Footer.tsx | 10 ++--- src/containers/editor/ArticleEditor/index.tsx | 13 +++---- src/containers/editor/ArticleEditor/logic.ts | 8 ++-- src/containers/editor/ArticleEditor/spec.ts | 15 ++++++++ src/containers/editor/ArticleEditor/store.ts | 36 ++++++++++++------ .../ArticleEditor/styles/addon/index.ts | 25 +++++++++++++ .../ArticleEditor/styles/addon/job_addon.ts | 27 ++++++-------- .../ArticleEditor/styles/addon/post_addon.ts | 27 +++----------- src/pages/publish/job.tsx | 2 +- src/widgets/Icons/Laptop.tsx | 2 +- src/widgets/Input/styles/index.ts | 2 +- utils/seo.ts | 14 +++++-- 15 files changed, 156 insertions(+), 89 deletions(-) create mode 100644 src/containers/editor/ArticleEditor/styles/addon/index.ts diff --git a/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx b/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx index 692907afa..a072f09d1 100644 --- a/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx +++ b/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx @@ -1,18 +1,47 @@ -import { FC, memo } from 'react' +import { FC, memo, useState } from 'react' +import { isURL } from '@/utils/validator' + +import type { TEditData } from '../spec' import { Wrapper, LaptopIcon, + LinkIcon, CompanyInput, LinkInput, } from '../styles/addon/job_addon' +import { editOnChange } from '../logic' + +type TProps = { + editData: TEditData +} + +const JobAddOn: FC = ({ editData }) => { + const [invalid, setInvalid] = useState(false) -const JobAddOn: FC = () => { return ( - - + editOnChange(v, 'company')} + /> + + { + if (!isURL(v.target.value)) { + setInvalid(true) + } else { + setInvalid(false) + } + + editOnChange(v, 'companyLink') + }} + /> ) } diff --git a/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx b/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx index fff90e391..95b12291c 100644 --- a/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx +++ b/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx @@ -3,18 +3,20 @@ import { FC, memo, useState } from 'react' import { isURL } from '@/utils/validator' import Checker from '@/widgets/Checker' +import type { TEditData } from '../spec' import { Wrapper, LinkWrapper, LinkInput, - ErrorHint, + LinkIcon, } from '../styles/addon/post_addon' +import { editOnChange } from '../logic' type TProps = { - onLinkChange: (link: string) => void + editData: TEditData } -const Header: FC = ({ onLinkChange }) => { +const PostAddOn: FC = ({ editData }) => { const [reprint, setReprint] = useState(false) const [invalid, setInvalid] = useState(false) @@ -26,7 +28,10 @@ const Header: FC = ({ onLinkChange }) => { {reprint && ( + { if (!isURL(v.target.value)) { @@ -35,15 +40,14 @@ const Header: FC = ({ onLinkChange }) => { setInvalid(false) } - onLinkChange(v) + editOnChange(v, 'linkAddr') }} autoFocus /> - {invalid && 无效地址} )} ) } -export default memo(Header) +export default memo(PostAddOn) diff --git a/src/containers/editor/ArticleEditor/AddOn/index.tsx b/src/containers/editor/ArticleEditor/AddOn/index.tsx index 9749d6192..1ac727fc9 100644 --- a/src/containers/editor/ArticleEditor/AddOn/index.tsx +++ b/src/containers/editor/ArticleEditor/AddOn/index.tsx @@ -3,23 +3,24 @@ import { FC, memo } from 'react' import type { TArticleThread } from '@/spec' import { ARTICLE_THREAD } from '@/constant' +import type { TEditData } from '../spec' + import JobAddOn from './JobAddOn' import PostAddOn from './PostAddOn' -import { editOnChange } from '../logic' - type TProps = { thread: TArticleThread + editData: TEditData } -const Addon: FC = ({ thread }) => { +const Addon: FC = ({ thread, editData }) => { switch (thread) { case ARTICLE_THREAD.JOB: { - return + return } default: { - return editOnChange(v, 'linkAddr')} /> + return } } } diff --git a/src/containers/editor/ArticleEditor/Footer.tsx b/src/containers/editor/ArticleEditor/Footer.tsx index c60af29db..aa7c674e2 100644 --- a/src/containers/editor/ArticleEditor/Footer.tsx +++ b/src/containers/editor/ArticleEditor/Footer.tsx @@ -18,16 +18,15 @@ import Checker from '@/widgets/Checker' import { SpaceGrow } from '@/widgets/Common' import WordsCounter from '@/widgets/WordsCounter' +import type { TEditData } from './spec' import { Wrapper, ArticleFooter, PublishFooter } from './styles/footer' import { editOnChange, onPublish, onCancel, setWordsCountState } from './logic' type TProps = { thread: TArticleThread mode: TEditMode - body: string + editData: TEditData tags: TTag[] - isQuestion: boolean - copyRight: string submitState: TSubmitState community: TCommunity } @@ -35,13 +34,12 @@ type TProps = { const Footer: FC = ({ thread, mode, - body, + editData, tags, - isQuestion, - copyRight, submitState, community, }) => { + const { body, isQuestion, copyRight } = editData return ( diff --git a/src/containers/editor/ArticleEditor/index.tsx b/src/containers/editor/ArticleEditor/index.tsx index 86769b928..af5ca3327 100755 --- a/src/containers/editor/ArticleEditor/index.tsx +++ b/src/containers/editor/ArticleEditor/index.tsx @@ -43,17 +43,16 @@ const ArticleEditorContainer: FC = ({ useInit(store) const { mode, - title, - body, - copyRight, - isQuestion, communityData, submitState, tagsData, texts, thread, + editData, } = store + const { title, body } = editData + const initEditor = mode === 'publish' || body !== '{}' return ( @@ -72,7 +71,7 @@ const ArticleEditorContainer: FC = ({ editOnChange(JSON.stringify(v), 'body')} - addon={} + addon={} placeholder={texts.holder.body} /> )} @@ -80,10 +79,8 @@ const ArticleEditorContainer: FC = ({ thread={thread} mode={mode} tags={tagsData} - body={body} community={communityData} - copyRight={copyRight} - isQuestion={isQuestion} + editData={editData} submitState={submitState} /> diff --git a/src/containers/editor/ArticleEditor/logic.ts b/src/containers/editor/ArticleEditor/logic.ts index f0226891f..5bf79e246 100755 --- a/src/containers/editor/ArticleEditor/logic.ts +++ b/src/containers/editor/ArticleEditor/logic.ts @@ -78,8 +78,8 @@ export const onPublish = (): void => { } const doCreate = () => { - const { thread, editingData, communityId } = store - const variables = { communityId, ...editingData } + const { thread, editData, communityId } = store + const variables = { communityId, ...editData } log('onPublish --> ', variables) const schema = S[`create${titleCase(thread)}`] @@ -87,9 +87,9 @@ const doCreate = () => { } const doUpdate = () => { - const { thread, editingData, viewingArticle } = store + const { thread, editData, viewingArticle } = store const { id } = viewingArticle - const variables = { id, ...editingData } + const variables = { id, ...editData } log('onUpdate --> ', variables) const schema = S[`update${titleCase(thread)}`] diff --git a/src/containers/editor/ArticleEditor/spec.ts b/src/containers/editor/ArticleEditor/spec.ts index 4ed62940c..f6c1c148e 100644 --- a/src/containers/editor/ArticleEditor/spec.ts +++ b/src/containers/editor/ArticleEditor/spec.ts @@ -1,6 +1,21 @@ +import type { TID } from '@/spec' + export type TTexts = { holder: { title: string body: string } } + +export type TEditData = { + title: string + body: string + copyRight: string + isQuestion?: boolean + linkAddr?: string + + company?: string + companyLink?: string + + articleTags: [TID] +} diff --git a/src/containers/editor/ArticleEditor/store.ts b/src/containers/editor/ArticleEditor/store.ts index 4359b411d..b656d3d5c 100755 --- a/src/containers/editor/ArticleEditor/store.ts +++ b/src/containers/editor/ArticleEditor/store.ts @@ -20,7 +20,7 @@ import { ARTICLE_THREAD } from '@/constant' import { markStates, toJS } from '@/utils/mobx' import { Community, Tag } from '@/model' -import type { TTexts } from './spec' +import type { TTexts, TEditData } from './spec' const ArticleEditor = T.model('ArticleEditor', { mode: T.optional(T.enumeration(['publish', 'update']), 'publish'), @@ -31,6 +31,11 @@ const ArticleEditor = T.model('ArticleEditor', { isQuestion: T.optional(T.boolean, false), community: T.optional(Community, {}), articleTags: T.optional(T.array(Tag), []), + + // job spec + company: T.optional(T.string, ''), + companyLink: T.optional(T.string, ''), + // showSubTitle: T.optional(T.boolean, false), publishing: T.optional(T.boolean, false), publishDone: T.optional(T.boolean, false), @@ -83,17 +88,24 @@ const ArticleEditor = T.model('ArticleEditor', { } } }, - get editingData() { - const tagsIds = toJS(self.articleTags).map((t) => t.id) - const baseFields = [ - 'title', - 'body', - 'copyRight', - 'isQuestion', - 'linkAddr', - ] - - return { ...pick(baseFields, self), articleTags: tagsIds } + get editData(): TEditData { + const slf = self as TStore + + const tagsIds = toJS(slf.articleTags).map((t) => t.id) + let baseFields + switch (slf.thread) { + case ARTICLE_THREAD.JOB: { + baseFields = ['title', 'body', 'copyRight', 'company', 'companyLink'] + break + } + + default: { + baseFields = ['title', 'body', 'copyRight', 'isQuestion', 'linkAddr'] + break + } + } + + return { ...pick(baseFields, slf), articleTags: tagsIds } }, get isReady(): boolean { const slf = self as TStore diff --git a/src/containers/editor/ArticleEditor/styles/addon/index.ts b/src/containers/editor/ArticleEditor/styles/addon/index.ts new file mode 100644 index 000000000..476749bb2 --- /dev/null +++ b/src/containers/editor/ArticleEditor/styles/addon/index.ts @@ -0,0 +1,25 @@ +import styled from 'styled-components' + +import Input from '@/widgets/Input' +import { theme } from '@/utils/themes' +import css from '@/utils/css' +// import animate from '@/utils/animations' + +import LinkSVG from '@/icons/Link' + +export const LinkIcon = styled(LinkSVG)` + ${css.size(16)}; + fill: ${theme('thread.articleDigest')}; + margin-top: 1px; + + transition: fill 0.2s; +` +export const LinkInput = styled(Input)<{ invalid?: boolean }>` + border: none; + background: none; + height: 26px; + width: 100px; + color: ${({ invalid }) => + invalid ? theme('baseColor.red') : theme('thread.articleDigest')}; + width: 200px; +` diff --git a/src/containers/editor/ArticleEditor/styles/addon/job_addon.ts b/src/containers/editor/ArticleEditor/styles/addon/job_addon.ts index 2e59c0a8a..6b8135627 100644 --- a/src/containers/editor/ArticleEditor/styles/addon/job_addon.ts +++ b/src/containers/editor/ArticleEditor/styles/addon/job_addon.ts @@ -6,12 +6,15 @@ import css from '@/utils/css' // import animate from '@/utils/animations' import LaptopSVG from '@/icons/Laptop' +import LinkSVG from '@/icons/Link' + +export { LinkInput } from './index' export const Wrapper = styled.div` ${css.flex('align-center')}; ` export const LaptopIcon = styled(LaptopSVG)` - ${css.size(32)}; + ${css.size(16)}; fill: ${theme('thread.articleDigest')}; margin-right: 2px; @@ -25,26 +28,18 @@ export const CompanyInput = styled(Input)` color: #139c9e; background: none; height: 26px; + width: 100px; &::placeholder { color: #4e7074; } ` -export const LinkInput = styled(Input)` - display: block; - border: none; - border-left: 2px solid; - border-right: 2px solid; - border-radius: 5px; - border-color: #1b4d53; - background: none; - color: #486368; - padding: 2px 8px; - height: 20px; - font-size: 13px; +export const LinkIcon = styled(LinkSVG)` + ${css.size(16)}; + fill: ${theme('thread.articleDigest')}; - &::placeholder { - color: #4a7073; - font-size: 12px; + ${Wrapper}:hover & { + fill: ${theme('thread.articleTitle')}; } + transition: fill 0.2s; ` diff --git a/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts b/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts index afbd87d3a..9fefb8539 100644 --- a/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts +++ b/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts @@ -1,41 +1,24 @@ import styled from 'styled-components' -import Input from '@/widgets/Input' import { theme } from '@/utils/themes' import css from '@/utils/css' import animate from '@/utils/animations' +export { LinkIcon, LinkInput } from './index' + export const Wrapper = styled.div` ${css.flex('align-center')}; ` export const LinkWrapper = styled.div` + ${css.flex('align-center')}; position: relative; - width: 260px; margin-left: 15px; ` -export const LinkInput = styled(Input)` - display: block; - border: none; - border-left: 2px solid; - border-right: 2px solid; - border-radius: 5px; - border-color: #1b4d53; - background: none; - color: #486368; - padding: 2px 8px; - height: 20px; - font-size: 13px; - - &::placeholder { - color: #4a7073; - font-size: 12px; - } -` export const ErrorHint = styled.div` position: absolute; - font-size: 13px; + font-size: 12px; right: -68px; - top: 1px; + top: 4px; color: ${theme('baseColor.red')}; animation: ${animate.breath} 2.5s linear infinite; ` diff --git a/src/pages/publish/job.tsx b/src/pages/publish/job.tsx index 83a568d3f..9c013de71 100755 --- a/src/pages/publish/job.tsx +++ b/src/pages/publish/job.tsx @@ -52,7 +52,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { export const PublishJobPage = (props) => { const store = useStore(props) - const seoConfig = articlePublishSEO() + const seoConfig = articlePublishSEO(ARTICLE_THREAD.JOB) return ( diff --git a/src/widgets/Icons/Laptop.tsx b/src/widgets/Icons/Laptop.tsx index 13a6c59ae..b94632fea 100644 --- a/src/widgets/Icons/Laptop.tsx +++ b/src/widgets/Icons/Laptop.tsx @@ -10,7 +10,7 @@ const Laptop = (props: SVGProps) => { height={200} {...props} > - + ) } diff --git a/src/widgets/Input/styles/index.ts b/src/widgets/Input/styles/index.ts index 066cca875..dc8b78fd9 100755 --- a/src/widgets/Input/styles/index.ts +++ b/src/widgets/Input/styles/index.ts @@ -15,7 +15,7 @@ export const Wrapper = styled.div.attrs(({ testid }: TTestable) => ({ 'data-test-id': testid, }))` position: relative; - width: 100%; + width: auto; ` const AddOn = styled.div` position: absolute; diff --git a/utils/seo.ts b/utils/seo.ts index c02d58bdd..5b0b6fd3f 100644 --- a/utils/seo.ts +++ b/utils/seo.ts @@ -103,15 +103,23 @@ export const articlePublishSEO = (thread: TThread = THREAD.POST): TSEO => { switch (thread) { case THREAD.BLOG: { return { - url: `${SITE_URL}/todo`, + url: `${SITE_URL}/publish/blog`, title: '发布博客', description: '提交新博客到社区', } } + case THREAD.JOB: { + return { + url: `${SITE_URL}/publish/job`, + title: '发布招聘', + description: ' 发布招人启事', + } + } + case THREAD.WORKS: { return { - url: `${SITE_URL}/todo`, + url: `${SITE_URL}/publish/works`, title: '发布作品', description: '提交新作品到社区', } @@ -119,7 +127,7 @@ export const articlePublishSEO = (thread: TThread = THREAD.POST): TSEO => { default: { return { - url: `${SITE_URL}/todo`, + url: `${SITE_URL}/publish/post`, title: '发布帖子', description: '发布新帖子到社区', } From 204e90ca459f95fa3460e893d7f85533b558061b Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 2 Nov 2021 18:21:51 +0800 Subject: [PATCH 04/11] refactor(job-workflow): job detail page --- server/routes.js | 6 ++ .../ArticleDigest/DesktopView/JobLayout.tsx | 77 +++++++++++++++++ .../ArticleDigest/DesktopView/Layout.tsx | 5 ++ .../styles/desktop_view/job_layout.ts | 29 +++++++ .../ArticleEditor/PublishRules/JobRules.tsx | 2 +- src/containers/editor/ArticleEditor/logic.ts | 4 + src/pages/job/[id].tsx | 83 +++++++++++++++++++ src/pages/post/[id].tsx | 5 +- src/schemas/pages/job.ts | 2 + 9 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx create mode 100644 src/containers/digest/ArticleDigest/styles/desktop_view/job_layout.ts create mode 100755 src/pages/job/[id].tsx diff --git a/server/routes.js b/server/routes.js index 27880937f..309a969f5 100644 --- a/server/routes.js +++ b/server/routes.js @@ -89,6 +89,12 @@ router.route('/post/:id').get((req, res) => { return renderAndCache({ req, res, path: `/post/${id}` }) }) +// 工作页 +router.route('/job/:id').get((req, res) => { + const { id } = req.params + return renderAndCache({ req, res, path: `/job/${id}` }) +}) + router.route('/blog/:id').get((req, res) => { const { id } = req.params return renderAndCache({ req, res, path: `/blog/${id}` }) diff --git a/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx b/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx new file mode 100644 index 000000000..132d94e81 --- /dev/null +++ b/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx @@ -0,0 +1,77 @@ +/* + * JobLayout + */ + +import { FC, Fragment, memo } from 'react' + +import type { TJob, TMetric } from '@/spec' +import { METRIC } from '@/constant' +import { buildLog } from '@/utils/logger' + +import { SpaceGrow } from '@/widgets/Common' +import ArticleBaseStats from '@/widgets/ArticleBaseStats' +import ArticleBelongCommunity from '@/widgets/ArticleBelongCommunity' +import DotDivider from '@/widgets/DotDivider' +import ArchivedSign from '@/widgets/ArchivedSign' +import ArticleMenu from '@/widgets/ArticleMenu' +import ReadableDate from '@/widgets/ReadableDate' +import Linker from '@/widgets/Linker' + +import { + Main, + Header, + PublishDateInfo, + Title, + BottomInfo, + CommunityInfo, + CompanyWrapper, + LaptopIcon, + CompanyName, +} from '../styles/desktop_view/job_layout' + +/* eslint-disable-next-line */ +const log = buildLog('C:ArticleDigest') + +type TProps = { + article: TJob + metric?: TMetric +} + +const JobLayout: FC = ({ metric = METRIC.ARTICLE, article }) => { + log('company: ', article.company) + log('companyLink: ', article.companyLink) + + return ( + +
+
+ + + + {article.isArchived && ( + + + + + )} + + +
+ {article.title}-job + + + + {article.company} + + + + +
+ + + +
+ ) +} + +export default memo(JobLayout) diff --git a/src/containers/digest/ArticleDigest/DesktopView/Layout.tsx b/src/containers/digest/ArticleDigest/DesktopView/Layout.tsx index 718aa006b..da0fdfd7c 100644 --- a/src/containers/digest/ArticleDigest/DesktopView/Layout.tsx +++ b/src/containers/digest/ArticleDigest/DesktopView/Layout.tsx @@ -4,6 +4,7 @@ import type { TArticle, TMetric, TThread } from '@/spec' import { THREAD, METRIC } from '@/constant' import PostLayout from './PostLayout' +import JobLayout from './JobLayout' import BlogLayout from './BlogLayout' import WorksLayout from './WorksLayout' @@ -25,6 +26,10 @@ const Layout: FC = ({ return } + case THREAD.JOB: { + return + } + case THREAD.BLOG: { return } diff --git a/src/containers/digest/ArticleDigest/styles/desktop_view/job_layout.ts b/src/containers/digest/ArticleDigest/styles/desktop_view/job_layout.ts new file mode 100644 index 000000000..25b3f4078 --- /dev/null +++ b/src/containers/digest/ArticleDigest/styles/desktop_view/job_layout.ts @@ -0,0 +1,29 @@ +import styled from 'styled-components' + +import { theme } from '@/utils/themes' +import css from '@/utils/css' + +import LaptopSVG from '@/icons/Laptop' + +export { + Main, + Header, + PublishDateInfo, + Title, + BottomInfo, + CommunityInfo, +} from './post_layout' + +export const CompanyWrapper = styled.div` + ${css.flex('align-center')}; + color: ${theme('thread.articleDigest')}; +` +export const LaptopIcon = styled(LaptopSVG)` + ${css.size(16)}; + fill: ${theme('thread.articleDigest')}; + margin-right: 4px; +` +export const CompanyName = styled.div` + font-size: 13px; + color: ${theme('thread.articleTitle')}; +` diff --git a/src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx b/src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx index 69f65d7c2..e5270bc66 100644 --- a/src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx +++ b/src/containers/editor/ArticleEditor/PublishRules/JobRules.tsx @@ -22,7 +22,7 @@ const PublishRules: FC = () => { 标签能提供的信息(比如薪资,城市,技术方向等)在帖子的标题中不必重复。
  • 请不要在一篇帖子里同时招聘多种职位,这会被视作猎头行为。
  • -
  • With all due respect, 猎头勿扰,
  • +
  • With all due respect, 请勿发布猎头职位,
  • ) diff --git a/src/containers/editor/ArticleEditor/logic.ts b/src/containers/editor/ArticleEditor/logic.ts index 5bf79e246..b722fb291 100755 --- a/src/containers/editor/ArticleEditor/logic.ts +++ b/src/containers/editor/ArticleEditor/logic.ts @@ -122,6 +122,10 @@ const DataSolver = [ match: asyncRes('createPost'), action: handleMutateRes, }, + { + match: asyncRes('createJob'), + action: handleMutateRes, + }, { match: asyncRes('updatePost'), action: handleMutateRes, diff --git a/src/pages/job/[id].tsx b/src/pages/job/[id].tsx new file mode 100755 index 000000000..4086fe568 --- /dev/null +++ b/src/pages/job/[id].tsx @@ -0,0 +1,83 @@ +import { Provider } from 'mobx-react' + +import { ARTICLE_THREAD, METRIC } from '@/constant' +import { + ssrBaseStates, + ssrFetchPrepare, + ssrError, + articleSEO, + ssrGetParam, + refreshIfneed, +} from '@/utils' +import { useStore } from '@/stores/init' + +import GlobalLayout from '@/containers/layout/GlobalLayout' +import ArticleDigest from '@/containers/digest/ArticleDigest' +import ArticleContent from '@/containers/content/ArticleContent' + +import { P } from '@/schemas' + +const fetchData = async (context, opt = {}) => { + const id = ssrGetParam(context, 'id') + const { gqClient, userHasLogin } = ssrFetchPrepare(context, opt) + + // query data + const sessionState = gqClient.request(P.sessionState) + const job = gqClient.request(P.job, { id, userHasLogin }) + + const subscribedCommunities = gqClient.request(P.subscribedCommunities, { + filter: { + page: 1, + size: 30, + }, + }) + + return { + ...(await sessionState), + ...(await job), + ...(await subscribedCommunities), + } +} + +export const getServerSideProps = async (context) => { + let resp + try { + resp = await fetchData(context) + const { job, sessionState } = resp + refreshIfneed(sessionState, `/job/${job.id}`, context) + } catch (e) { + console.log('#### error from server: ', e) + return ssrError(context, 'fetch', 500) + } + + const { job } = resp + + const initProps = { + ...ssrBaseStates(resp), + viewing: { + job, + activeThread: ARTICLE_THREAD.JOB, + }, + } + + return { props: { errorCode: null, ...initProps } } +} + +const JobPage = (props) => { + const store = useStore(props) + const { viewing } = props + const { job } = viewing + + const seoConfig = articleSEO(ARTICLE_THREAD.JOB, job) + + return ( + + + + + + + ) +} + +export default JobPage diff --git a/src/pages/post/[id].tsx b/src/pages/post/[id].tsx index 2e7d62171..4e190539b 100755 --- a/src/pages/post/[id].tsx +++ b/src/pages/post/[id].tsx @@ -1,6 +1,6 @@ import { Provider } from 'mobx-react' -import { ROUTE, ARTICLE_THREAD, METRIC } from '@/constant' +import { ARTICLE_THREAD, METRIC } from '@/constant' import { ssrBaseStates, ssrFetchPrepare, @@ -54,13 +54,10 @@ export const getServerSideProps = async (context) => { const initProps = { ...ssrBaseStates(resp), - route: { mainPath: ROUTE.POST, subPath: post.id }, viewing: { post, activeThread: ARTICLE_THREAD.POST, }, - // TODO: load comments on Client - // comments: { pagedComments }, } return { props: { errorCode: null, ...initProps } } diff --git a/src/schemas/pages/job.ts b/src/schemas/pages/job.ts index ee4dab042..18d3153bb 100755 --- a/src/schemas/pages/job.ts +++ b/src/schemas/pages/job.ts @@ -5,6 +5,8 @@ export const job = ` job(id: $id) { ${F.article} ${F.articleDetail} + company + companyLink } } ` From 2588ab9c7ec084729c8bca633f4f2e4049e259c8 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 2 Nov 2021 18:57:31 +0800 Subject: [PATCH 05/11] refactor(job-workflow): update workflow --- server/routes.js | 10 ++- .../ArticleDigest/DesktopView/JobLayout.tsx | 2 +- src/containers/editor/ArticleEditor/logic.ts | 4 ++ src/containers/editor/ArticleEditor/schema.ts | 57 +++++++++++++++ src/containers/editor/ArticleEditor/store.ts | 9 ++- src/pages/update/job/[id].tsx | 70 +++++++++++++++++++ 6 files changed, 148 insertions(+), 4 deletions(-) create mode 100755 src/pages/update/job/[id].tsx diff --git a/server/routes.js b/server/routes.js index 309a969f5..27f2cc827 100644 --- a/server/routes.js +++ b/server/routes.js @@ -115,17 +115,23 @@ router.route('/publish/post').get((req, res) => { return renderAndCache({ req, res, page: '/publish/post' }) }) -// 编辑新帖子 +// 编辑帖子 router.route('/update/post/:id').get((req, res) => { const { id } = req.params return renderAndCache({ req, res, path: `/update/post/${id}` }) }) -// 创建新帖子 +// 创建新工作 router.route('/publish/job').get((req, res) => { return renderAndCache({ req, res, page: '/publish/job' }) }) +// 编辑工作 +router.route('/update/job/:id').get((req, res) => { + const { id } = req.params + return renderAndCache({ req, res, path: `/update/job/${id}` }) +}) + // 创建新博客 router.route('/publish/blog').get((req, res) => { return renderAndCache({ req, res, page: '/publish/blog' }) diff --git a/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx b/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx index 132d94e81..fe84d6407 100644 --- a/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx +++ b/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx @@ -57,7 +57,7 @@ const JobLayout: FC = ({ metric = METRIC.ARTICLE, article }) => { - {article.title}-job + {article.title} diff --git a/src/containers/editor/ArticleEditor/logic.ts b/src/containers/editor/ArticleEditor/logic.ts index b722fb291..2320ec58c 100755 --- a/src/containers/editor/ArticleEditor/logic.ts +++ b/src/containers/editor/ArticleEditor/logic.ts @@ -130,6 +130,10 @@ const DataSolver = [ match: asyncRes('updatePost'), action: handleMutateRes, }, + { + match: asyncRes('updateJob'), + action: handleMutateRes, + }, { match: asyncRes('community'), action: ({ community }) => store.mark({ community }), diff --git a/src/containers/editor/ArticleEditor/schema.ts b/src/containers/editor/ArticleEditor/schema.ts index 1023c4e99..7ef0eade1 100755 --- a/src/containers/editor/ArticleEditor/schema.ts +++ b/src/containers/editor/ArticleEditor/schema.ts @@ -77,6 +77,32 @@ const createJob = gql` } ` +const updateJob = gql` + mutation ( + $id: ID! + $title: String + $company: String! + $companyLink: String + $body: String + $articleTags: [Ids] + ) { + updateJob( + id: $id + title: $title + company: $company + companyLink: $companyLink + body: $body + articleTags: $articleTags + ) { + id + title + meta { + thread + } + } + } +` + // viewer_has_subscribed const community = gql` query ($raw: String) { @@ -119,11 +145,42 @@ const post = gql` } } ` +const job = gql` + query job($id: ID!) { + job(id: $id) { + id + title + company + companyLink + copyRight + archivedAt + isArchived + + originalCommunity { + ${F.community} + } + + articleTags { + ${F.tag} + } + + meta { + thread + } + + document { + body + } + } + } +` const schema = { post, + job, createPost, updatePost, createJob, + updateJob, community, } diff --git a/src/containers/editor/ArticleEditor/store.ts b/src/containers/editor/ArticleEditor/store.ts index b656d3d5c..50d107b2c 100755 --- a/src/containers/editor/ArticleEditor/store.ts +++ b/src/containers/editor/ArticleEditor/store.ts @@ -53,7 +53,7 @@ const ArticleEditor = T.model('ArticleEditor', { }, get thread(): TArticleThread { const root = getParent(self) as TRootStore - return toJS(root.viewing.activeThread) + return toJS(root.viewing.viewingThread) }, get communityData(): TCommunity { return toJS(self.community) @@ -142,6 +142,10 @@ const ArticleEditor = T.model('ArticleEditor', { document, originalCommunity, articleTags, + // @ts-ignore + company, + // @ts-ignore + companyLink, } = article self.title = title @@ -155,6 +159,9 @@ const ArticleEditor = T.model('ArticleEditor', { if (isQuestion) self.isQuestion = isQuestion // @ts-ignore if (articleTags) self.articleTags = articleTags + + if (company) self.company = company + if (companyLink) self.companyLink = companyLink }, reset(): void { self.mode = 'publish' diff --git a/src/pages/update/job/[id].tsx b/src/pages/update/job/[id].tsx new file mode 100755 index 000000000..78a261888 --- /dev/null +++ b/src/pages/update/job/[id].tsx @@ -0,0 +1,70 @@ +import { GetServerSideProps } from 'next' +import { Provider } from 'mobx-react' +import { METRIC, ARTICLE_THREAD } from '@/constant' +import { + articleUpdateSEO, + ssrBaseStates, + ssrRescue, + ssrFetchPrepare, + ssrGetParam, +} from '@/utils' +import { P } from '@/schemas' + +import { useStore } from '@/stores/init' +import GlobalLayout from '@/containers/layout/GlobalLayout' +import ArticleEditor from '@/containers/editor/ArticleEditor' + +const fetchData = async (context, opt = {}) => { + const { gqClient } = ssrFetchPrepare(context, opt) + + const sessionState = gqClient.request(P.sessionState) + + return { + ...(await sessionState), + } +} + +export const getServerSideProps: GetServerSideProps = async (context) => { + let resp + try { + resp = await fetchData(context) + } catch ({ response: { errors } }) { + if (ssrRescue.hasLoginError(errors)) { + resp = await fetchData(context, { tokenExpired: true }) + } + } + + const id = ssrGetParam(context, 'id') + + const initProps = { + ...ssrBaseStates(resp), + articleEditor: { + mode: 'update', + }, + viewing: { + job: { id }, + viewingThread: ARTICLE_THREAD.JOB, + }, + } + + return { props: { errorCode: null, ...initProps } } +} + +export const UpdateJobPage = (props) => { + const store = useStore(props) + const seoConfig = articleUpdateSEO() + + return ( + + + + + + ) +} + +export default UpdateJobPage From de1abacd2a8d082ab873848d9d5f6e5c51d9b215 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 2 Nov 2021 21:41:15 +0800 Subject: [PATCH 06/11] refactor(article-workflow): Archived UI --- .../ArticleDigest/DesktopView/JobLayout.tsx | 3 -- .../ArticleDigest/DesktopView/PostLayout.tsx | 2 +- src/containers/editor/ArticleEditor/index.tsx | 8 ++++ src/containers/editor/ArticleEditor/store.ts | 9 ++++- src/spec/utils.ts | 1 + src/stores/Model/helper/article.ts | 6 +-- src/widgets/ArchiveAlert/index.tsx | 32 +++++++++++++++ src/widgets/ArchiveAlert/styles/index.ts | 12 ++++++ src/widgets/ArchiveAlert/tests/index.test.ts | 10 +++++ src/widgets/ArchivedSign/DetailPanel.tsx | 15 +++---- src/widgets/ArchivedSign/RealArchivedSign.tsx | 39 ------------------- src/widgets/ArchivedSign/index.tsx | 23 ++++++++--- .../ArchivedSign/styles/detail_panel.ts | 6 +-- src/widgets/ArchivedSign/styles/index.ts | 4 +- src/widgets/Buttons/SubmitButton.tsx | 19 ++++++++- src/widgets/Icons/Archived.tsx | 18 +++++++++ src/widgets/Icons/Info.tsx | 18 +++++++++ src/widgets/Icons/Light.tsx | 18 +++++++++ src/widgets/Icons/Notice.tsx | 18 +++++++++ src/widgets/NoticeBar/Icon.tsx | 22 +++++++---- src/widgets/NoticeBar/constant.ts | 11 ++++++ src/widgets/NoticeBar/index.tsx | 6 ++- src/widgets/NoticeBar/spec.ts | 1 + src/widgets/NoticeBar/styles/icon.ts | 27 ++++++++----- src/widgets/dynamic.tsx | 8 ++++ 25 files changed, 246 insertions(+), 90 deletions(-) create mode 100755 src/widgets/ArchiveAlert/index.tsx create mode 100755 src/widgets/ArchiveAlert/styles/index.ts create mode 100755 src/widgets/ArchiveAlert/tests/index.test.ts delete mode 100755 src/widgets/ArchivedSign/RealArchivedSign.tsx create mode 100644 src/widgets/Icons/Archived.tsx create mode 100644 src/widgets/Icons/Info.tsx create mode 100644 src/widgets/Icons/Light.tsx create mode 100644 src/widgets/Icons/Notice.tsx create mode 100644 src/widgets/NoticeBar/constant.ts create mode 100644 src/widgets/NoticeBar/spec.ts diff --git a/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx b/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx index fe84d6407..ce8ab7017 100644 --- a/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx +++ b/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx @@ -38,9 +38,6 @@ type TProps = { } const JobLayout: FC = ({ metric = METRIC.ARTICLE, article }) => { - log('company: ', article.company) - log('companyLink: ', article.companyLink) - return (
    diff --git a/src/containers/digest/ArticleDigest/DesktopView/PostLayout.tsx b/src/containers/digest/ArticleDigest/DesktopView/PostLayout.tsx index 5a2b11b42..cabbd6478 100644 --- a/src/containers/digest/ArticleDigest/DesktopView/PostLayout.tsx +++ b/src/containers/digest/ArticleDigest/DesktopView/PostLayout.tsx @@ -8,11 +8,11 @@ import type { TPost, TMetric } from '@/spec' import { METRIC } from '@/constant' import { buildLog } from '@/utils/logger' +import { ArchivedSign } from '@/widgets/dynamic' import { SpaceGrow } from '@/widgets/Common' import ArticleBaseStats from '@/widgets/ArticleBaseStats' import ArticleBelongCommunity from '@/widgets/ArticleBelongCommunity' import DotDivider from '@/widgets/DotDivider' -import ArchivedSign from '@/widgets/ArchivedSign' import ArticleMenu from '@/widgets/ArticleMenu' import ReadableDate from '@/widgets/ReadableDate' diff --git a/src/containers/editor/ArticleEditor/index.tsx b/src/containers/editor/ArticleEditor/index.tsx index af5ca3327..7ce3896bc 100755 --- a/src/containers/editor/ArticleEditor/index.tsx +++ b/src/containers/editor/ArticleEditor/index.tsx @@ -10,6 +10,8 @@ import { METRIC } from '@/constant' import { buildLog } from '@/utils/logger' import { pluggedIn } from '@/utils/mobx' +import { ArchiveAlert } from '@/widgets/dynamic' + import CommunityTagSetter from '@/containers/tool/CommunityTagSetter' import RichEditor from '@/containers/editor/RichEditor' import CommunityBadgeSelector from '@/widgets/CommunityBadgeSelector' @@ -42,6 +44,8 @@ const ArticleEditorContainer: FC = ({ }) => { useInit(store) const { + isArchived, + archivedAt, mode, communityData, submitState, @@ -66,6 +70,10 @@ const ArticleEditorContainer: FC = ({ /> )} + {isArchived && ( + + )} + {initEditor && ( ({ @@ -146,10 +149,14 @@ const ArticleEditor = T.model('ArticleEditor', { company, // @ts-ignore companyLink, + isArchived, + archivedAt, } = article self.title = title self.copyRight = copyRight + self.isArchived = isArchived + self.archivedAt = archivedAt if (document?.body) self.body = document.body diff --git a/src/spec/utils.ts b/src/spec/utils.ts index db43b3fe3..8667817ac 100644 --- a/src/spec/utils.ts +++ b/src/spec/utils.ts @@ -125,6 +125,7 @@ export type TSubmitState = { publishing?: boolean publishDone?: boolean isReady?: boolean + isArchived?: boolean stepReady?: boolean[] } diff --git a/src/stores/Model/helper/article.ts b/src/stores/Model/helper/article.ts index 4ddc873bf..76bf673c7 100755 --- a/src/stores/Model/helper/article.ts +++ b/src/stores/Model/helper/article.ts @@ -13,9 +13,6 @@ const ArticleMeta = T.model('ArticleMeta', { lastActiveAt: T.optional(T.string, ''), citingCount: T.optional(T.number, 0), latestUpvotedUsers: T.optional(T.array(SimpleUser), []), - - archivedAt: T.optional(T.string, ''), - isArchived: T.optional(T.boolean, false), }) const Document = T.model('ArticleMeta', { @@ -66,6 +63,9 @@ export const articleFields = () => { insertedAt: T.optional(T.string, ''), updatedAt: T.optional(T.string, ''), activeAt: T.optional(T.string, ''), + + isArchived: T.optional(T.boolean, false), + archivedAt: T.optional(T.string, ''), } } diff --git a/src/widgets/ArchiveAlert/index.tsx b/src/widgets/ArchiveAlert/index.tsx new file mode 100755 index 000000000..482c0be4f --- /dev/null +++ b/src/widgets/ArchiveAlert/index.tsx @@ -0,0 +1,32 @@ +/* + * + * ArchiveAlert + * + */ + +import { FC, memo } from 'react' + +import type { TSpace } from '@/spec' + +import NoticeBar from '@/widgets/NoticeBar' +import { buildLog } from '@/utils/logger' + +/* eslint-disable-next-line */ +const log = buildLog('c:ArchiveAlert:index') + +type TProps = { + date?: string +} & TSpace + +const ArchiveAlert: FC = ({ date = '', ...restProps }) => { + const dateString = new Date(date).toLocaleString() + return ( + + ) +} + +export default memo(ArchiveAlert) diff --git a/src/widgets/ArchiveAlert/styles/index.ts b/src/widgets/ArchiveAlert/styles/index.ts new file mode 100755 index 000000000..74680fc67 --- /dev/null +++ b/src/widgets/ArchiveAlert/styles/index.ts @@ -0,0 +1,12 @@ +import styled from 'styled-components' + +import type { TTestable } from '@/spec' + +// import Img from '@/Img' +// import { theme } from '@/utils/themes' + +export const Wrapper = styled.div.attrs(({ testid }: TTestable) => ({ + 'data-test-id': testid, +}))`` + +export const Title = styled.div`` diff --git a/src/widgets/ArchiveAlert/tests/index.test.ts b/src/widgets/ArchiveAlert/tests/index.test.ts new file mode 100755 index 000000000..7772f43d1 --- /dev/null +++ b/src/widgets/ArchiveAlert/tests/index.test.ts @@ -0,0 +1,10 @@ +// import React from 'react' +// import { shallow } from 'enzyme' + +// import ArchiveAlert from '../index' + +describe('TODO ', () => { + it('Expect to have unit tests specified', () => { + expect(true).toEqual(true) + }) +}) diff --git a/src/widgets/ArchivedSign/DetailPanel.tsx b/src/widgets/ArchivedSign/DetailPanel.tsx index d53e7a607..704431827 100644 --- a/src/widgets/ArchivedSign/DetailPanel.tsx +++ b/src/widgets/ArchivedSign/DetailPanel.tsx @@ -1,23 +1,20 @@ import { FC, memo } from 'react' -import Button from '@/widgets/Buttons/Button' -import { Wrapper, Title, Text, LinksWrapper } from './styles/detail_panel' +import { Wrapper, Title, Text } from './styles/detail_panel' type TProps = { date: string } const DetailPanel: FC = ({ date }) => { + const dateString = new Date(date).toLocaleString() + return ( - 本帖已于 {date} 存档 - 存档后无法编辑,删除及讨论。 + 本帖已于 {dateString} 存档 + 存档后为只读, 无法编辑,删除。 - - - + {/* 什么是存档? */} ) } diff --git a/src/widgets/ArchivedSign/RealArchivedSign.tsx b/src/widgets/ArchivedSign/RealArchivedSign.tsx deleted file mode 100755 index 15944e794..000000000 --- a/src/widgets/ArchivedSign/RealArchivedSign.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * - * ArchivedSign - * - */ - -import { FC, memo } from 'react' - -import { ICON } from '@/config' -import { buildLog } from '@/utils/logger' -import Tooltip from '@/widgets/Tooltip' - -import DetailPanel from './DetailPanel' -import { Wrapper, SignIcon, Text } from './styles' - -/* eslint-disable-next-line */ -const log = buildLog('c:ArchivedSign:index') - -type TProps = { - testid?: string - date?: string -} - -const ArchivedSign: FC = ({ testid = 'archived-sign', date }) => { - return ( - } - delay={500} - > - - - 已存档 - - - ) -} - -export default memo(ArchivedSign) diff --git a/src/widgets/ArchivedSign/index.tsx b/src/widgets/ArchivedSign/index.tsx index d8f249c9a..f133c2018 100755 --- a/src/widgets/ArchivedSign/index.tsx +++ b/src/widgets/ArchivedSign/index.tsx @@ -3,19 +3,30 @@ */ import { FC, memo } from 'react' -import dynamic from 'next/dynamic' + +import Tooltip from '@/widgets/Tooltip' + +import DetailPanel from './DetailPanel' +import { Wrapper, SignIcon, Text } from './styles' type TProps = { testid?: string date?: string } -const RealArchivedSign = dynamic(() => import('./RealArchivedSign'), { - ssr: false, -}) - const ArchivedSign: FC = ({ testid = 'archived-sign', date }) => { - return + return ( + } + delay={500} + > + + + 已存档 + + + ) } export default memo(ArchivedSign) diff --git a/src/widgets/ArchivedSign/styles/detail_panel.ts b/src/widgets/ArchivedSign/styles/detail_panel.ts index 9eb84db25..38918aa39 100755 --- a/src/widgets/ArchivedSign/styles/detail_panel.ts +++ b/src/widgets/ArchivedSign/styles/detail_panel.ts @@ -5,11 +5,7 @@ import css from '@/utils/css' export const Wrapper = styled.div` ${css.flexColumn()}; - width: 200px; - /* border: 1px solid; - border-color: #00424f; - padding: 0 8px; - border-radius: 4px; */ + width: 230px; ` export const Title = styled.div` color: ${theme('thread.articleTitle')}; diff --git a/src/widgets/ArchivedSign/styles/index.ts b/src/widgets/ArchivedSign/styles/index.ts index 45f573fe2..d2bb328e5 100755 --- a/src/widgets/ArchivedSign/styles/index.ts +++ b/src/widgets/ArchivedSign/styles/index.ts @@ -2,9 +2,9 @@ import styled from 'styled-components' import type { TTestable } from '@/spec' -import Img from '@/Img' import { theme } from '@/utils/themes' import css from '@/utils/css' +import ArchivedSVG from '@/icons/Archived' export const Wrapper = styled.div.attrs(({ testid }: TTestable) => ({ 'data-test-id': testid, @@ -16,7 +16,7 @@ export const Wrapper = styled.div.attrs(({ testid }: TTestable) => ({ border-radius: 4px; cursor: default; ` -export const SignIcon = styled(Img)` +export const SignIcon = styled(ArchivedSVG)` ${css.size(12)}; fill: ${theme('thread.articleDigest')}; margin-right: 5px; diff --git a/src/widgets/Buttons/SubmitButton.tsx b/src/widgets/Buttons/SubmitButton.tsx index 0f1b2ba0c..78b4a0e1b 100644 --- a/src/widgets/Buttons/SubmitButton.tsx +++ b/src/widgets/Buttons/SubmitButton.tsx @@ -57,9 +57,24 @@ const SubmitButton: FC = ({ withCancel = false, onCancel = log, onPublish = log, - submitState = { publishing: false, publishDone: false, isReady: false }, + submitState = { + publishing: false, + publishDone: false, + isReady: false, + isArchived: false, + }, }) => { - const { publishDone } = submitState + const { publishDone, isArchived } = submitState + + if (isArchived) { + return ( +
    + +
    + ) + } return (
    diff --git a/src/widgets/Icons/Archived.tsx b/src/widgets/Icons/Archived.tsx new file mode 100644 index 000000000..582b3eaaa --- /dev/null +++ b/src/widgets/Icons/Archived.tsx @@ -0,0 +1,18 @@ +import { memo, SVGProps } from 'react' + +const Archived = (props: SVGProps) => { + return ( + + + + ) +} + +export default memo(Archived) diff --git a/src/widgets/Icons/Info.tsx b/src/widgets/Icons/Info.tsx new file mode 100644 index 000000000..e14cce6ca --- /dev/null +++ b/src/widgets/Icons/Info.tsx @@ -0,0 +1,18 @@ +import { memo, SVGProps } from 'react' + +const Info = (props: SVGProps) => { + return ( + + + + ) +} + +export default memo(Info) diff --git a/src/widgets/Icons/Light.tsx b/src/widgets/Icons/Light.tsx new file mode 100644 index 000000000..1b64dcf5f --- /dev/null +++ b/src/widgets/Icons/Light.tsx @@ -0,0 +1,18 @@ +import { memo, SVGProps } from 'react' + +const Light = (props: SVGProps) => { + return ( + + + + ) +} + +export default memo(Light) diff --git a/src/widgets/Icons/Notice.tsx b/src/widgets/Icons/Notice.tsx new file mode 100644 index 000000000..4e638b476 --- /dev/null +++ b/src/widgets/Icons/Notice.tsx @@ -0,0 +1,18 @@ +import { memo, SVGProps } from 'react' + +const Notice = (props: SVGProps) => { + return ( + + + + ) +} + +export default memo(Notice) diff --git a/src/widgets/NoticeBar/Icon.tsx b/src/widgets/NoticeBar/Icon.tsx index a308133b7..dca7c87d7 100644 --- a/src/widgets/NoticeBar/Icon.tsx +++ b/src/widgets/NoticeBar/Icon.tsx @@ -1,22 +1,28 @@ import { FC, memo } from 'react' import { ICON } from '@/config' -import { LockIcon, NoticeIcon, InfoIcon } from './styles/icon' +import { LockIcon, ArchivedIcon, NoticeIcon, InfoIcon } from './styles/icon' + +import type { TType } from './spec' +import { TYPE } from './constant' type TProps = { - type?: 'lock' | 'notice' | 'bot' | 'info' + type: TType } -const Icon: FC = ({ type = 'notice' }) => { +const Icon: FC = ({ type }) => { switch (type) { - case 'lock': { - return + case TYPE.ARCHIVED: { + return + } + case TYPE.LOCK: { + return } - case 'info': { - return + case TYPE.INFO: { + return } default: { - return + return } } } diff --git a/src/widgets/NoticeBar/constant.ts b/src/widgets/NoticeBar/constant.ts new file mode 100644 index 000000000..2d7530c47 --- /dev/null +++ b/src/widgets/NoticeBar/constant.ts @@ -0,0 +1,11 @@ +import type { TType } from './spec' + +export const TYPE = { + LOCK: 'lock' as TType, + ARCHIVED: 'archived' as TType, + NOTICE: 'notice' as TType, + BOT: 'bot' as TType, + INFO: 'info' as TType, +} + +export const holder = 1 diff --git a/src/widgets/NoticeBar/index.tsx b/src/widgets/NoticeBar/index.tsx index 12b9013eb..9e3f97b34 100755 --- a/src/widgets/NoticeBar/index.tsx +++ b/src/widgets/NoticeBar/index.tsx @@ -14,6 +14,8 @@ import { ICON } from '@/config' import Icon from './Icon' +import type { TType } from './spec' +import { TYPE } from './constant' import { Wrapper, Main, UserName, AuthorTag, Timestamp, Why } from './styles' /* eslint-disable-next-line */ @@ -21,7 +23,7 @@ const log = buildLog('c:NoticeBar:index') type TProps = { testid?: string - type?: 'lock' | 'notice' | 'bot' | 'info' + type?: TType user?: { nickname: string } | null @@ -34,7 +36,7 @@ type TProps = { const NoticeBar: FC = ({ testid = 'notice-bar', - type = 'notice', + type = TYPE.NOTICE, user = null, isArticleAuthor = false, content, diff --git a/src/widgets/NoticeBar/spec.ts b/src/widgets/NoticeBar/spec.ts new file mode 100644 index 000000000..a3e27f360 --- /dev/null +++ b/src/widgets/NoticeBar/spec.ts @@ -0,0 +1 @@ +export type TType = 'lock' | 'archived' | 'notice' | 'bot' | 'info' diff --git a/src/widgets/NoticeBar/styles/icon.ts b/src/widgets/NoticeBar/styles/icon.ts index 91c817dbd..de8cef971 100644 --- a/src/widgets/NoticeBar/styles/icon.ts +++ b/src/widgets/NoticeBar/styles/icon.ts @@ -1,22 +1,31 @@ import styled from 'styled-components' -// import type { TTestable } from '@/spec' -import Img from '@/Img' import { theme } from '@/utils/themes' import css from '@/utils/css' -const Icon = styled(Img)` - fill: #a57a32; // ${theme('thread.articleTitle')}; +import ArchivedSVG from '@/icons/Archived' +import LockSVG from '@/icons/Lock' +import NoticeSVG from '@/icons/Notice' +import LightSVG from '@/icons/Light' + +const baseIcon = ` ${css.size(15)}; margin-right: 12px; margin-top: 5px; ` -export const LockIcon = styled(Icon)` - fill: #a57a32; // ${theme('thread.articleTitle')}; +export const LockIcon = styled(LockSVG)` + fill: #a57a32; + ${baseIcon}; ` -export const NoticeIcon = styled(Icon)` - fill: #a57a32; // ${theme('thread.articleTitle')}; +export const NoticeIcon = styled(NoticeSVG)` + fill: #a57a32; + ${baseIcon}; ` -export const InfoIcon = styled(Icon)` +export const InfoIcon = styled(LightSVG)` fill: ${theme('button.primary')}; + ${baseIcon}; +` +export const ArchivedIcon = styled(ArchivedSVG)` + fill: #a57a32; + ${baseIcon}; ` diff --git a/src/widgets/dynamic.tsx b/src/widgets/dynamic.tsx index 8068a07ca..23ee64903 100644 --- a/src/widgets/dynamic.tsx +++ b/src/widgets/dynamic.tsx @@ -14,3 +14,11 @@ export const LavaLampLoading = dynamic( ssr: false, }, ) + +export const ArchiveAlert = dynamic(() => import('./ArchiveAlert'), { + ssr: false, +}) + +export const ArchivedSign = dynamic(() => import('./ArchivedSign'), { + ssr: false, +}) From 202bf86773f7606d441b5c4d254e9dfdee7437a0 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 2 Nov 2021 21:57:24 +0800 Subject: [PATCH 07/11] refactor(post-workflow): reprint update check --- .../editor/ArticleEditor/AddOn/PostAddOn.tsx | 18 ++++++++++++++++-- src/containers/editor/ArticleEditor/store.ts | 2 +- src/pages/publish/job.tsx | 2 +- src/pages/publish/post.tsx | 5 ++++- src/stores/Model/helper/article.ts | 2 +- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx b/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx index 95b12291c..546468558 100644 --- a/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx +++ b/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx @@ -1,4 +1,4 @@ -import { FC, memo, useState } from 'react' +import { FC, memo, useState, useEffect } from 'react' import { isURL } from '@/utils/validator' import Checker from '@/widgets/Checker' @@ -20,9 +20,23 @@ const PostAddOn: FC = ({ editData }) => { const [reprint, setReprint] = useState(false) const [invalid, setInvalid] = useState(false) + useEffect(() => { + if (!!editData.linkAddr) { + setReprint(true) + } + }, []) + return ( - + { + setReprint(checked) + if (!checked) editOnChange('', 'linkAddr') + }} + > 转载 / 翻译 diff --git a/src/containers/editor/ArticleEditor/store.ts b/src/containers/editor/ArticleEditor/store.ts index 18f4ae374..255a758e2 100755 --- a/src/containers/editor/ArticleEditor/store.ts +++ b/src/containers/editor/ArticleEditor/store.ts @@ -25,7 +25,7 @@ import type { TTexts, TEditData } from './spec' const ArticleEditor = T.model('ArticleEditor', { mode: T.optional(T.enumeration(['publish', 'update']), 'publish'), isArchived: T.optional(T.boolean, false), - archivedAt: T.optional(T.string, ''), + archivedAt: T.maybeNull(T.string), title: T.optional(T.string, ''), body: T.optional(T.string, '{}'), diff --git a/src/pages/publish/job.tsx b/src/pages/publish/job.tsx index 9c013de71..80cbbe50e 100755 --- a/src/pages/publish/job.tsx +++ b/src/pages/publish/job.tsx @@ -43,7 +43,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { const initProps = { ...ssrBaseStates(resp), viewing: { - activeThread: ARTICLE_THREAD.JOB, + viewingThread: ARTICLE_THREAD.JOB, }, } diff --git a/src/pages/publish/post.tsx b/src/pages/publish/post.tsx index 121cb3038..d3f15ae24 100755 --- a/src/pages/publish/post.tsx +++ b/src/pages/publish/post.tsx @@ -1,6 +1,6 @@ import { GetServerSideProps } from 'next' import { Provider } from 'mobx-react' -import { METRIC } from '@/constant' +import { METRIC, ARTICLE_THREAD } from '@/constant' import { articlePublishSEO, ssrFetchPrepare, @@ -42,6 +42,9 @@ export const getServerSideProps: GetServerSideProps = async (context) => { const initProps = { ...ssrBaseStates(resp), + viewing: { + viewingThread: ARTICLE_THREAD.POST, + }, } return { props: { errorCode: null, ...initProps } } diff --git a/src/stores/Model/helper/article.ts b/src/stores/Model/helper/article.ts index 76bf673c7..5be31a387 100755 --- a/src/stores/Model/helper/article.ts +++ b/src/stores/Model/helper/article.ts @@ -65,7 +65,7 @@ export const articleFields = () => { activeAt: T.optional(T.string, ''), isArchived: T.optional(T.boolean, false), - archivedAt: T.optional(T.string, ''), + archivedAt: T.maybeNull(T.string), } } From 0ef60c3321cc85a1d927815a2bcaa05f9513e2ce Mon Sep 17 00:00:00 2001 From: mydearxym Date: Wed, 3 Nov 2021 10:17:42 +0800 Subject: [PATCH 08/11] refactor(radar-workflow): read done --- server/routes.js | 15 ++++ .../{PostLayout.tsx => ArticleLayout.tsx} | 12 ++- .../DesktopView/BlogLayout/index.tsx | 7 +- .../DesktopView/WorksLayout/index.tsx | 8 +- .../ArticleContent/DesktopView/index.tsx | 4 +- .../{post_layout.ts => article_layout.ts} | 0 .../ArticleEditor/PublishRules/PostRules.tsx | 5 +- .../ArticleEditor/styles/publish_rules.ts | 6 ++ src/pages/radar/[id].tsx | 83 +++++++++++++++++++ src/widgets/ArticleCard/Header.tsx | 18 ++-- src/widgets/ArticleCard/styles/header.ts | 9 +- 11 files changed, 144 insertions(+), 23 deletions(-) rename src/containers/content/ArticleContent/DesktopView/{PostLayout.tsx => ArticleLayout.tsx} (83%) rename src/containers/content/ArticleContent/styles/desktop_view/{post_layout.ts => article_layout.ts} (100%) create mode 100755 src/pages/radar/[id].tsx diff --git a/server/routes.js b/server/routes.js index 27f2cc827..d4444b68d 100644 --- a/server/routes.js +++ b/server/routes.js @@ -100,6 +100,10 @@ router.route('/blog/:id').get((req, res) => { return renderAndCache({ req, res, path: `/blog/${id}` }) }) +router.route('/radar/:id').get((req, res) => { + const { id } = req.params + return renderAndCache({ req, res, path: `/radar/${id}` }) +}) // repo 帖子页 // router.route('/:community/repo/:id').get((req, res) => { // return renderAndCache({ req, res, path: '/repo' }) @@ -153,6 +157,17 @@ router.route('/update/works/:id').get((req, res) => { return renderAndCache({ req, res, path: `/update/works/${id}` }) }) +// 创建新雷达 +router.route('/publish/radar').get((req, res) => { + return renderAndCache({ req, res, page: '/publish/radar' }) +}) + +// 编辑雷达 +router.route('/update/radar/:id').get((req, res) => { + const { id } = req.params + return renderAndCache({ req, res, path: `/update/radar/${id}` }) +}) + // 所有社区 router.route('/explore').get((req, res) => res.redirect('/explore/pl')) diff --git a/src/containers/content/ArticleContent/DesktopView/PostLayout.tsx b/src/containers/content/ArticleContent/DesktopView/ArticleLayout.tsx similarity index 83% rename from src/containers/content/ArticleContent/DesktopView/PostLayout.tsx rename to src/containers/content/ArticleContent/DesktopView/ArticleLayout.tsx index 75a4ac68e..dd56ec6d5 100644 --- a/src/containers/content/ArticleContent/DesktopView/PostLayout.tsx +++ b/src/containers/content/ArticleContent/DesktopView/ArticleLayout.tsx @@ -13,6 +13,7 @@ import { pluggedIn } from '@/utils/mobx' import { ArticleFooter, Comments } from '@/containers/dynamic' import ArticleSticker from '@/containers/tool/ArticleSticker' import ArtimentBody from '@/widgets/ArtimentBody' +import Linker from '@/widgets/Linker' import ViewportTracker from '@/widgets/ViewportTracker' @@ -25,7 +26,7 @@ import { SidebarWrapper, ArticleWrapper, CommentsWrapper, -} from '../styles/desktop_view/post_layout' +} from '../styles/desktop_view/article_layout' import { useInit, checkAnchor } from '../logic' @@ -45,10 +46,10 @@ const ArticleContentContainer: FC = ({ }) => { useInit(store) - const { viewingArticle } = store + const { viewingArticle: article } = store const ref = useRef() - if (!viewingArticle.id) return null + if (!article.id) return null return ( @@ -59,7 +60,10 @@ const ArticleContentContainer: FC = ({ /> - + {!!article.linkAddr && ( + + )} + diff --git a/src/containers/content/ArticleContent/DesktopView/BlogLayout/index.tsx b/src/containers/content/ArticleContent/DesktopView/BlogLayout/index.tsx index 35fb6473b..62c95a8b5 100644 --- a/src/containers/content/ArticleContent/DesktopView/BlogLayout/index.tsx +++ b/src/containers/content/ArticleContent/DesktopView/BlogLayout/index.tsx @@ -38,8 +38,9 @@ const ArticleContentContainer: FC = ({ }) => { useInit(store) - const { viewingArticle, articleTab, blogRssInfoData } = store - if (!viewingArticle.id) return null + const { viewingArticle: article, articleTab, blogRssInfoData } = store + if (!article.id) return null + if (articleTab === BLOG_TAB.FEEDS) { return ( @@ -69,7 +70,7 @@ const ArticleContentContainer: FC = ({ return ( - + diff --git a/src/containers/content/ArticleContent/DesktopView/WorksLayout/index.tsx b/src/containers/content/ArticleContent/DesktopView/WorksLayout/index.tsx index 34a44ec57..d7c0bb328 100644 --- a/src/containers/content/ArticleContent/DesktopView/WorksLayout/index.tsx +++ b/src/containers/content/ArticleContent/DesktopView/WorksLayout/index.tsx @@ -37,14 +37,14 @@ const ArticleContentContainer: FC = ({ }) => { useInit(store) - const { viewingArticle, articleTab } = store - if (!viewingArticle.id) return null + const { viewingArticle: works, articleTab } = store + if (!works.id) return null if (articleTab === WORKS_TAB.TECHSTACKS) { return ( - + @@ -56,7 +56,7 @@ const ArticleContentContainer: FC = ({ return ( - + diff --git a/src/containers/content/ArticleContent/DesktopView/index.tsx b/src/containers/content/ArticleContent/DesktopView/index.tsx index de0eba2a5..73a664838 100644 --- a/src/containers/content/ArticleContent/DesktopView/index.tsx +++ b/src/containers/content/ArticleContent/DesktopView/index.tsx @@ -1,6 +1,6 @@ import { METRIC } from '@/constant' -import PostLayout from './PostLayout' +import ArticleLayout from './ArticleLayout' import BlogLayout from './BlogLayout' import WorksLayout from './WorksLayout' @@ -15,7 +15,7 @@ const ArticleContent = (props) => { return } default: { - return + return } } } diff --git a/src/containers/content/ArticleContent/styles/desktop_view/post_layout.ts b/src/containers/content/ArticleContent/styles/desktop_view/article_layout.ts similarity index 100% rename from src/containers/content/ArticleContent/styles/desktop_view/post_layout.ts rename to src/containers/content/ArticleContent/styles/desktop_view/article_layout.ts diff --git a/src/containers/editor/ArticleEditor/PublishRules/PostRules.tsx b/src/containers/editor/ArticleEditor/PublishRules/PostRules.tsx index 27f6b3f84..16885fa9b 100644 --- a/src/containers/editor/ArticleEditor/PublishRules/PostRules.tsx +++ b/src/containers/editor/ArticleEditor/PublishRules/PostRules.tsx @@ -20,10 +20,9 @@ const PublishRules: FC = () => {
  • 卖课,领资料,公众号引流等一月禁言起步。多次发布,停止服务。
  • 请尊重自己和他人的时间,不要发布无意义的烂梗 / 黑话, 以及排版糟糕的 - Trash Talk + Trash Talk.
  • -
  • 严禁侵权,勿议国是,不搞沸腾。
  • -
  • 如有其他疑问或建议反馈,请发布到 /feetback#publish。
  • +
  • 严禁侵权,勿议国是,尊重事实,不搞沸腾。
  • ) diff --git a/src/containers/editor/ArticleEditor/styles/publish_rules.ts b/src/containers/editor/ArticleEditor/styles/publish_rules.ts index 971a3ea63..11c338b46 100644 --- a/src/containers/editor/ArticleEditor/styles/publish_rules.ts +++ b/src/containers/editor/ArticleEditor/styles/publish_rules.ts @@ -29,3 +29,9 @@ export const Ul = styled.ul` export const Li = styled.li` margin-bottom: 8px; ` +export const Footer = styled.div` + font-size: 12px; + color: ${theme('thread.articleTitle')}; + opacity: 0.6; + margin-top: 10px; +` diff --git a/src/pages/radar/[id].tsx b/src/pages/radar/[id].tsx new file mode 100755 index 000000000..5ac8b40b8 --- /dev/null +++ b/src/pages/radar/[id].tsx @@ -0,0 +1,83 @@ +import { Provider } from 'mobx-react' + +import { ARTICLE_THREAD, METRIC } from '@/constant' +import { + ssrBaseStates, + ssrFetchPrepare, + ssrError, + articleSEO, + ssrGetParam, + refreshIfneed, +} from '@/utils' +import { useStore } from '@/stores/init' + +import GlobalLayout from '@/containers/layout/GlobalLayout' +import ArticleDigest from '@/containers/digest/ArticleDigest' +import ArticleContent from '@/containers/content/ArticleContent' + +import { P } from '@/schemas' + +const fetchData = async (context, opt = {}) => { + const id = ssrGetParam(context, 'id') + const { gqClient, userHasLogin } = ssrFetchPrepare(context, opt) + + // query data + const sessionState = gqClient.request(P.sessionState) + const radar = gqClient.request(P.radar, { id, userHasLogin }) + + const subscribedCommunities = gqClient.request(P.subscribedCommunities, { + filter: { + page: 1, + size: 30, + }, + }) + + return { + ...(await sessionState), + ...(await radar), + ...(await subscribedCommunities), + } +} + +export const getServerSideProps = async (context) => { + let resp + try { + resp = await fetchData(context) + const { radar, sessionState } = resp + refreshIfneed(sessionState, `/radar/${radar.id}`, context) + } catch (e) { + console.log('#### error from server: ', e) + return ssrError(context, 'fetch', 500) + } + + const { radar } = resp + + const initProps = { + ...ssrBaseStates(resp), + viewing: { + radar, + activeThread: ARTICLE_THREAD.RADAR, + }, + } + + return { props: { errorCode: null, ...initProps } } +} + +const RadarPage = (props) => { + const store = useStore(props) + const { viewing } = props + const { radar } = viewing + + const seoConfig = articleSEO(ARTICLE_THREAD.RADAR, radar) + + return ( + + + + + + + ) +} + +export default RadarPage diff --git a/src/widgets/ArticleCard/Header.tsx b/src/widgets/ArticleCard/Header.tsx index 7bd53c9db..c302e21f6 100644 --- a/src/widgets/ArticleCard/Header.tsx +++ b/src/widgets/ArticleCard/Header.tsx @@ -1,8 +1,9 @@ import { FC, memo } from 'react' +import Link from 'next/link' import type { TJob, TRadar } from '@/spec' import { ICON } from '@/config' -import { THREAD } from '@/constant' +import { ARTICLE_THREAD } from '@/constant' import { cutRest } from '@/utils/helper' import TagsList from '@/widgets/TagsList' import { Br } from '@/widgets/Common' @@ -20,8 +21,8 @@ import { const Header: FC = ({ data, thread }) => { switch (thread) { - case THREAD.RADAR: { - const { title, articleTags, linkAddr } = data as TRadar + case ARTICLE_THREAD.RADAR: { + const { id, title, articleTags, linkAddr } = data as TRadar return ( @@ -31,7 +32,9 @@ const Header: FC = ({ data, thread }) => { {linkAddr}
    - {cutRest(title, 100)} + + {cutRest(title, 100)} +
    @@ -39,19 +42,22 @@ const Header: FC = ({ data, thread }) => { } default: { - const { title, articleTags, company, companyLink } = data as TJob + const { id, title, articleTags, company, companyLink } = data as TJob return (
    + <ExtraInfo> <CompanyLink href={companyLink} target="_blank"> {cutRest(company, 12)} </CompanyLink> </ExtraInfo> - {cutRest(title, 100)} + <Link href={`/${ARTICLE_THREAD.JOB}/${id}`} passHref> + {cutRest(title, 100)} + </Link>
    ) diff --git a/src/widgets/ArticleCard/styles/header.ts b/src/widgets/ArticleCard/styles/header.ts index 4e09e332b..6853a8728 100644 --- a/src/widgets/ArticleCard/styles/header.ts +++ b/src/widgets/ArticleCard/styles/header.ts @@ -27,11 +27,18 @@ export const LinkSrc = styled.a` cursor: pointer; } ` -export const Title = styled.div` +export const Title = styled.a` display: inline; color: ${theme('thread.articleTitle')}; font-size: 17px; cursor: pointer; + text-decoration: none; + + &:hover { + text-decoration: underline; + color: ${theme('thread.articleTitle')}; + } + ${css.media.mobile` ${css.cutRest('150px')}; `}; From 219380549ecd9c50089be53e7e6207fc2cb7672c Mon Sep 17 00:00:00 2001 From: mydearxym Date: Wed, 3 Nov 2021 12:18:09 +0800 Subject: [PATCH 09/11] refactor(radar-workflow): create & update done --- .../editor/ArticleEditor/AddOn/RadarAddOn.tsx | 46 +++++++++++ .../editor/ArticleEditor/AddOn/index.tsx | 7 +- .../ArticleEditor/PublishRules/RadarRules.tsx | 24 ++++++ .../ArticleEditor/PublishRules/index.tsx | 5 ++ src/containers/editor/ArticleEditor/logic.ts | 8 ++ src/containers/editor/ArticleEditor/schema.ts | 81 +++++++++++++++++++ src/containers/editor/ArticleEditor/store.ts | 23 +++++- .../ArticleEditor/styles/addon/post_addon.ts | 10 --- .../ArticleEditor/styles/addon/radar_addon.ts | 13 +++ src/pages/publish/radar.tsx | 63 +++++++++++++++ src/pages/update/radar/[id].tsx | 70 ++++++++++++++++ src/widgets/Input/styles/index.ts | 3 +- src/widgets/Navigator/DigestView.tsx | 2 +- 13 files changed, 339 insertions(+), 16 deletions(-) create mode 100644 src/containers/editor/ArticleEditor/AddOn/RadarAddOn.tsx create mode 100644 src/containers/editor/ArticleEditor/PublishRules/RadarRules.tsx create mode 100644 src/containers/editor/ArticleEditor/styles/addon/radar_addon.ts create mode 100755 src/pages/publish/radar.tsx create mode 100755 src/pages/update/radar/[id].tsx diff --git a/src/containers/editor/ArticleEditor/AddOn/RadarAddOn.tsx b/src/containers/editor/ArticleEditor/AddOn/RadarAddOn.tsx new file mode 100644 index 000000000..1f9ab560b --- /dev/null +++ b/src/containers/editor/ArticleEditor/AddOn/RadarAddOn.tsx @@ -0,0 +1,46 @@ +import { FC, memo, useState, useEffect } from 'react' + +import { isURL } from '@/utils/validator' +import Checker from '@/widgets/Checker' + +import type { TEditData } from '../spec' +import { + Wrapper, + LinkWrapper, + LinkInput, + LinkIcon, +} from '../styles/addon/radar_addon' +import { editOnChange } from '../logic' + +type TProps = { + editData: TEditData +} + +const RadarAddOn: FC = ({ editData }) => { + const [invalid, setInvalid] = useState(false) + + return ( + + + + { + if (!isURL(v.target.value)) { + setInvalid(true) + } else { + setInvalid(false) + } + + editOnChange(v, 'linkAddr') + }} + autoFocus + /> + + + ) +} + +export default memo(RadarAddOn) diff --git a/src/containers/editor/ArticleEditor/AddOn/index.tsx b/src/containers/editor/ArticleEditor/AddOn/index.tsx index 1ac727fc9..8e6ef10be 100644 --- a/src/containers/editor/ArticleEditor/AddOn/index.tsx +++ b/src/containers/editor/ArticleEditor/AddOn/index.tsx @@ -5,8 +5,9 @@ import { ARTICLE_THREAD } from '@/constant' import type { TEditData } from '../spec' -import JobAddOn from './JobAddOn' import PostAddOn from './PostAddOn' +import RadarAddOn from './RadarAddOn' +import JobAddOn from './JobAddOn' type TProps = { thread: TArticleThread @@ -18,7 +19,9 @@ const Addon: FC = ({ thread, editData }) => { case ARTICLE_THREAD.JOB: { return } - + case ARTICLE_THREAD.RADAR: { + return + } default: { return } diff --git a/src/containers/editor/ArticleEditor/PublishRules/RadarRules.tsx b/src/containers/editor/ArticleEditor/PublishRules/RadarRules.tsx new file mode 100644 index 000000000..ef564ece3 --- /dev/null +++ b/src/containers/editor/ArticleEditor/PublishRules/RadarRules.tsx @@ -0,0 +1,24 @@ +import { FC, memo } from 'react' + +import { Wrapper, Title, Ul, Li } from '../styles/publish_rules' + +const PublishRules: FC = () => { + return ( + +
      + 规则与边界 +
    • + 这里仅限发布技术动态,科技新闻,业界言论等资讯,相关资讯会被同步到关联的子社区中。 +
    • +
    • + 请勿对资讯内容进行全文转载或翻译,概括描述以及个人见解是推荐的方式。 +
    • +
    • 请尽量找到资讯源头,而不是几经传播的 N 手消息,并附上原始链接。
    • +
    • 发布卖课,培训等资讯会被一刀切,永久封号。
    • +
    • 请根据分享的内容选择合适的标签以及子社区。
    • +
    +
    + ) +} + +export default memo(PublishRules) diff --git a/src/containers/editor/ArticleEditor/PublishRules/index.tsx b/src/containers/editor/ArticleEditor/PublishRules/index.tsx index 99a59a4d8..1e7a38ff0 100644 --- a/src/containers/editor/ArticleEditor/PublishRules/index.tsx +++ b/src/containers/editor/ArticleEditor/PublishRules/index.tsx @@ -5,6 +5,7 @@ import { ARTICLE_THREAD } from '@/constant' import PostRules from './PostRules' import JobRules from './JobRules' +import RadarRules from './RadarRules' type TProps = { thread: TArticleThread @@ -16,6 +17,10 @@ const PublishRules: FC = ({ thread }) => { return } + case ARTICLE_THREAD.RADAR: { + return + } + default: { return } diff --git a/src/containers/editor/ArticleEditor/logic.ts b/src/containers/editor/ArticleEditor/logic.ts index 2320ec58c..9b0598ad4 100755 --- a/src/containers/editor/ArticleEditor/logic.ts +++ b/src/containers/editor/ArticleEditor/logic.ts @@ -126,6 +126,10 @@ const DataSolver = [ match: asyncRes('createJob'), action: handleMutateRes, }, + { + match: asyncRes('createRadar'), + action: handleMutateRes, + }, { match: asyncRes('updatePost'), action: handleMutateRes, @@ -134,6 +138,10 @@ const DataSolver = [ match: asyncRes('updateJob'), action: handleMutateRes, }, + { + match: asyncRes('updateRadar'), + action: handleMutateRes, + }, { match: asyncRes('community'), action: ({ community }) => store.mark({ community }), diff --git a/src/containers/editor/ArticleEditor/schema.ts b/src/containers/editor/ArticleEditor/schema.ts index 7ef0eade1..1d5697de6 100755 --- a/src/containers/editor/ArticleEditor/schema.ts +++ b/src/containers/editor/ArticleEditor/schema.ts @@ -32,6 +32,7 @@ const updatePost = gql` $id: ID! $title: String $body: String + $linkAddr: String $copyRight: String $articleTags: [Id] ) { @@ -39,6 +40,7 @@ const updatePost = gql` id: $id title: $title body: $body + linkAddr: $linkAddr copyRight: $copyRight articleTags: $articleTags ) { @@ -103,6 +105,54 @@ const updateJob = gql` } ` +// radar +const createRadar = gql` + mutation ( + $title: String! + $body: String + $linkAddr: String! + $communityId: ID! + $articleTags: [Id] + ) { + createRadar( + title: $title + body: $body + linkAddr: $linkAddr + communityId: $communityId + articleTags: $articleTags + ) { + id + title + meta { + thread + } + } + } +` +const updateRadar = gql` + mutation ( + $id: ID! + $title: String + $body: String + $linkAddr: String + $articleTags: [Id] + ) { + updateRadar( + id: $id + title: $title + body: $body + linkAddr: $linkAddr + articleTags: $articleTags + ) { + id + title + meta { + thread + } + } + } +` + // viewer_has_subscribed const community = gql` query ($raw: String) { @@ -174,13 +224,44 @@ const job = gql` } } ` +const radar = gql` + query radar($id: ID!) { + radar(id: $id) { + id + title + linkAddr + copyRight + archivedAt + isArchived + + originalCommunity { + ${F.community} + } + + articleTags { + ${F.tag} + } + + meta { + thread + } + + document { + body + } + } + } +` const schema = { post, job, + radar, createPost, updatePost, createJob, updateJob, + createRadar, + updateRadar, community, } diff --git a/src/containers/editor/ArticleEditor/store.ts b/src/containers/editor/ArticleEditor/store.ts index 255a758e2..78d70bfce 100755 --- a/src/containers/editor/ArticleEditor/store.ts +++ b/src/containers/editor/ArticleEditor/store.ts @@ -18,6 +18,7 @@ import type { import { ARTICLE_THREAD } from '@/constant' import { markStates, toJS } from '@/utils/mobx' +import { isURL } from '@/utils/validator' import { Community, Tag } from '@/model' import type { TTexts, TEditData } from './spec' @@ -81,6 +82,15 @@ const ArticleEditor = T.model('ArticleEditor', { } } + case ARTICLE_THREAD.RADAR: { + return { + holder: { + title: '// 消息标题', + body: "// 消息内容('Tab' 键插入富文本)", + }, + } + } + default: { return { holder: { @@ -102,6 +112,11 @@ const ArticleEditor = T.model('ArticleEditor', { break } + case ARTICLE_THREAD.RADAR: { + baseFields = ['title', 'body', 'copyRight', 'linkAddr'] + break + } + default: { baseFields = ['title', 'body', 'copyRight', 'isQuestion', 'linkAddr'] break @@ -112,8 +127,12 @@ const ArticleEditor = T.model('ArticleEditor', { }, get isReady(): boolean { const slf = self as TStore - const { wordsCountReady } = slf - const titleReady = slf.title.length > 0 + const { title, thread, wordsCountReady, linkAddr } = slf + const titleReady = title.length > 0 + + if (thread === ARTICLE_THREAD.RADAR) { + return wordsCountReady && titleReady && !!isURL(linkAddr, true) + } return wordsCountReady && titleReady }, diff --git a/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts b/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts index 9fefb8539..252937782 100644 --- a/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts +++ b/src/containers/editor/ArticleEditor/styles/addon/post_addon.ts @@ -1,8 +1,6 @@ import styled from 'styled-components' -import { theme } from '@/utils/themes' import css from '@/utils/css' -import animate from '@/utils/animations' export { LinkIcon, LinkInput } from './index' @@ -14,11 +12,3 @@ export const LinkWrapper = styled.div` position: relative; margin-left: 15px; ` -export const ErrorHint = styled.div` - position: absolute; - font-size: 12px; - right: -68px; - top: 4px; - color: ${theme('baseColor.red')}; - animation: ${animate.breath} 2.5s linear infinite; -` diff --git a/src/containers/editor/ArticleEditor/styles/addon/radar_addon.ts b/src/containers/editor/ArticleEditor/styles/addon/radar_addon.ts new file mode 100644 index 000000000..937646216 --- /dev/null +++ b/src/containers/editor/ArticleEditor/styles/addon/radar_addon.ts @@ -0,0 +1,13 @@ +import styled from 'styled-components' + +import css from '@/utils/css' + +export { LinkIcon, LinkInput } from './index' + +export const Wrapper = styled.div` + ${css.flex('align-center')}; +` +export const LinkWrapper = styled.div` + ${css.flex('align-center')}; + position: relative; +` diff --git a/src/pages/publish/radar.tsx b/src/pages/publish/radar.tsx new file mode 100755 index 000000000..fd4687af5 --- /dev/null +++ b/src/pages/publish/radar.tsx @@ -0,0 +1,63 @@ +import { GetServerSideProps } from 'next' +import { Provider } from 'mobx-react' +import { METRIC, ARTICLE_THREAD } from '@/constant' +import { + articlePublishSEO, + ssrFetchPrepare, + ssrBaseStates, + refreshIfneed, + ssrError, +} from '@/utils' +import { P } from '@/schemas' + +import { useStore } from '@/stores/init' +import GlobalLayout from '@/containers/layout/GlobalLayout' +import ArticleEditor from '@/containers/editor/ArticleEditor' + +const fetchData = async (context, opt = {}) => { + const { gqClient } = ssrFetchPrepare(context, opt) + const sessionState = gqClient.request(P.sessionState) + + return { + ...(await sessionState), + } +} + +export const getServerSideProps: GetServerSideProps = async (context) => { + let resp + try { + resp = await fetchData(context) + const { sessionState } = resp + refreshIfneed(sessionState, '/publish/blog', context) + } catch (e) { + return ssrError(context, 'fetch', 500) + } + + const initProps = { + ...ssrBaseStates(resp), + viewing: { + viewingThread: ARTICLE_THREAD.RADAR, + }, + } + + return { props: { errorCode: null, ...initProps } } +} + +export const PublishRadarPage = (props) => { + const store = useStore(props) + const seoConfig = articlePublishSEO() + + return ( + + + + + + ) +} + +export default PublishRadarPage diff --git a/src/pages/update/radar/[id].tsx b/src/pages/update/radar/[id].tsx new file mode 100755 index 000000000..ca638df3f --- /dev/null +++ b/src/pages/update/radar/[id].tsx @@ -0,0 +1,70 @@ +import { GetServerSideProps } from 'next' +import { Provider } from 'mobx-react' +import { METRIC, ARTICLE_THREAD } from '@/constant' +import { + articleUpdateSEO, + ssrBaseStates, + ssrRescue, + ssrFetchPrepare, + ssrGetParam, +} from '@/utils' +import { P } from '@/schemas' + +import { useStore } from '@/stores/init' +import GlobalLayout from '@/containers/layout/GlobalLayout' +import ArticleEditor from '@/containers/editor/ArticleEditor' + +const fetchData = async (context, opt = {}) => { + const { gqClient } = ssrFetchPrepare(context, opt) + + const sessionState = gqClient.request(P.sessionState) + + return { + ...(await sessionState), + } +} + +export const getServerSideProps: GetServerSideProps = async (context) => { + let resp + try { + resp = await fetchData(context) + } catch ({ response: { errors } }) { + if (ssrRescue.hasLoginError(errors)) { + resp = await fetchData(context, { tokenExpired: true }) + } + } + + const id = ssrGetParam(context, 'id') + + const initProps = { + ...ssrBaseStates(resp), + articleEditor: { + mode: 'update', + }, + viewing: { + radar: { id }, + viewingThread: ARTICLE_THREAD.RADAR, + }, + } + + return { props: { errorCode: null, ...initProps } } +} + +export const UpdateRadarPage = (props) => { + const store = useStore(props) + const seoConfig = articleUpdateSEO() + + return ( + + + + + + ) +} + +export default UpdateRadarPage diff --git a/src/widgets/Input/styles/index.ts b/src/widgets/Input/styles/index.ts index dc8b78fd9..7ed5a266a 100755 --- a/src/widgets/Input/styles/index.ts +++ b/src/widgets/Input/styles/index.ts @@ -73,7 +73,8 @@ export const InputWrapper = styled.input` background-color: #0b2631; border-color: ${theme('editor.border')}; ::placeholder { - color: ${theme('editor.placeholder')}; + color: ${theme('thread.articleDigest')}; + opacity: 0.5; } &:hover { border-color: ${theme('editor.borderActive')}; diff --git a/src/widgets/Navigator/DigestView.tsx b/src/widgets/Navigator/DigestView.tsx index fe1d593b9..28a8f014f 100755 --- a/src/widgets/Navigator/DigestView.tsx +++ b/src/widgets/Navigator/DigestView.tsx @@ -24,7 +24,7 @@ export const BlinkCursor = dynamic(() => import('@/widgets/BlinkCursor'), { const renderMainEntries = (metric) => { switch (metric) { case METRIC.ARTICLE_EDITOR: { - return 发布帖子 + return 发布内容 } default: { From 7a04259a6da84944e6192a6b0dedac2b145aabab Mon Sep 17 00:00:00 2001 From: mydearxym Date: Wed, 3 Nov 2021 12:23:05 +0800 Subject: [PATCH 10/11] refactor(radar-workflow): adjust card link icon --- src/widgets/ArticleCard/Header.tsx | 4 ++-- src/widgets/ArticleCard/styles/header.ts | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/widgets/ArticleCard/Header.tsx b/src/widgets/ArticleCard/Header.tsx index c302e21f6..2bf5d239f 100644 --- a/src/widgets/ArticleCard/Header.tsx +++ b/src/widgets/ArticleCard/Header.tsx @@ -27,8 +27,8 @@ const Header: FC = ({ data, thread }) => { return ( - {/* */} - + {/* */} + {linkAddr}
    diff --git a/src/widgets/ArticleCard/styles/header.ts b/src/widgets/ArticleCard/styles/header.ts index 6853a8728..f6c2cc36f 100644 --- a/src/widgets/ArticleCard/styles/header.ts +++ b/src/widgets/ArticleCard/styles/header.ts @@ -1,15 +1,16 @@ import styled from 'styled-components' import { theme } from '@/utils/themes' -import Img from '@/Img' +// import Img from '@/Img' import css from '@/utils/css' +import LinkSVG from '@/icons/Link' export const Wrapper = styled.div`` export const LinkWraper = styled.div` ${css.flex('align-center')}; ` -export const LinkIcon = styled(Img)` +export const LinkIcon = styled(LinkSVG)` fill: ${theme('thread.articleDigest')}; ${css.size(14)}; ` @@ -18,7 +19,7 @@ export const LinkSrc = styled.a` font-size: 13px; color: ${theme('thread.articleDigest')}; opacity: 0.8; - margin-left: 6px; + margin-left: 3px; &:hover { text-decoration: underline; From 0b655a4f409147bd47686db33fffaba0deafd248 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Wed, 3 Nov 2021 12:40:46 +0800 Subject: [PATCH 11/11] fix(tag-setter): community arg error --- src/containers/editor/ArticleEditor/Footer.tsx | 2 +- src/containers/tool/CommunityTagSetter/TagSetter/GroupTags.tsx | 3 ++- src/containers/tool/CommunityTagSetter/logic.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/containers/editor/ArticleEditor/Footer.tsx b/src/containers/editor/ArticleEditor/Footer.tsx index aa7c674e2..db073d0ca 100644 --- a/src/containers/editor/ArticleEditor/Footer.tsx +++ b/src/containers/editor/ArticleEditor/Footer.tsx @@ -48,7 +48,7 @@ const Footer: FC = ({ mLeft={0} size="medium" community={community} - thread={ARTICLE_THREAD.POST} + thread={thread} withSetter={mode === 'publish'} /> = ({ }) => { return ( - {folder} + {folder !== 'null' ? {folder} :
    } + {tags.map((tag) => ( { store.mark({ tagsLoading: true }) const args = { - filter: { communityId: community.raw, thread: thread.toUpperCase() }, + filter: { communityId: community.id, thread: thread.toUpperCase() }, } log('query tags args: ', args)