diff --git a/server/routes.js b/server/routes.js index 21e309008..d4444b68d 100644 --- a/server/routes.js +++ b/server/routes.js @@ -89,11 +89,21 @@ 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}` }) }) +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' }) @@ -109,12 +119,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' }) @@ -136,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/digest/ArticleDigest/DesktopView/JobLayout.tsx b/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx new file mode 100644 index 000000000..ce8ab7017 --- /dev/null +++ b/src/containers/digest/ArticleDigest/DesktopView/JobLayout.tsx @@ -0,0 +1,74 @@ +/* + * 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 }) => { + return ( + +
+
+ + + + {article.isArchived && ( + + + + + )} + + +
+ {article.title} + + + + {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/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/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/AddOn/JobAddOn.tsx b/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx new file mode 100644 index 000000000..a072f09d1 --- /dev/null +++ b/src/containers/editor/ArticleEditor/AddOn/JobAddOn.tsx @@ -0,0 +1,49 @@ +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) + + return ( + + + editOnChange(v, 'company')} + /> + + { + if (!isURL(v.target.value)) { + setInvalid(true) + } else { + setInvalid(false) + } + + editOnChange(v, 'companyLink') + }} + /> + + ) +} + +export default memo(JobAddOn) diff --git a/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx b/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx new file mode 100644 index 000000000..546468558 --- /dev/null +++ b/src/containers/editor/ArticleEditor/AddOn/PostAddOn.tsx @@ -0,0 +1,67 @@ +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/post_addon' +import { editOnChange } from '../logic' + +type TProps = { + editData: TEditData +} + +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') + }} + > + 转载 / 翻译 + + + {reprint && ( + + + { + if (!isURL(v.target.value)) { + setInvalid(true) + } else { + setInvalid(false) + } + + editOnChange(v, 'linkAddr') + }} + autoFocus + /> + + )} + + ) +} + +export default memo(PostAddOn) 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 new file mode 100644 index 000000000..8e6ef10be --- /dev/null +++ b/src/containers/editor/ArticleEditor/AddOn/index.tsx @@ -0,0 +1,31 @@ +import { FC, memo } from 'react' + +import type { TArticleThread } from '@/spec' +import { ARTICLE_THREAD } from '@/constant' + +import type { TEditData } from '../spec' + +import PostAddOn from './PostAddOn' +import RadarAddOn from './RadarAddOn' +import JobAddOn from './JobAddOn' + +type TProps = { + thread: TArticleThread + editData: TEditData +} + +const Addon: FC = ({ thread, editData }) => { + switch (thread) { + case ARTICLE_THREAD.JOB: { + return + } + case ARTICLE_THREAD.RADAR: { + return + } + default: { + return + } + } +} + +export default memo(Addon) diff --git a/src/containers/editor/ArticleEditor/Footer.tsx b/src/containers/editor/ArticleEditor/Footer.tsx index 8865495a3..db073d0ca 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' @@ -17,28 +18,28 @@ 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 } const Footer: FC = ({ + thread, mode, - body, + editData, tags, - isQuestion, - copyRight, submitState, community, }) => { + const { body, isQuestion, copyRight } = editData return ( @@ -47,7 +48,7 @@ const Footer: FC = ({ mLeft={0} size="medium" community={community} - thread={THREAD.POST} + thread={thread} 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 75% rename from src/containers/editor/ArticleEditor/PublishRules.tsx rename to src/containers/editor/ArticleEditor/PublishRules/PostRules.tsx index 212e2b552..16885fa9b 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,10 +19,10 @@ const PublishRules: FC = () => {
  • 卖课,领资料,公众号引流等一月禁言起步。多次发布,停止服务。
  • - 请尊重自己和他人的时间,不要发布无意义的烂梗 / 黑话之类的 trash talk。 + 请尊重自己和他人的时间,不要发布无意义的烂梗 / 黑话, 以及排版糟糕的 + Trash Talk.
  • -
  • 严禁侵权,勿议国是,不搞沸腾。
  • -
  • 如有其他疑问或建议反馈,请发布到 /feetback#publish。
  • +
  • 严禁侵权,勿议国是,尊重事实,不搞沸腾。
) 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 new file mode 100644 index 000000000..1e7a38ff0 --- /dev/null +++ b/src/containers/editor/ArticleEditor/PublishRules/index.tsx @@ -0,0 +1,30 @@ +import { FC, memo } from 'react' + +import type { TArticleThread } from '@/spec' +import { ARTICLE_THREAD } from '@/constant' + +import PostRules from './PostRules' +import JobRules from './JobRules' +import RadarRules from './RadarRules' + +type TProps = { + thread: TArticleThread +} + +const PublishRules: FC = ({ thread }) => { + switch (thread) { + case ARTICLE_THREAD.JOB: { + return + } + + case ARTICLE_THREAD.RADAR: { + 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..7ce3896bc 100755 --- a/src/containers/editor/ArticleEditor/index.tsx +++ b/src/containers/editor/ArticleEditor/index.tsx @@ -4,17 +4,20 @@ import { FC } from 'react' -import type { TMetric, TEditMode } from '@/spec' +import type { TMetric } from '@/spec' 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' import TitleInput from './TitleInput' +import AddOn from './AddOn' import Footer from './Footer' import PublishRules from './PublishRules' @@ -32,26 +35,28 @@ 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 { - title, - body, - copyRight, - isQuestion, + isArchived, + archivedAt, + mode, communityData, submitState, tagsData, + texts, + thread, + editData, } = store + const { title, body } = editData + const initEditor = mode === 'publish' || body !== '{}' return ( @@ -65,27 +70,31 @@ const ArticleEditorContainer: FC = ({ /> )} - + {isArchived && ( + + )} + + {initEditor && ( editOnChange(JSON.stringify(v), 'body')} - onLinkChange={(v) => editOnChange(v, 'linkAddr')} + addon={} + placeholder={texts.holder.body} /> )}