import React , { useState , useEffect , useCallback , useContext , useRef } from 'react' ; import { Helmet } from 'react-helmet' ; import { NextPage } from 'next' ; import { default as Router } from 'next/router' ; import Link from 'next/link' ; import { useTranslations } from 'next-intl' ; import { Modal , Form , Input , message } from 'antd' ; import { TagOutlined } from '@ant-design/icons' ; import { GlobalContext } from '@/context/global' ; import { DoubleColumnLayout } from '@/layout/DoubleColumnLayout' ; import { ArticleProvider } from '@/providers/article' ; import { LocaleTime } from '@/components/LocaleTime' ; import { ImageViewer } from '@/components/ImageViewer' ; import { Comment } from '@/components/Comment' ; import { ArticleRecommend } from '@/components/ArticleRecommend' ; import { MarkdownReader } from '@/components/MarkdownReader' ; import { Toc } from '@/components/Toc' ; import style from './index.module.scss' ; const url = require ( 'url' ) ; interface IProps { article : IArticle ; } const Article = ( { article } ) => { const t = useTranslations ( ) ; const { setting } = useContext ( GlobalContext ) ; const passwdRef = useRef ( null ) ; const [ shouldCheckPassWord , setShouldCheckPassword ] = useState ( article && article . needPassword ) ; const tocs = article && article . toc ? JSON . parse ( article . toc ) : [ ] ; const checkPassWord = useCallback ( ( ) => { ArticleProvider . checkPassword ( article . id , passwdRef . current ) . then ( ( res ) => { if ( res . pass ) { Object . assign ( article , res ) ; setShouldCheckPassword ( false ) ; } else { message . error ( t ( 'wrongPasswd' ) ) ; setShouldCheckPassword ( true ) ; } } ) ; } , [ t , article ] ) ; const back = useCallback ( ( ) => { Router . push ( '/' ) ; } , [ ] ) ; const checkPassWordModal = ( < Modal title = { t ( 'protectedArticleMsg' ) } cancelText = { t ( 'backHome' ) } okText = { t ( 'confirm' ) } visible = { shouldCheckPassWord } onOk = { checkPassWord } onCancel = { back } > < Form . Item label = { t ( 'passwd' ) } > < Input . Password onChange = { ( e ) => { passwdRef . current = e . target . value ; } } /> < /Form.Item> < /Modal>); useEffect ( ( ) => { setShouldCheckPassword ( article && article . needPassword ) ; } , [ article ] ) ; useEffect ( ( ) => { if ( ! shouldCheckPassWord ) { ArticleProvider . updateArticleViews ( article . id ) ; } } , [ shouldCheckPassWord , article ] ) ; const Content = ( < > { checkPassWordModal } < Helmet > < title > { ( article . title || t ( 'unknownTitle' ) ) + ' | ' + setting . systemTitle } < /title> < /Helmet> < ImageViewer containerSelector = "#js-article-wrapper" > < article id = "js-article-wrapper" className = { style . articleWrap } > { } { setting . systemUrl && ( < meta itemProp = "url" content = { url . resolve ( setting . systemUrl , `/article/${ article . id }` ) } / > ) } < meta itemProp = "headline" content = { article . title } / > { article . tags && ( < meta itemProp = "keywords" content = { article . tags . map ( ( tag ) => tag . label ) . join ( ' ' ) } / > ) } < meta itemProp = "dataPublished" content = { article . publishAt } / > { article . cover && < meta itemProp = "image" content = { article . cover } / > } { } { } { article . cover && ( < div className = { style . coverWrapper } > < img src = { article . cover } alt = { t ( 'articleCover' ) as string } / > < /div>)} { } { } < div className = { style . metaInfoWrap } > < h1 className = { style . title } > { article . title } < /h1> < p className = { style . desc } > < span > { t ( 'publishAt' ) } < LocaleTime date = { article . publishAt } / > < /span> < span > • < /span> < span > { t ( 'readings' ) } { article . views } < /span> < /p> < /div> { } { } < MarkdownReader content = { article . html } /> { } { } < div className = { style . footerInfoWrap } > { } < div className = { style . copyrightInfo } > { t ( 'publishAt' ) } < LocaleTime date = { article . publishAt } / > | { t ( 'copyrightInfo' ) } ： < a href = "https://creativecommons.org/licenses/by-nc/3.0/cn/deed.zh" target = "_blank" rel = "noreferrer" > { t ( 'copyrightContent' ) } < /a> < /div> { } { } { article . tags && article . tags . length ? ( < div className = { style . tagsWrap } > { article . tags . map ( ( tag ) => { return ( < div className = { style . tagWrapper } key = { tag . id } > < div className = { style . tag } > < Link href = { '/tag/[tag]' } as = { '/tag/' + tag . value } scroll = { false } > < a aria - label = { tag . label } > < TagOutlined /> < span > { tag . label } < /span> < /a> < /Link> < /div> < /div>); } ) } < /div>) : null} { } < /div> { } < /article> { } { article . isCommentable && ( < div className = { style . commentWrap } > < p className = { style . title } > { t ( 'comment' ) } < /p> < Comment key = { article . id } hostId = { article . id } / > < /div>)} { } { } < div className = { style . recmmendArticles } > < p className = { style . title } > { t ( 'recommendToReading' ) } < /p> < div className = { style . articleContainer } > < ArticleRecommend articleId = { article . id } needTitle = { false } / > < /div> < /div> { } < /ImageViewer> < />); const Aside = ( < > < div className = { 'sticky' } > { tocs && tocs . length ? < Toc key = { article . id } tocs = { tocs } maxHeight = { '80vh' } / > : null } < /div> < />); return ( < DoubleColumnLayout leftNode = { Content } rightNode = { Aside } likesProps = { { defaultCount : article . likes , id : article . id , api : ( id , type ) => ArticleProvider . updateArticleLikes ( id , type ) . then ( ( res ) => res . likes ) , } } showComment = { article . isCommentable } shareProps = { shouldCheckPassWord ? null : { cover : article . cover , title : article . title , desc : article . summary , url : `/article/${ article . id }` , } } / > ) ; } ; Article . getInitialProps = async ( ctx ) => { const { id } = ctx . query ; const article = await ArticleProvider . getArticle ( id ) ; return { article } ; } ; export default Article ;