mirror of https://gitee.com/answerdev/answer.git
feat: add side nav
This commit is contained in:
parent
e1e6627357
commit
6f63a0a0cd
|
@ -708,6 +708,8 @@ ui:
|
||||||
logout: Log out
|
logout: Log out
|
||||||
admin: Admin
|
admin: Admin
|
||||||
review: Review
|
review: Review
|
||||||
|
bookmark: Bookmarks
|
||||||
|
moderation: Moderation
|
||||||
search:
|
search:
|
||||||
placeholder: Search
|
placeholder: Search
|
||||||
footer:
|
footer:
|
||||||
|
@ -903,7 +905,7 @@ ui:
|
||||||
Views: Viewed
|
Views: Viewed
|
||||||
Follow: Follow
|
Follow: Follow
|
||||||
Following: Following
|
Following: Following
|
||||||
follow_tip: Follow this question to receive notifications.
|
follow_tip: Follow this question to receive notifications
|
||||||
answered: answered
|
answered: answered
|
||||||
closed_in: Closed in
|
closed_in: Closed in
|
||||||
show_exist: Show existing question.
|
show_exist: Show existing question.
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
.page-right-side {
|
||||||
|
flex: none;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 12px;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--bs-gray-300);
|
||||||
|
min-height: calc(100vh - 62px - 74px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// lg
|
||||||
|
@media screen and (max-width: 1199.9px) {
|
||||||
|
.page-right-side {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// md
|
||||||
|
@media screen and (max-width: 991.9px) {
|
||||||
|
.line {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,29 +63,16 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
|
||||||
onClick={handleLinkClick}>
|
onClick={handleLinkClick}>
|
||||||
{t('header.nav.profile')}
|
{t('header.nav.profile')}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item
|
||||||
|
href={`/users/${userInfo.username}/bookmarks`}
|
||||||
|
onClick={handleLinkClick}>
|
||||||
|
{t('header.nav.bookmark')}
|
||||||
|
</Dropdown.Item>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
href="/users/settings/profile"
|
href="/users/settings/profile"
|
||||||
onClick={handleLinkClick}>
|
onClick={handleLinkClick}>
|
||||||
{t('header.nav.setting')}
|
{t('header.nav.setting')}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
{userInfo?.role_id === 2 ? (
|
|
||||||
<Dropdown.Item href="/admin" onClick={handleLinkClick}>
|
|
||||||
{t('header.nav.admin')}
|
|
||||||
</Dropdown.Item>
|
|
||||||
) : null}
|
|
||||||
{redDot?.can_revision ? (
|
|
||||||
<Dropdown.Item
|
|
||||||
href="/review"
|
|
||||||
className="position-relative"
|
|
||||||
onClick={handleLinkClick}>
|
|
||||||
{t('header.nav.review')}
|
|
||||||
{redDot?.revision > 0 && (
|
|
||||||
<span className="position-absolute top-50 translate-middle-y end-0 me-3 p-2 bg-danger border border-light rounded-circle">
|
|
||||||
<span className="visually-hidden">New Review</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Dropdown.Item>
|
|
||||||
) : null}
|
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<Dropdown.Item onClick={logOut}>
|
<Dropdown.Item onClick={logOut}>
|
||||||
{t('header.nav.logout')}
|
{t('header.nav.logout')}
|
||||||
|
|
|
@ -52,10 +52,14 @@
|
||||||
&.theme-light {
|
&.theme-light {
|
||||||
background: linear-gradient(180deg, rgb(255, 255, 255) 0%, rgba(255, 255, 255, 0.95) 100%);
|
background: linear-gradient(180deg, rgb(255, 255, 255) 0%, rgba(255, 255, 255, 0.95) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maxw-400 {
|
||||||
|
max-width: 400px;;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 992.9px) {
|
@media (max-width: 991.9px) {
|
||||||
#header {
|
#header {
|
||||||
.nav-grow {
|
.nav-grow {
|
||||||
flex-grow: 1!important;
|
flex-grow: 1!important;
|
||||||
|
@ -65,8 +69,8 @@
|
||||||
display: flex!important;
|
display: flex!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-75 {
|
.maxw-400 {
|
||||||
width: 100% !important;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
useSearchParams,
|
useSearchParams,
|
||||||
NavLink,
|
|
||||||
Link,
|
Link,
|
||||||
useNavigate,
|
useNavigate,
|
||||||
useLocation,
|
useLocation,
|
||||||
|
@ -27,6 +26,7 @@ import {
|
||||||
brandingStore,
|
brandingStore,
|
||||||
loginSettingStore,
|
loginSettingStore,
|
||||||
themeSettingStore,
|
themeSettingStore,
|
||||||
|
sideNavStore,
|
||||||
} from '@/stores';
|
} from '@/stores';
|
||||||
import { logout, useQueryNotificationStatus } from '@/services';
|
import { logout, useQueryNotificationStatus } from '@/services';
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ const Header: FC = () => {
|
||||||
const siteInfo = siteInfoStore((state) => state.siteInfo);
|
const siteInfo = siteInfoStore((state) => state.siteInfo);
|
||||||
const brandingInfo = brandingStore((state) => state.branding);
|
const brandingInfo = brandingStore((state) => state.branding);
|
||||||
const loginSetting = loginSettingStore((state) => state.login);
|
const loginSetting = loginSettingStore((state) => state.login);
|
||||||
|
const { updateReiview, updateVisible } = sideNavStore();
|
||||||
const { data: redDot } = useQueryNotificationStatus();
|
const { data: redDot } = useQueryNotificationStatus();
|
||||||
/**
|
/**
|
||||||
* Automatically append `tag` information when creating a question
|
* Automatically append `tag` information when creating a question
|
||||||
|
@ -55,6 +56,13 @@ const Header: FC = () => {
|
||||||
askUrl = `${askUrl}?tags=${tagMatch.params.slugName}`;
|
askUrl = `${askUrl}?tags=${tagMatch.params.slugName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateReiview({
|
||||||
|
can_revision: Boolean(redDot?.can_revision),
|
||||||
|
revision: Number(redDot?.revision),
|
||||||
|
});
|
||||||
|
}, [redDot]);
|
||||||
|
|
||||||
const handleInput = (val) => {
|
const handleInput = (val) => {
|
||||||
setSearch(val);
|
setSearch(val);
|
||||||
};
|
};
|
||||||
|
@ -106,10 +114,13 @@ const Header: FC = () => {
|
||||||
aria-controls="navBarContent"
|
aria-controls="navBarContent"
|
||||||
className="answer-navBar me-2"
|
className="answer-navBar me-2"
|
||||||
id="navBarToggle"
|
id="navBarToggle"
|
||||||
|
onClick={() => {
|
||||||
|
updateVisible();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="d-flex justify-content-between align-items-center nav-grow flex-nowrap">
|
<div className="d-flex justify-content-between align-items-center nav-grow flex-nowrap">
|
||||||
<Navbar.Brand to="/" as={Link} className="lh-1 me-0 me-sm-3 p-0">
|
<Navbar.Brand to="/" as={Link} className="lh-1 me-0 me-sm-5 p-0">
|
||||||
{brandingInfo.logo ? (
|
{brandingInfo.logo ? (
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
|
@ -159,26 +170,11 @@ const Header: FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Navbar.Collapse id="navBarContent" className="me-auto">
|
<Navbar.Collapse id="navBarContent" className="me-auto">
|
||||||
<hr className="hr lg-none mb-2" style={{ marginTop: '12px' }} />
|
<hr className="hr lg-none mb-3" style={{ marginTop: '12px' }} />
|
||||||
<Col md={4}>
|
<Col lg={8} className="ps-0">
|
||||||
<Nav>
|
|
||||||
<NavLink className="nav-link" to="/questions">
|
|
||||||
{t('header.nav.question')}
|
|
||||||
</NavLink>
|
|
||||||
<NavLink className="nav-link" to="/tags">
|
|
||||||
{t('header.nav.tag')}
|
|
||||||
</NavLink>
|
|
||||||
<NavLink className="nav-link" to="/users">
|
|
||||||
{t('header.nav.user')}
|
|
||||||
</NavLink>
|
|
||||||
</Nav>
|
|
||||||
</Col>
|
|
||||||
<hr className="hr lg-none mt-2" />
|
|
||||||
|
|
||||||
<Col lg={4} className="d-flex justify-content-center">
|
|
||||||
<Form
|
<Form
|
||||||
action="/search"
|
action="/search"
|
||||||
className="w-75 px-0 px-lg-2"
|
className="w-100 maxw-400"
|
||||||
onSubmit={handleSearch}>
|
onSubmit={handleSearch}>
|
||||||
<FormControl
|
<FormControl
|
||||||
placeholder={t('header.search.placeholder')}
|
placeholder={t('header.search.placeholder')}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
#sideNav {
|
||||||
|
.nav {
|
||||||
|
max-width: 172px;
|
||||||
|
}
|
||||||
|
.nav-link {
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
.nav-link:hover {
|
||||||
|
color: rgba(0, 0, 0);
|
||||||
|
background-color: var(--bs-gray-100);
|
||||||
|
}
|
||||||
|
.nav-link.active {
|
||||||
|
color: black;
|
||||||
|
background-color: var(--bs-gray-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 991.9px) {
|
||||||
|
#sideNav {
|
||||||
|
.nav {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { Col, Nav } from 'react-bootstrap';
|
||||||
|
import { NavLink, useLocation } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
import { loggedUserInfoStore, sideNavStore } from '@/stores';
|
||||||
|
import { Icon } from '@/components';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
const Index: FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const { user: userInfo } = loggedUserInfoStore();
|
||||||
|
const { visible, can_revision, revision } = sideNavStore();
|
||||||
|
return (
|
||||||
|
<Col
|
||||||
|
xl={2}
|
||||||
|
lg={3}
|
||||||
|
md={12}
|
||||||
|
className={classnames(
|
||||||
|
'pt-4 position-relative',
|
||||||
|
visible ? 'fade-in' : 'd-none d-lg-block',
|
||||||
|
)}
|
||||||
|
id="sideNav">
|
||||||
|
<Nav variant="pills" className="flex-column">
|
||||||
|
<NavLink
|
||||||
|
to="/questions"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
isActive || pathname === '/' ? 'nav-link active' : 'nav-link'
|
||||||
|
}>
|
||||||
|
<Icon name="question-circle-fill" className="me-2" />
|
||||||
|
<span>{t('header.nav.question')}</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink to="/tags" className="nav-link">
|
||||||
|
<Icon name="tags-fill" className="me-2" />
|
||||||
|
<span>{t('header.nav.tag')}</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink to="/users" className="nav-link">
|
||||||
|
<Icon name="people-fill" className="me-2" />
|
||||||
|
<span>{t('header.nav.user')}</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
{can_revision || userInfo?.role_id === 2 ? (
|
||||||
|
<>
|
||||||
|
<div className="py-2 px-3 mt-3 fs-14 fw-bold">
|
||||||
|
{t('header.nav.moderation')}
|
||||||
|
</div>
|
||||||
|
{can_revision && (
|
||||||
|
<NavLink to="/review" className="nav-link">
|
||||||
|
<span>{t('header.nav.review')}</span>
|
||||||
|
<span className="float-end">
|
||||||
|
{revision > 99 ? '99+' : revision > 0 ? revision : ''}
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{userInfo?.role_id === 2 ? (
|
||||||
|
<NavLink to="/admin" className="nav-link">
|
||||||
|
{t('header.nav.admin')}
|
||||||
|
</NavLink>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Nav>
|
||||||
|
<div className="line" />
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Index;
|
|
@ -40,6 +40,7 @@ import HotQuestions from './HotQuestions';
|
||||||
import HttpErrorContent from './HttpErrorContent';
|
import HttpErrorContent from './HttpErrorContent';
|
||||||
import CustomSidebar from './CustomSidebar';
|
import CustomSidebar from './CustomSidebar';
|
||||||
import ImgViewer from './ImgViewer';
|
import ImgViewer from './ImgViewer';
|
||||||
|
import SideNav from './SideNav';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
@ -86,5 +87,6 @@ export {
|
||||||
HttpErrorContent,
|
HttpErrorContent,
|
||||||
CustomSidebar,
|
CustomSidebar,
|
||||||
ImgViewer,
|
ImgViewer,
|
||||||
|
SideNav,
|
||||||
};
|
};
|
||||||
export type { EditorRef, JSONSchema, UISchema };
|
export type { EditorRef, JSONSchema, UISchema };
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
import { Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||||
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
|
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -320,14 +320,10 @@ const Ask = () => {
|
||||||
title: pageTitle,
|
title: pageTitle,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5">
|
<div className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<h3 className="mb-4">{isEdit ? t('edit_title') : t('title')}</h3>
|
||||||
<Col xxl={10} md={12}>
|
<Row>
|
||||||
<h3 className="mb-4">{isEdit ? t('edit_title') : t('title')}</h3>
|
<Col className="flex-auto">
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row className="justify-content-center">
|
|
||||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
|
||||||
<Form noValidate onSubmit={handleSubmit}>
|
<Form noValidate onSubmit={handleSubmit}>
|
||||||
{isEdit && (
|
{isEdit && (
|
||||||
<Form.Group controlId="revision" className="mb-3">
|
<Form.Group controlId="revision" className="mb-3">
|
||||||
|
@ -491,7 +487,7 @@ const Ask = () => {
|
||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
{t('title', { keyPrefix: 'how_to_format' })}
|
{t('title', { keyPrefix: 'how_to_format' })}
|
||||||
|
@ -505,7 +501,7 @@ const Ask = () => {
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Container, Row, Col } from 'react-bootstrap';
|
import { Row, Col } from 'react-bootstrap';
|
||||||
import {
|
import {
|
||||||
useParams,
|
useParams,
|
||||||
useSearchParams,
|
useSearchParams,
|
||||||
|
@ -198,69 +198,67 @@ const Index = () => {
|
||||||
keywords: question?.tags.map((_) => _.slug_name).join(','),
|
keywords: question?.tags.map((_) => _.slug_name).join(','),
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5 questionDetailPage">
|
<Row className="questionDetailPage pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<Col className="flex-auto">
|
||||||
<Col xxl={7} lg={8} sm={12} className="mb-5 mb-md-0">
|
{question?.operation?.level && <Alert data={question.operation} />}
|
||||||
{question?.operation?.level && <Alert data={question.operation} />}
|
{isLoading ? (
|
||||||
{isLoading ? (
|
<ContentLoader />
|
||||||
<ContentLoader />
|
) : (
|
||||||
) : (
|
<Question
|
||||||
<Question
|
data={question}
|
||||||
data={question}
|
initPage={initPage}
|
||||||
initPage={initPage}
|
hasAnswer={answers.count > 0}
|
||||||
hasAnswer={answers.count > 0}
|
isLogged={isLogged}
|
||||||
isLogged={isLogged}
|
/>
|
||||||
|
)}
|
||||||
|
{!isLoading && answers.count > 0 && (
|
||||||
|
<>
|
||||||
|
<AnswerHead count={answers.count} order={order} />
|
||||||
|
{answers?.list?.map((item) => {
|
||||||
|
return (
|
||||||
|
<Answer
|
||||||
|
aid={aid}
|
||||||
|
key={item?.id}
|
||||||
|
data={item}
|
||||||
|
questionTitle={question?.title || ''}
|
||||||
|
slugTitle={question?.url_title}
|
||||||
|
canAccept={isAuthor || isAdmin || isModerator}
|
||||||
|
callback={initPage}
|
||||||
|
isLogged={isLogged}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && Math.ceil(answers.count / 15) > 1 && (
|
||||||
|
<div className="d-flex justify-content-center answer-item pt-4">
|
||||||
|
<Pagination
|
||||||
|
currentPage={Number(page || 1)}
|
||||||
|
pageSize={15}
|
||||||
|
totalSize={answers?.count || 0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading &&
|
||||||
|
Number(question?.status) !== 2 &&
|
||||||
|
!question?.operation?.type && (
|
||||||
|
<WriteAnswer
|
||||||
|
data={{
|
||||||
|
qid,
|
||||||
|
answered: question?.answered,
|
||||||
|
loggedUserRank,
|
||||||
|
}}
|
||||||
|
callback={writeAnswerCallback}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isLoading && answers.count > 0 && (
|
</Col>
|
||||||
<>
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
<AnswerHead count={answers.count} order={order} />
|
<CustomSidebar />
|
||||||
{answers?.list?.map((item) => {
|
<RelatedQuestions id={question?.id || ''} />
|
||||||
return (
|
</Col>
|
||||||
<Answer
|
</Row>
|
||||||
aid={aid}
|
|
||||||
key={item?.id}
|
|
||||||
data={item}
|
|
||||||
questionTitle={question?.title || ''}
|
|
||||||
slugTitle={question?.url_title}
|
|
||||||
canAccept={isAuthor || isAdmin || isModerator}
|
|
||||||
callback={initPage}
|
|
||||||
isLogged={isLogged}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isLoading && Math.ceil(answers.count / 15) > 1 && (
|
|
||||||
<div className="d-flex justify-content-center answer-item pt-4">
|
|
||||||
<Pagination
|
|
||||||
currentPage={Number(page || 1)}
|
|
||||||
pageSize={15}
|
|
||||||
totalSize={answers?.count || 0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isLoading &&
|
|
||||||
Number(question?.status) !== 2 &&
|
|
||||||
!question?.operation?.type && (
|
|
||||||
<WriteAnswer
|
|
||||||
data={{
|
|
||||||
qid,
|
|
||||||
answered: question?.answered,
|
|
||||||
loggedUserRank,
|
|
||||||
}}
|
|
||||||
callback={writeAnswerCallback}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
|
||||||
<CustomSidebar />
|
|
||||||
<RelatedQuestions id={question?.id || ''} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useRef, useEffect, useLayoutEffect } from 'react';
|
import React, { useState, useRef, useEffect, useLayoutEffect } from 'react';
|
||||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
import { Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -184,14 +184,10 @@ const Index = () => {
|
||||||
title: t('edit_answer', { keyPrefix: 'page_title' }),
|
title: t('edit_answer', { keyPrefix: 'page_title' }),
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5 edit-answer-wrap">
|
<div className="pt-4 mb-5 edit-answer-wrap">
|
||||||
<Row className="justify-content-center">
|
<h3 className="mb-4">{t('title')}</h3>
|
||||||
<Col xxl={10} md={12}>
|
<Row>
|
||||||
<h3 className="mb-4">{t('title')}</h3>
|
<Col className="flex-auto">
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row className="justify-content-center">
|
|
||||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
|
||||||
<a
|
<a
|
||||||
href={pathFactory.questionLanding(qid, data?.question.url_title)}
|
href={pathFactory.questionLanding(qid, data?.question.url_title)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -285,7 +281,7 @@ const Index = () => {
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
{t('title', { keyPrefix: 'how_to_format' })}
|
{t('title', { keyPrefix: 'how_to_format' })}
|
||||||
|
@ -299,7 +295,7 @@ const Index = () => {
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Container, Row, Col } from 'react-bootstrap';
|
import { Row, Col } from 'react-bootstrap';
|
||||||
import { useMatch, Link, useSearchParams } from 'react-router-dom';
|
import { useMatch, Link, useSearchParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -44,48 +44,46 @@ const Questions: FC = () => {
|
||||||
|
|
||||||
usePageTags({ title: pageTitle, subtitle: slogan });
|
usePageTags({ title: pageTitle, subtitle: slogan });
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5">
|
<Row className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<Col className="flex-auto">
|
||||||
<Col xxl={7} lg={8} sm={12}>
|
<QuestionList
|
||||||
<QuestionList
|
source="questions"
|
||||||
source="questions"
|
data={listData}
|
||||||
data={listData}
|
isLoading={listLoading}
|
||||||
isLoading={listLoading}
|
/>
|
||||||
/>
|
</Col>
|
||||||
</Col>
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
<CustomSidebar />
|
||||||
<CustomSidebar />
|
{!loggedUser.username && (
|
||||||
{!loggedUser.username && (
|
<div className="card mb-4">
|
||||||
<div className="card mb-4">
|
<div className="card-body">
|
||||||
<div className="card-body">
|
<h5 className="card-title">
|
||||||
<h5 className="card-title">
|
{t2('website_welcome', {
|
||||||
{t2('website_welcome', {
|
site_name: siteInfo.name,
|
||||||
site_name: siteInfo.name,
|
})}
|
||||||
})}
|
</h5>
|
||||||
</h5>
|
<p className="card-text">{siteInfo.description}</p>
|
||||||
<p className="card-text">{siteInfo.description}</p>
|
<Link
|
||||||
|
to={userCenter.getLoginUrl()}
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={floppyNavigation.handleRouteLinkClick}>
|
||||||
|
{t('login', { keyPrefix: 'btns' })}
|
||||||
|
</Link>
|
||||||
|
{loginSetting.allow_new_registrations ? (
|
||||||
<Link
|
<Link
|
||||||
to={userCenter.getLoginUrl()}
|
to={userCenter.getSignUpUrl()}
|
||||||
className="btn btn-primary"
|
className="btn btn-link ms-2"
|
||||||
onClick={floppyNavigation.handleRouteLinkClick}>
|
onClick={floppyNavigation.handleRouteLinkClick}>
|
||||||
{t('login', { keyPrefix: 'btns' })}
|
{t('signup', { keyPrefix: 'btns' })}
|
||||||
</Link>
|
</Link>
|
||||||
{loginSetting.allow_new_registrations ? (
|
) : null}
|
||||||
<Link
|
|
||||||
to={userCenter.getSignUpUrl()}
|
|
||||||
className="btn btn-link ms-2"
|
|
||||||
onClick={floppyNavigation.handleRouteLinkClick}>
|
|
||||||
{t('signup', { keyPrefix: 'btns' })}
|
|
||||||
</Link>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
{loggedUser.access_token && <FollowingTags />}
|
)}
|
||||||
<HotQuestions />
|
{loggedUser.access_token && <FollowingTags />}
|
||||||
</Col>
|
<HotQuestions />
|
||||||
</Row>
|
</Col>
|
||||||
</Container>
|
</Row>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { Container, Row, Col, Alert, Stack, Button } from 'react-bootstrap';
|
import { Row, Col, Alert, Stack, Button } from 'react-bootstrap';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -123,121 +123,111 @@ const Index: FC = () => {
|
||||||
title: t('review'),
|
title: t('review'),
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="pt-2 mt-4 mb-5">
|
<Row className="pt-4 mb-5">
|
||||||
<Row>
|
<h3 className="mb-4">{t('review')}</h3>
|
||||||
<Col lg={{ span: 7, offset: 1 }}>
|
<Col className="flex-auto">
|
||||||
<h3 className="mb-4">{t('review')}</h3>
|
|
||||||
</Col>
|
|
||||||
{!noTasks && ro && (
|
{!noTasks && ro && (
|
||||||
<>
|
<>
|
||||||
<Col lg={{ span: 7, offset: 1 }}>
|
<Alert variant="secondary">
|
||||||
<Alert variant="secondary">
|
<Stack className="align-items-start">
|
||||||
<Stack className="align-items-start">
|
<span className="badge text-bg-secondary mb-2">
|
||||||
<span className="badge text-bg-secondary mb-2">
|
{editBadge}
|
||||||
{editBadge}
|
</span>
|
||||||
</span>
|
<Link to={itemLink} target="_blank">
|
||||||
<Link to={itemLink} target="_blank">
|
{itemTitle}
|
||||||
{itemTitle}
|
</Link>
|
||||||
</Link>
|
<p className="mb-0">
|
||||||
<p className="mb-0">
|
{t('edit_summary')}: {editSummary}
|
||||||
{t('edit_summary')}: {editSummary}
|
</p>
|
||||||
</p>
|
</Stack>
|
||||||
</Stack>
|
<Stack
|
||||||
<Stack
|
direction="horizontal"
|
||||||
direction="horizontal"
|
gap={1}
|
||||||
gap={1}
|
className="align-items-baseline mt-2">
|
||||||
className="align-items-baseline mt-2">
|
<BaseUserCard data={editor} avatarSize="24" />
|
||||||
<BaseUserCard data={editor} avatarSize="24" />
|
{editTime && (
|
||||||
{editTime && (
|
<FormatTime
|
||||||
<FormatTime
|
time={editTime}
|
||||||
time={editTime}
|
className="small text-secondary"
|
||||||
className="small text-secondary"
|
preFix={t('proposed')}
|
||||||
preFix={t('proposed')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Alert>
|
|
||||||
</Col>
|
|
||||||
<Col lg={{ span: 7, offset: 1 }}>
|
|
||||||
{type === 'question' &&
|
|
||||||
info &&
|
|
||||||
reviewInfo &&
|
|
||||||
'content' in reviewInfo && (
|
|
||||||
<DiffContent
|
|
||||||
className="mt-2"
|
|
||||||
objectType={type}
|
|
||||||
oldData={{
|
|
||||||
title: info.title,
|
|
||||||
original_text: info.content,
|
|
||||||
tags: info.tags,
|
|
||||||
}}
|
|
||||||
newData={{
|
|
||||||
title: reviewInfo.title,
|
|
||||||
original_text: reviewInfo.content,
|
|
||||||
tags: reviewInfo.tags,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{type === 'answer' &&
|
</Stack>
|
||||||
info &&
|
</Alert>
|
||||||
reviewInfo &&
|
{type === 'question' &&
|
||||||
'content' in reviewInfo && (
|
info &&
|
||||||
<DiffContent
|
reviewInfo &&
|
||||||
className="mt-2"
|
'content' in reviewInfo && (
|
||||||
objectType={type}
|
<DiffContent
|
||||||
newData={{
|
className="mt-2"
|
||||||
original_text: reviewInfo.content,
|
objectType={type}
|
||||||
}}
|
oldData={{
|
||||||
oldData={{
|
title: info.title,
|
||||||
original_text: info.content,
|
original_text: info.content,
|
||||||
}}
|
tags: info.tags,
|
||||||
/>
|
}}
|
||||||
)}
|
newData={{
|
||||||
{type === 'tag' && info && reviewInfo && (
|
title: reviewInfo.title,
|
||||||
|
original_text: reviewInfo.content,
|
||||||
|
tags: reviewInfo.tags,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === 'answer' &&
|
||||||
|
info &&
|
||||||
|
reviewInfo &&
|
||||||
|
'content' in reviewInfo && (
|
||||||
<DiffContent
|
<DiffContent
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
objectType={type}
|
objectType={type}
|
||||||
newData={{
|
newData={{
|
||||||
original_text: reviewInfo.original_text,
|
original_text: reviewInfo.content,
|
||||||
}}
|
}}
|
||||||
oldData={{
|
oldData={{
|
||||||
original_text: info.content,
|
original_text: info.content,
|
||||||
}}
|
}}
|
||||||
opts={{ showTitle: false, showTagUrlSlug: false }}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Col>
|
{type === 'tag' && info && reviewInfo && (
|
||||||
<Col lg={{ span: 7, offset: 1 }}>
|
<DiffContent
|
||||||
<Stack direction="horizontal" gap={2} className="mt-4">
|
className="mt-2"
|
||||||
<Button
|
objectType={type}
|
||||||
variant="outline-primary"
|
newData={{
|
||||||
disabled={isLoading}
|
original_text: reviewInfo.original_text,
|
||||||
onClick={handlingApprove}>
|
}}
|
||||||
{t('approve', { keyPrefix: 'btns' })}
|
oldData={{
|
||||||
</Button>
|
original_text: info.content,
|
||||||
<Button
|
}}
|
||||||
variant="outline-primary"
|
opts={{ showTitle: false, showTagUrlSlug: false }}
|
||||||
disabled={isLoading}
|
/>
|
||||||
onClick={handlingReject}>
|
)}
|
||||||
{t('reject', { keyPrefix: 'btns' })}
|
<Stack direction="horizontal" gap={2} className="mt-4">
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
variant="outline-primary"
|
||||||
variant="outline-primary"
|
disabled={isLoading}
|
||||||
disabled={isLoading}
|
onClick={handlingApprove}>
|
||||||
onClick={handlingSkip}>
|
{t('approve', { keyPrefix: 'btns' })}
|
||||||
{t('skip', { keyPrefix: 'btns' })}
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
</Stack>
|
variant="outline-primary"
|
||||||
</Col>
|
disabled={isLoading}
|
||||||
|
onClick={handlingReject}>
|
||||||
|
{t('reject', { keyPrefix: 'btns' })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline-primary"
|
||||||
|
disabled={isLoading}
|
||||||
|
onClick={handlingSkip}>
|
||||||
|
{t('skip', { keyPrefix: 'btns' })}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{noTasks && (
|
{noTasks && <Empty>{t('empty')}</Empty>}
|
||||||
<Col lg={{ span: 7, offset: 1 }}>
|
</Col>
|
||||||
<Empty>{t('empty')}</Empty>
|
|
||||||
</Col>
|
<Col className="page-right-side mt-4 mt-xl-0" />
|
||||||
)}
|
</Row>
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import { Row, Col, ListGroup } from 'react-bootstrap';
|
||||||
import { Container, Row, Col, ListGroup } from 'react-bootstrap';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -39,36 +38,34 @@ const Index = () => {
|
||||||
title: pageTitle,
|
title: pageTitle,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5">
|
<Row className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<Col className="flex-auto">
|
||||||
<Col xxl={7} lg={8} sm={12} className="mb-3">
|
<Head data={extra} />
|
||||||
<Head data={extra} />
|
<SearchHead sort={order} count={count} />
|
||||||
<SearchHead sort={order} count={count} />
|
<ListGroup className="rounded-0 mb-5">
|
||||||
<ListGroup className="rounded-0 mb-5">
|
{isLoading ? (
|
||||||
{isLoading ? (
|
<ListLoader />
|
||||||
<ListLoader />
|
) : (
|
||||||
) : (
|
list?.map((item) => {
|
||||||
list?.map((item) => {
|
return <SearchItem key={item.object.id} data={item} />;
|
||||||
return <SearchItem key={item.object.id} data={item} />;
|
})
|
||||||
})
|
)}
|
||||||
)}
|
</ListGroup>
|
||||||
</ListGroup>
|
|
||||||
|
|
||||||
{!isLoading && !list?.length && <Empty />}
|
{!isLoading && !list?.length && <Empty />}
|
||||||
|
|
||||||
<div className="d-flex justify-content-center">
|
<div className="d-flex justify-content-center">
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPage={Number(page)}
|
currentPage={Number(page)}
|
||||||
pageSize={20}
|
pageSize={20}
|
||||||
totalSize={count}
|
totalSize={count}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
<Tips />
|
<Tips />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { FC, memo } from 'react';
|
||||||
|
import { Container, Row, Col } from 'react-bootstrap';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { SideNav } from '@/components';
|
||||||
|
|
||||||
|
import '@/common/sideNavLayout.scss';
|
||||||
|
|
||||||
|
const Index: FC = () => {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Row>
|
||||||
|
<SideNav />
|
||||||
|
<Col xl={10} lg={9} md={12}>
|
||||||
|
<Outlet />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Index);
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
import { Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -139,14 +139,10 @@ const Index = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5 edit-answer-wrap">
|
<div className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<h3 className="mb-4">{t('title')}</h3>
|
||||||
<Col xxl={10} md={12}>
|
<Row>
|
||||||
<h3 className="mb-4">{t('title')}</h3>
|
<Col className="flex-auto">
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row className="justify-content-center">
|
|
||||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
|
||||||
<Form noValidate onSubmit={handleSubmit}>
|
<Form noValidate onSubmit={handleSubmit}>
|
||||||
<Form.Group controlId="display_name" className="mb-3">
|
<Form.Group controlId="display_name" className="mb-3">
|
||||||
<Form.Label>{t('form.fields.display_name.label')}</Form.Label>
|
<Form.Label>{t('form.fields.display_name.label')}</Form.Label>
|
||||||
|
@ -208,7 +204,7 @@ const Index = () => {
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
{t('title', { keyPrefix: 'how_to_format' })}
|
{t('title', { keyPrefix: 'how_to_format' })}
|
||||||
|
@ -222,7 +218,7 @@ const Index = () => {
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { Container, Row, Col, Button } from 'react-bootstrap';
|
import { Row, Col, Button } from 'react-bootstrap';
|
||||||
import {
|
import {
|
||||||
useParams,
|
useParams,
|
||||||
Link,
|
Link,
|
||||||
|
@ -100,64 +100,62 @@ const Questions: FC = () => {
|
||||||
keywords: keywords.join(','),
|
keywords: keywords.join(','),
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5">
|
<Row className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<Col className="flex-auto">
|
||||||
<Col xxl={7} lg={8} sm={12}>
|
{isLoading || listLoading ? (
|
||||||
{isLoading || listLoading ? (
|
<div className="tag-box mb-5 placeholder-glow">
|
||||||
<div className="tag-box mb-5 placeholder-glow">
|
<div className="mb-3 h3 placeholder" style={{ width: '120px' }} />
|
||||||
<div className="mb-3 h3 placeholder" style={{ width: '120px' }} />
|
<p
|
||||||
<p
|
className="placeholder w-100 d-block align-top"
|
||||||
className="placeholder w-100 d-block align-top"
|
style={{ height: '24px' }}
|
||||||
style={{ height: '24px' }}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="placeholder d-block align-top"
|
className="placeholder d-block align-top"
|
||||||
style={{ height: '38px', width: '100px' }}
|
style={{ height: '38px', width: '100px' }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="tag-box mb-5">
|
||||||
|
<h3 className="mb-3">
|
||||||
|
<Link
|
||||||
|
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||||
|
replace
|
||||||
|
className="link-dark">
|
||||||
|
{tagInfo.display_name}
|
||||||
|
</Link>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-break">
|
||||||
|
{escapeRemove(tagInfo.excerpt) || t('no_desc')}
|
||||||
|
<Link to={pathFactory.tagInfo(curTagName)} className="ms-1">
|
||||||
|
[{t('more')}]
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="box-ft">
|
||||||
|
{tagInfo.is_follower ? (
|
||||||
|
<Button variant="primary" onClick={() => toggleFollow()}>
|
||||||
|
{t('button_following')}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="outline-primary"
|
||||||
|
onClick={() => toggleFollow()}>
|
||||||
|
{t('button_follow')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<div className="tag-box mb-5">
|
)}
|
||||||
<h3 className="mb-3">
|
<QuestionList source="tag" data={listData} isLoading={listLoading} />
|
||||||
<Link
|
</Col>
|
||||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
replace
|
<CustomSidebar />
|
||||||
className="link-dark">
|
<FollowingTags />
|
||||||
{tagInfo.display_name}
|
<HotQuestions />
|
||||||
</Link>
|
</Col>
|
||||||
</h3>
|
</Row>
|
||||||
|
|
||||||
<p className="text-break">
|
|
||||||
{escapeRemove(tagInfo.excerpt) || t('no_desc')}
|
|
||||||
<Link to={pathFactory.tagInfo(curTagName)} className="ms-1">
|
|
||||||
[{t('more')}]
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="box-ft">
|
|
||||||
{tagInfo.is_follower ? (
|
|
||||||
<Button variant="primary" onClick={() => toggleFollow()}>
|
|
||||||
{t('button_following')}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
variant="outline-primary"
|
|
||||||
onClick={() => toggleFollow()}>
|
|
||||||
{t('button_follow')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<QuestionList source="tag" data={listData} isLoading={listLoading} />
|
|
||||||
</Col>
|
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
|
||||||
<CustomSidebar />
|
|
||||||
<FollowingTags />
|
|
||||||
<HotQuestions />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
import { Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -188,14 +188,10 @@ const Index = () => {
|
||||||
title: t('edit_tag', { keyPrefix: 'page_title' }),
|
title: t('edit_tag', { keyPrefix: 'page_title' }),
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5 edit-answer-wrap">
|
<div className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<h3 className="mb-4">{t('title')}</h3>
|
||||||
<Col xxl={10} md={12}>
|
<Row>
|
||||||
<h3 className="mb-4">{t('title')}</h3>
|
<Col className="flex-auto">
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row className="justify-content-center">
|
|
||||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
|
||||||
<Form noValidate onSubmit={handleSubmit}>
|
<Form noValidate onSubmit={handleSubmit}>
|
||||||
<Form.Group controlId="revision" className="mb-3">
|
<Form.Group controlId="revision" className="mb-3">
|
||||||
<Form.Label>{t('form.fields.revision.label')}</Form.Label>
|
<Form.Label>{t('form.fields.revision.label')}</Form.Label>
|
||||||
|
@ -291,7 +287,7 @@ const Index = () => {
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
{t('title', { keyPrefix: 'how_to_format' })}
|
{t('title', { keyPrefix: 'how_to_format' })}
|
||||||
|
@ -305,7 +301,7 @@ const Index = () => {
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Container, Row, Col, Button, Card } from 'react-bootstrap';
|
import { Row, Col, Button, Card } from 'react-bootstrap';
|
||||||
import { useParams, useNavigate, Link, useLocation } from 'react-router-dom';
|
import { useParams, useNavigate, Link, useLocation } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -132,132 +132,130 @@ const TagIntroduction = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5">
|
<Row className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<Col className="flex-auto">
|
||||||
<Col xxl={7} lg={8} sm={12}>
|
<h3 className="mb-3">
|
||||||
<h3 className="mb-3">
|
<Link
|
||||||
<Link
|
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
replace
|
||||||
replace
|
className="link-dark">
|
||||||
className="link-dark">
|
{tagInfo.display_name}
|
||||||
{tagInfo.display_name}
|
</Link>
|
||||||
</Link>
|
</h3>
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="text-secondary mb-4 fs-14">
|
<div className="text-secondary mb-4 fs-14">
|
||||||
<FormatTime preFix={t('created_at')} time={tagInfo.created_at} />
|
<FormatTime preFix={t('created_at')} time={tagInfo.created_at} />
|
||||||
<FormatTime
|
<FormatTime
|
||||||
preFix={t('edited_at')}
|
preFix={t('edited_at')}
|
||||||
className="ms-3"
|
className="ms-3"
|
||||||
time={tagInfo.updated_at}
|
time={tagInfo.updated_at}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="content text-break fmt"
|
|
||||||
dangerouslySetInnerHTML={{ __html: tagInfo?.parsed_text }}
|
|
||||||
/>
|
/>
|
||||||
<div className="mt-4">
|
</div>
|
||||||
{tagInfo?.member_actions.map((action, index) => {
|
|
||||||
return (
|
<div
|
||||||
<Button
|
className="content text-break fmt"
|
||||||
key={action.name}
|
dangerouslySetInnerHTML={{ __html: tagInfo?.parsed_text }}
|
||||||
variant="link"
|
/>
|
||||||
className={classNames(
|
<div className="mt-4">
|
||||||
'link-secondary btn-no-border p-0 fs-14',
|
{tagInfo?.member_actions.map((action, index) => {
|
||||||
index > 0 && 'ms-3',
|
return (
|
||||||
)}
|
<Button
|
||||||
onClick={() => onAction(action)}>
|
key={action.name}
|
||||||
{action.name}
|
variant="link"
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{isLogged && (
|
|
||||||
<Link
|
|
||||||
to={`/tags/${tagInfo?.tag_id}/timeline`}
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'link-secondary btn-no-border p-0 fs-14',
|
'link-secondary btn-no-border p-0 fs-14',
|
||||||
tagInfo?.member_actions?.length > 0 && 'ms-3',
|
index > 0 && 'ms-3',
|
||||||
)}>
|
)}
|
||||||
{t('history')}
|
onClick={() => onAction(action)}>
|
||||||
</Link>
|
{action.name}
|
||||||
)}
|
</Button>
|
||||||
</div>
|
);
|
||||||
</Col>
|
})}
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
{isLogged && (
|
||||||
<Card>
|
<Link
|
||||||
<Card.Header className="d-flex justify-content-between">
|
to={`/tags/${tagInfo?.tag_id}/timeline`}
|
||||||
<span>{t('synonyms.title')}</span>
|
className={classNames(
|
||||||
{isEdit ? (
|
'link-secondary btn-no-border p-0 fs-14',
|
||||||
<Button
|
tagInfo?.member_actions?.length > 0 && 'ms-3',
|
||||||
variant="link"
|
)}>
|
||||||
className="p-0 btn-no-border"
|
{t('history')}
|
||||||
onClick={handleSave}>
|
</Link>
|
||||||
{t('synonyms.btn_save')}
|
)}
|
||||||
</Button>
|
</div>
|
||||||
) : synonymsData?.member_actions?.find(
|
</Col>
|
||||||
(v) => v.action === 'edit',
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
) ? (
|
<Card>
|
||||||
<Button
|
<Card.Header className="d-flex justify-content-between">
|
||||||
variant="link"
|
<span>{t('synonyms.title')}</span>
|
||||||
className="p-0 btn-no-border"
|
{isEdit ? (
|
||||||
onClick={handleEdit}>
|
<Button
|
||||||
{t('synonyms.btn_edit')}
|
variant="link"
|
||||||
</Button>
|
className="p-0 btn-no-border"
|
||||||
) : null}
|
onClick={handleSave}>
|
||||||
</Card.Header>
|
{t('synonyms.btn_save')}
|
||||||
<Card.Body>
|
</Button>
|
||||||
{isEdit && (
|
) : synonymsData?.member_actions?.find(
|
||||||
<>
|
(v) => v.action === 'edit',
|
||||||
<div className="mb-3">
|
) ? (
|
||||||
{t('synonyms.text')}{' '}
|
<Button
|
||||||
<Tag
|
variant="link"
|
||||||
data={{
|
className="p-0 btn-no-border"
|
||||||
slug_name: tagName || '',
|
onClick={handleEdit}>
|
||||||
main_tag_slug_name: '',
|
{t('synonyms.btn_edit')}
|
||||||
display_name:
|
</Button>
|
||||||
tagInfo?.display_name || tagInfo?.slug_name || '',
|
) : null}
|
||||||
recommend: false,
|
</Card.Header>
|
||||||
reserved: false,
|
<Card.Body>
|
||||||
}}
|
{isEdit && (
|
||||||
/>
|
<>
|
||||||
</div>
|
<div className="mb-3">
|
||||||
<TagSelector
|
{t('synonyms.text')}{' '}
|
||||||
value={synonymsData?.synonyms}
|
<Tag
|
||||||
onChange={handleTagsChange}
|
data={{
|
||||||
hiddenDescription
|
slug_name: tagName || '',
|
||||||
|
main_tag_slug_name: '',
|
||||||
|
display_name:
|
||||||
|
tagInfo?.display_name || tagInfo?.slug_name || '',
|
||||||
|
recommend: false,
|
||||||
|
reserved: false,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<TagSelector
|
||||||
|
value={synonymsData?.synonyms}
|
||||||
|
onChange={handleTagsChange}
|
||||||
|
hiddenDescription
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!isEdit &&
|
||||||
|
(synonymsData?.synonyms && synonymsData.synonyms.length > 0 ? (
|
||||||
|
<div className="m-n1">
|
||||||
|
{synonymsData.synonyms.map((item) => {
|
||||||
|
return (
|
||||||
|
<Tag key={item.tag_id} className="m-1" data={item} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="text-muted mb-3">{t('synonyms.empty')}</div>
|
||||||
|
{synonymsData?.member_actions?.find(
|
||||||
|
(v) => v.action === 'edit',
|
||||||
|
) && (
|
||||||
|
<Button
|
||||||
|
variant="outline-primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleEdit}>
|
||||||
|
{t('synonyms.btn_add')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
))}
|
||||||
{!isEdit &&
|
</Card.Body>
|
||||||
(synonymsData?.synonyms && synonymsData.synonyms.length > 0 ? (
|
</Card>
|
||||||
<div className="m-n1">
|
</Col>
|
||||||
{synonymsData.synonyms.map((item) => {
|
</Row>
|
||||||
return (
|
|
||||||
<Tag key={item.tag_id} className="m-1" data={item} />
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="text-muted mb-3">{t('synonyms.empty')}</div>
|
|
||||||
{synonymsData?.member_actions?.find(
|
|
||||||
(v) => v.action === 'edit',
|
|
||||||
) && (
|
|
||||||
<Button
|
|
||||||
variant="outline-primary"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleEdit}>
|
|
||||||
{t('synonyms.btn_add')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</Card.Body>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import { Row, Col, Card, Button, Form, Stack } from 'react-bootstrap';
|
||||||
Container,
|
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
Card,
|
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
Stack,
|
|
||||||
} from 'react-bootstrap';
|
|
||||||
import { useSearchParams, Link } from 'react-router-dom';
|
import { useSearchParams, Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -60,90 +52,89 @@ const Tags = () => {
|
||||||
title: t('tags', { keyPrefix: 'page_title' }),
|
title: t('tags', { keyPrefix: 'page_title' }),
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="py-3 my-3">
|
<Row className="py-4 mb-4">
|
||||||
<Row className="mb-4 d-flex justify-content-center">
|
<Col xxl={12}>
|
||||||
<Col xxl={10} sm={12}>
|
<h3 className="mb-4">{t('title')}</h3>
|
||||||
<h3 className="mb-4">{t('title')}</h3>
|
<div className="d-block d-sm-flex justify-content-between align-items-center flex-wrap">
|
||||||
<div className="d-block d-sm-flex justify-content-between align-items-center flex-wrap">
|
<Stack direction="horizontal" gap={3} className="mb-3 mb-sm-0">
|
||||||
<Stack direction="horizontal" gap={3} className="mb-3 mb-sm-0">
|
<Form>
|
||||||
<Form>
|
<Form.Group controlId="formBasicEmail">
|
||||||
<Form.Group controlId="formBasicEmail">
|
<Form.Control
|
||||||
<Form.Control
|
value={searchTag}
|
||||||
value={searchTag}
|
placeholder={t('search_placeholder')}
|
||||||
placeholder={t('search_placeholder')}
|
type="text"
|
||||||
type="text"
|
onChange={handleChange}
|
||||||
onChange={handleChange}
|
size="sm"
|
||||||
size="sm"
|
/>
|
||||||
/>
|
</Form.Group>
|
||||||
</Form.Group>
|
</Form>
|
||||||
</Form>
|
{role_id === 2 || role_id === 3 ? (
|
||||||
{role_id === 2 || role_id === 3 ? (
|
<Link
|
||||||
<Link
|
className="btn btn-outline-primary btn-sm"
|
||||||
className="btn btn-outline-primary btn-sm"
|
to="/tags/create">
|
||||||
to="/tags/create">
|
{t('title', { keyPrefix: 'tag_modal' })}
|
||||||
{t('title', { keyPrefix: 'tag_modal' })}
|
</Link>
|
||||||
</Link>
|
) : null}
|
||||||
) : null}
|
</Stack>
|
||||||
</Stack>
|
<QueryGroup
|
||||||
<QueryGroup
|
data={sortBtns}
|
||||||
data={sortBtns}
|
currentSort={sort || 'popular'}
|
||||||
currentSort={sort || 'popular'}
|
sortKey="sort"
|
||||||
sortKey="sort"
|
i18nKeyPrefix="tags.sort_buttons"
|
||||||
i18nKeyPrefix="tags.sort_buttons"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</Col>
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col className="mt-4" xxl={10} sm={12}>
|
<Col className="mt-4" xxl={12}>
|
||||||
<Row>
|
<Row>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<TagsLoader />
|
<TagsLoader />
|
||||||
) : (
|
) : (
|
||||||
tags?.list?.map((tag) => (
|
tags?.list?.map((tag) => (
|
||||||
<Col
|
<Col
|
||||||
key={tag.slug_name}
|
key={tag.slug_name}
|
||||||
xs={12}
|
xl={3}
|
||||||
lg={3}
|
lg={4}
|
||||||
md={4}
|
md={4}
|
||||||
sm={6}
|
sm={6}
|
||||||
className="mb-4">
|
xs={12}
|
||||||
<Card className="h-100">
|
className="mb-4">
|
||||||
<Card.Body className="d-flex flex-column align-items-start">
|
<Card className="h-100">
|
||||||
<Tag className="mb-3" data={tag} />
|
<Card.Body className="d-flex flex-column align-items-start">
|
||||||
|
<Tag className="mb-3" data={tag} />
|
||||||
|
|
||||||
<div className="fs-14 flex-fill text-break text-wrap text-truncate-3 reset-p mb-3">
|
<div className="fs-14 flex-fill text-break text-wrap text-truncate-3 reset-p mb-3">
|
||||||
{escapeRemove(tag.excerpt)}
|
{escapeRemove(tag.excerpt)}
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<Button
|
<Button
|
||||||
className={`me-2 ${tag.is_follower ? 'active' : ''}`}
|
className={`me-2 ${tag.is_follower ? 'active' : ''}`}
|
||||||
variant="outline-primary"
|
variant="outline-primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleFollow(tag)}>
|
onClick={() => handleFollow(tag)}>
|
||||||
{tag.is_follower
|
{tag.is_follower
|
||||||
? t('button_following')
|
? t('button_following')
|
||||||
: t('button_follow')}
|
: t('button_follow')}
|
||||||
</Button>
|
</Button>
|
||||||
<span className="text-secondary fs-14 text-nowrap">
|
<span className="text-secondary fs-14 text-nowrap">
|
||||||
{formatCount(tag.question_count)} {t('tag_label')}
|
{formatCount(tag.question_count)} {t('tag_label')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
<div className="d-flex justify-content-center">
|
<div className="d-flex justify-content-center">
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPage={page}
|
currentPage={page}
|
||||||
totalSize={tags?.count || 0}
|
totalSize={tags?.count || 0}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC, useState, useEffect } from 'react';
|
import { FC, useState, useEffect } from 'react';
|
||||||
import { Container, Row, Col, Form, Table } from 'react-bootstrap';
|
import { Form, Table } from 'react-bootstrap';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
@ -79,54 +79,50 @@ const Index: FC = () => {
|
||||||
title: pageTitle,
|
title: pageTitle,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="py-3">
|
<div className="py-4 mb-5">
|
||||||
<Row className="py-3 justify-content-center">
|
<h5 className="mb-4">
|
||||||
<Col xxl={10}>
|
{timelineData?.object_info.object_type === 'tag'
|
||||||
<h5 className="mb-4">
|
? t('tag_title')
|
||||||
{timelineData?.object_info.object_type === 'tag'
|
: t('title')}{' '}
|
||||||
? t('tag_title')
|
<Link to={linkUrl}>{timelineData?.object_info?.title}</Link>
|
||||||
: t('title')}{' '}
|
</h5>
|
||||||
<Link to={linkUrl}>{timelineData?.object_info?.title}</Link>
|
{timelineData?.object_info.object_type !== 'tag' && (
|
||||||
</h5>
|
<Form.Check
|
||||||
{timelineData?.object_info.object_type !== 'tag' && (
|
className="mb-4"
|
||||||
<Form.Check
|
type="switch"
|
||||||
className="mb-4"
|
id="custom-switch"
|
||||||
type="switch"
|
label={t('show_votes')}
|
||||||
id="custom-switch"
|
checked={showVotes}
|
||||||
label={t('show_votes')}
|
onChange={(e) => handleSwitch(e.target.checked)}
|
||||||
checked={showVotes}
|
/>
|
||||||
onChange={(e) => handleSwitch(e.target.checked)}
|
)}
|
||||||
/>
|
<Table hover>
|
||||||
)}
|
<thead>
|
||||||
<Table hover>
|
<tr>
|
||||||
<thead>
|
<th style={{ width: '20%' }}>{t('datetime')}</th>
|
||||||
<tr>
|
<th style={{ width: '15%' }}>{t('type')}</th>
|
||||||
<th style={{ width: '20%' }}>{t('datetime')}</th>
|
<th style={{ width: '19%' }}>{t('by')}</th>
|
||||||
<th style={{ width: '15%' }}>{t('type')}</th>
|
<th>{t('comment')}</th>
|
||||||
<th style={{ width: '19%' }}>{t('by')}</th>
|
</tr>
|
||||||
<th>{t('comment')}</th>
|
</thead>
|
||||||
</tr>
|
<tbody>
|
||||||
</thead>
|
{timelineData?.timeline?.map((item) => {
|
||||||
<tbody>
|
return (
|
||||||
{timelineData?.timeline?.map((item) => {
|
<HistoryItem
|
||||||
return (
|
data={item}
|
||||||
<HistoryItem
|
objectInfo={timelineData?.object_info}
|
||||||
data={item}
|
key={item.activity_id}
|
||||||
objectInfo={timelineData?.object_info}
|
isAdmin={role_id === 2}
|
||||||
key={item.activity_id}
|
revisionList={revisionList}
|
||||||
isAdmin={role_id === 2}
|
/>
|
||||||
revisionList={revisionList}
|
);
|
||||||
/>
|
})}
|
||||||
);
|
</tbody>
|
||||||
})}
|
</Table>
|
||||||
</tbody>
|
{!isLoading && Number(timelineData?.timeline?.length) <= 0 && (
|
||||||
</Table>
|
<Empty>{t('no_data')}</Empty>
|
||||||
{!isLoading && Number(timelineData?.timeline?.length) <= 0 && (
|
)}
|
||||||
<Empty>{t('no_data')}</Empty>
|
</div>
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Container, Row, Col, ButtonGroup, Button } from 'react-bootstrap';
|
import { Row, Col, ButtonGroup, Button } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -74,62 +74,60 @@ const Notifications = () => {
|
||||||
title: t('notifications', { keyPrefix: 'page_title' }),
|
title: t('notifications', { keyPrefix: 'page_title' }),
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5">
|
<Row className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<Col className="flex-auto">
|
||||||
<Col xxl={7} lg={8} sm={12}>
|
<h3 className="mb-4">{t('title')}</h3>
|
||||||
<h3 className="mb-4">{t('title')}</h3>
|
<div className="d-flex justify-content-between mb-3">
|
||||||
<div className="d-flex justify-content-between mb-3">
|
<ButtonGroup size="sm">
|
||||||
<ButtonGroup size="sm">
|
|
||||||
<Button
|
|
||||||
as="a"
|
|
||||||
href="/users/notifications/inbox"
|
|
||||||
variant="outline-secondary"
|
|
||||||
active={type === 'inbox'}
|
|
||||||
onClick={(evt) => handleTypeChange(evt, 'inbox')}>
|
|
||||||
{t('inbox')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
as="a"
|
|
||||||
href="/users/notifications/achievement"
|
|
||||||
variant="outline-secondary"
|
|
||||||
active={type === 'achievement'}
|
|
||||||
onClick={(evt) => handleTypeChange(evt, 'achievement')}>
|
|
||||||
{t('achievement')}
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
as="a"
|
||||||
|
href="/users/notifications/inbox"
|
||||||
variant="outline-secondary"
|
variant="outline-secondary"
|
||||||
onClick={handleUnreadNotification}>
|
active={type === 'inbox'}
|
||||||
{t('all_read')}
|
onClick={(evt) => handleTypeChange(evt, 'inbox')}>
|
||||||
|
{t('inbox')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
as="a"
|
||||||
|
href="/users/notifications/achievement"
|
||||||
|
variant="outline-secondary"
|
||||||
|
active={type === 'achievement'}
|
||||||
|
onClick={(evt) => handleTypeChange(evt, 'achievement')}>
|
||||||
|
{t('achievement')}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline-secondary"
|
||||||
|
onClick={handleUnreadNotification}>
|
||||||
|
{t('all_read')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{type === 'inbox' && (
|
||||||
|
<Inbox
|
||||||
|
data={notificationData}
|
||||||
|
handleReadNotification={handleReadNotification}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === 'achievement' && (
|
||||||
|
<Achievements
|
||||||
|
data={notificationData}
|
||||||
|
handleReadNotification={handleReadNotification}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(data?.count || 0) > PAGE_SIZE * page && (
|
||||||
|
<div className="d-flex justify-content-center align-items-center py-3">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
className="btn-no-border"
|
||||||
|
onClick={handleLoadMore}>
|
||||||
|
{t('show_more')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{type === 'inbox' && (
|
)}
|
||||||
<Inbox
|
</Col>
|
||||||
data={notificationData}
|
<Col className="page-right-side" />
|
||||||
handleReadNotification={handleReadNotification}
|
</Row>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{type === 'achievement' && (
|
|
||||||
<Achievements
|
|
||||||
data={notificationData}
|
|
||||||
handleReadNotification={handleReadNotification}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{(data?.count || 0) > PAGE_SIZE * page && (
|
|
||||||
<div className="d-flex justify-content-center align-items-center py-3">
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
className="btn-no-border"
|
|
||||||
onClick={handleLoadMore}>
|
|
||||||
{t('show_more')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0" />
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Container, Row, Col } from 'react-bootstrap';
|
import { Row, Col } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams, useSearchParams, Link } from 'react-router-dom';
|
import { useParams, useSearchParams, Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -58,19 +58,19 @@ const Personal: FC = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="pt-4 mt-2 mb-5">
|
<div className="pt-4 mb-5">
|
||||||
<Row className="justify-content-center">
|
<Row>
|
||||||
{userInfo?.status !== 'normal' && userInfo?.status_msg && (
|
{userInfo?.status !== 'normal' && userInfo?.status_msg && (
|
||||||
<Alert data={userInfo?.status_msg} />
|
<Alert data={userInfo?.status_msg} />
|
||||||
)}
|
)}
|
||||||
<Col xxl={7} lg={8} sm={12}>
|
<Col className="flex-auto">
|
||||||
<UserInfo data={userInfo as UserInfoRes} />
|
<UserInfo data={userInfo as UserInfoRes} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col
|
<Col
|
||||||
xxl={3}
|
xxl={3}
|
||||||
lg={4}
|
lg={4}
|
||||||
sm={12}
|
sm={12}
|
||||||
className="d-flex justify-content-start justify-content-md-end">
|
className="page-right-side mt-4 mt-xl-0 d-flex justify-content-start justify-content-md-end">
|
||||||
{isSelf && (
|
{isSelf && (
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<Link
|
<Link
|
||||||
|
@ -83,11 +83,9 @@ const Personal: FC = () => {
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row className="justify-content-center">
|
<Row>
|
||||||
<Col xxl={10}>
|
<NavBar tabName={tabName} slug={username} isSelf={isSelf} />
|
||||||
<NavBar tabName={tabName} slug={username} isSelf={isSelf} />
|
<Col className="flex-auto">
|
||||||
</Col>
|
|
||||||
<Col xxl={7} lg={8} sm={12}>
|
|
||||||
<Overview
|
<Overview
|
||||||
visible={tabName === 'overview'}
|
visible={tabName === 'overview'}
|
||||||
introduction={userInfo?.bio_html || ''}
|
introduction={userInfo?.bio_html || ''}
|
||||||
|
@ -120,7 +118,7 @@ const Personal: FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
<Col className="page-right-side mt-4 mt-xl-0">
|
||||||
<h5 className="mb-3">{t('stats')}</h5>
|
<h5 className="mb-3">{t('stats')}</h5>
|
||||||
{userInfo?.created_at && (
|
{userInfo?.created_at && (
|
||||||
<>
|
<>
|
||||||
|
@ -137,7 +135,7 @@ const Personal: FC = () => {
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Personal;
|
export default Personal;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
.settings-nav {
|
||||||
|
flex: none;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-main {
|
||||||
|
flex: none;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lg
|
||||||
|
@media screen and (max-width: 1199.9px) {
|
||||||
|
.settings-nav {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
.settings-main {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sm
|
||||||
|
@media screen and (max-width: 767.9px) {
|
||||||
|
.settings-main {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC, memo } from 'react';
|
import { FC, memo } from 'react';
|
||||||
import { Container, Row, Col } from 'react-bootstrap';
|
import { Row, Col } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ import { usePageTags } from '@/hooks';
|
||||||
|
|
||||||
import Nav from './components/Nav';
|
import Nav from './components/Nav';
|
||||||
|
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
const { t } = useTranslation('translation', {
|
const { t } = useTranslation('translation', {
|
||||||
keyPrefix: 'settings.profile',
|
keyPrefix: 'settings.profile',
|
||||||
|
@ -16,23 +18,14 @@ const Index: FC = () => {
|
||||||
title: t('settings', { keyPrefix: 'page_title' }),
|
title: t('settings', { keyPrefix: 'page_title' }),
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Container className="mt-4 mb-5 pb-5">
|
<Row className="mt-4 mb-5 pb-5">
|
||||||
<Row className="justify-content-center">
|
<Col className="settings-nav mb-4">
|
||||||
<Col xxl={10} md={12}>
|
<Nav />
|
||||||
<h3 className="mb-4">{t('page_title', { keyPrefix: 'settings' })}</h3>
|
</Col>
|
||||||
</Col>
|
<Col className="settings-main">
|
||||||
</Row>
|
<Outlet />
|
||||||
|
</Col>
|
||||||
<Row>
|
</Row>
|
||||||
<Col xxl={1} />
|
|
||||||
<Col md={3} lg={2} className="mb-3">
|
|
||||||
<Nav />
|
|
||||||
</Col>
|
|
||||||
<Col md={9} lg={6}>
|
|
||||||
<Outlet />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Container, Row, Col } from 'react-bootstrap';
|
import { Row, Col } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
|
@ -22,61 +22,60 @@ const Users = () => {
|
||||||
|
|
||||||
const keys = Object.keys(users);
|
const keys = Object.keys(users);
|
||||||
return (
|
return (
|
||||||
<Container className="py-3 my-3">
|
<Row className="py-4 mb-4 d-flex justify-content-center">
|
||||||
<Row className="mb-4 d-flex justify-content-center">
|
<Col xxl={12}>
|
||||||
<Col xxl={10} sm={12}>
|
<h3 className="mb-4">{t('title')}</h3>
|
||||||
<h3 className="mb-4">{t('title')}</h3>
|
</Col>
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col xxl={10} sm={12}>
|
<Col xxl={12}>
|
||||||
{keys.map((key, index) => {
|
{keys.map((key, index) => {
|
||||||
if (users[key]?.length === 0) {
|
if (users[key]?.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Fragment key={key}>
|
<Fragment key={key}>
|
||||||
<Row className="mb-4">
|
<Row className="mb-4">
|
||||||
<Col>
|
<Col>
|
||||||
<h6 className="mb-0">{t(key)}</h6>
|
<h6 className="mb-0">{t(key)}</h6>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row className={index === keys.length - 1 ? '' : 'mb-4'}>
|
<Row className={index === keys.length - 1 ? '' : 'mb-4'}>
|
||||||
{users[key]?.map((user) => (
|
{users[key]?.map((user) => (
|
||||||
<Col
|
<Col
|
||||||
key={user.username}
|
key={user.username}
|
||||||
xs={12}
|
xl={3}
|
||||||
lg={3}
|
lg={4}
|
||||||
md={4}
|
md={4}
|
||||||
sm={6}
|
sm={6}
|
||||||
className="mb-4">
|
xs={12}
|
||||||
<div className="d-flex">
|
className="mb-4">
|
||||||
|
<div className="d-flex">
|
||||||
|
<Link to={`/users/${user.username}`}>
|
||||||
|
<Avatar
|
||||||
|
size="48px"
|
||||||
|
avatar={user?.avatar}
|
||||||
|
searchStr="s=96"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<div className="ms-2">
|
||||||
<Link to={`/users/${user.username}`}>
|
<Link to={`/users/${user.username}`}>
|
||||||
<Avatar
|
{user.display_name}
|
||||||
size="48px"
|
|
||||||
avatar={user?.avatar}
|
|
||||||
searchStr="s=96"
|
|
||||||
/>
|
|
||||||
</Link>
|
</Link>
|
||||||
<div className="ms-2">
|
<div className="text-secondary fs-14">
|
||||||
<Link to={`/users/${user.username}`}>
|
{key === 'users_with_the_most_vote'
|
||||||
{user.display_name}
|
? `${user.vote_count} ${t('votes')}`
|
||||||
</Link>
|
: `${user.rank} ${t('reputation')}`}
|
||||||
<div className="text-secondary fs-14">
|
|
||||||
{key === 'users_with_the_most_vote'
|
|
||||||
? `${user.vote_count} ${t('votes')}`
|
|
||||||
: `${user.rank} ${t('reputation')}`}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</div>
|
||||||
))}
|
</Col>
|
||||||
</Row>
|
))}
|
||||||
</Fragment>
|
</Row>
|
||||||
);
|
</Fragment>
|
||||||
})}
|
);
|
||||||
</Col>
|
})}
|
||||||
</Row>
|
</Col>
|
||||||
</Container>
|
</Row>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -40,127 +40,161 @@ const routes: RouteNode[] = [
|
||||||
children: [
|
children: [
|
||||||
// question and answer
|
// question and answer
|
||||||
{
|
{
|
||||||
index: true,
|
// side nav layout
|
||||||
page: 'pages/Questions',
|
path: '/',
|
||||||
},
|
page: 'pages/SideNavLayout',
|
||||||
{
|
|
||||||
path: 'questions',
|
|
||||||
page: 'pages/Questions',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'questions/ask',
|
|
||||||
page: 'pages/Questions/Ask',
|
|
||||||
guard: () => {
|
|
||||||
return guard.activated();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'questions/:qid',
|
|
||||||
page: 'pages/Questions/Detail',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'questions/:qid/:slugPermalink',
|
|
||||||
page: 'pages/Questions/Detail',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'questions/:qid/:slugPermalink/:aid',
|
|
||||||
page: 'pages/Questions/Detail',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'posts/:qid/edit',
|
|
||||||
page: 'pages/Questions/Ask',
|
|
||||||
guard: () => {
|
|
||||||
return guard.activated();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'posts/:qid/:aid/edit',
|
|
||||||
page: 'pages/Questions/EditAnswer',
|
|
||||||
loader: async ({ params }) => {
|
|
||||||
const ret = await editCheck(params.aid as string, true);
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
guard: (args) => {
|
|
||||||
return isEditable(args);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/search',
|
|
||||||
page: 'pages/Search',
|
|
||||||
},
|
|
||||||
// tags
|
|
||||||
{
|
|
||||||
path: 'tags',
|
|
||||||
page: 'pages/Tags',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tags/create',
|
|
||||||
page: 'pages/Tags/Create',
|
|
||||||
guard: () => {
|
|
||||||
return guard.isAdminOrModerator();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tags/:tagName',
|
|
||||||
page: 'pages/Tags/Detail',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tags/:tagName/info',
|
|
||||||
page: 'pages/Tags/Info',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tags/:tagId/edit',
|
|
||||||
page: 'pages/Tags/Edit',
|
|
||||||
guard: () => {
|
|
||||||
return guard.activated();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// for users
|
|
||||||
{
|
|
||||||
path: 'users',
|
|
||||||
page: 'pages/Users',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'users/:username',
|
|
||||||
page: 'pages/Users/Personal',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'users/:username/:tabName',
|
|
||||||
page: 'pages/Users/Personal',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'users/settings',
|
|
||||||
page: 'pages/Users/Settings',
|
|
||||||
guard: () => {
|
|
||||||
return guard.logged();
|
|
||||||
},
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
page: 'pages/Users/Settings/Profile',
|
page: 'pages/Questions',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'profile',
|
path: 'questions',
|
||||||
page: 'pages/Users/Settings/Profile',
|
page: 'pages/Questions',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'notify',
|
path: 'questions/ask',
|
||||||
page: 'pages/Users/Settings/Notification',
|
page: 'pages/Questions/Ask',
|
||||||
|
guard: () => {
|
||||||
|
return guard.activated();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'account',
|
path: 'posts/:qid/edit',
|
||||||
page: 'pages/Users/Settings/Account',
|
page: 'pages/Questions/Ask',
|
||||||
|
guard: () => {
|
||||||
|
return guard.activated();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'interface',
|
path: 'posts/:qid/:aid/edit',
|
||||||
page: 'pages/Users/Settings/Interface',
|
page: 'pages/Questions/EditAnswer',
|
||||||
|
loader: async ({ params }) => {
|
||||||
|
const ret = await editCheck(params.aid as string, true);
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
guard: (args) => {
|
||||||
|
return isEditable(args);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'questions/:qid',
|
||||||
|
page: 'pages/Questions/Detail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'questions/:qid/:slugPermalink',
|
||||||
|
page: 'pages/Questions/Detail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'questions/:qid/:slugPermalink/:aid',
|
||||||
|
page: 'pages/Questions/Detail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/search',
|
||||||
|
page: 'pages/Search',
|
||||||
|
},
|
||||||
|
// tags
|
||||||
|
{
|
||||||
|
path: 'tags',
|
||||||
|
page: 'pages/Tags',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags/create',
|
||||||
|
page: 'pages/Tags/Create',
|
||||||
|
guard: () => {
|
||||||
|
return guard.isAdminOrModerator();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags/:tagName',
|
||||||
|
page: 'pages/Tags/Detail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags/:tagName/info',
|
||||||
|
page: 'pages/Tags/Info',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags/:tagId/edit',
|
||||||
|
page: 'pages/Tags/Edit',
|
||||||
|
guard: () => {
|
||||||
|
return guard.activated();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// for users
|
||||||
|
{
|
||||||
|
path: 'users',
|
||||||
|
page: 'pages/Users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users/:username',
|
||||||
|
page: 'pages/Users/Personal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users/:username/:tabName',
|
||||||
|
page: 'pages/Users/Personal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users/settings',
|
||||||
|
page: 'pages/Users/Settings',
|
||||||
|
guard: () => {
|
||||||
|
return guard.logged();
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
page: 'pages/Users/Settings/Profile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'profile',
|
||||||
|
page: 'pages/Users/Settings/Profile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'notify',
|
||||||
|
page: 'pages/Users/Settings/Notification',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'account',
|
||||||
|
page: 'pages/Users/Settings/Account',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'interface',
|
||||||
|
page: 'pages/Users/Settings/Interface',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users/notifications/:type',
|
||||||
|
page: 'pages/Users/Notifications',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/posts/:qid/timeline',
|
||||||
|
page: 'pages/Timeline',
|
||||||
|
guard: () => {
|
||||||
|
return guard.logged();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/posts/:qid/:aid/timeline',
|
||||||
|
page: 'pages/Timeline',
|
||||||
|
guard: () => {
|
||||||
|
return guard.logged();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/tags/:tid/timeline',
|
||||||
|
page: 'pages/Timeline',
|
||||||
|
guard: () => {
|
||||||
|
return guard.logged();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// for review
|
||||||
|
{
|
||||||
|
path: 'review',
|
||||||
|
page: 'pages/Review',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'users/notifications/:type',
|
|
||||||
page: 'pages/Users/Notifications',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'users/login',
|
path: 'users/login',
|
||||||
page: 'pages/Users/Login',
|
page: 'pages/Users/Login',
|
||||||
|
@ -243,27 +277,6 @@ const routes: RouteNode[] = [
|
||||||
path: '/users/auth-landing',
|
path: '/users/auth-landing',
|
||||||
page: 'pages/Users/AuthCallback',
|
page: 'pages/Users/AuthCallback',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/posts/:qid/timeline',
|
|
||||||
page: 'pages/Timeline',
|
|
||||||
guard: () => {
|
|
||||||
return guard.logged();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/posts/:qid/:aid/timeline',
|
|
||||||
page: 'pages/Timeline',
|
|
||||||
guard: () => {
|
|
||||||
return guard.logged();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/tags/:tid/timeline',
|
|
||||||
page: 'pages/Timeline',
|
|
||||||
guard: () => {
|
|
||||||
return guard.logged();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// for admin
|
// for admin
|
||||||
{
|
{
|
||||||
path: 'admin',
|
path: 'admin',
|
||||||
|
@ -374,11 +387,6 @@ const routes: RouteNode[] = [
|
||||||
path: '/user-center/auth-failed',
|
path: '/user-center/auth-failed',
|
||||||
page: 'pages/UserCenter/AuthFailed',
|
page: 'pages/UserCenter/AuthFailed',
|
||||||
},
|
},
|
||||||
// for review
|
|
||||||
{
|
|
||||||
path: 'review',
|
|
||||||
page: 'pages/Review',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
page: 'pages/404',
|
page: 'pages/404',
|
||||||
|
|
|
@ -11,6 +11,7 @@ import customizeStore from './customize';
|
||||||
import themeSettingStore from './themeSetting';
|
import themeSettingStore from './themeSetting';
|
||||||
import loginToContinueStore from './loginToContinue';
|
import loginToContinueStore from './loginToContinue';
|
||||||
import errorCodeStore from './errorCode';
|
import errorCodeStore from './errorCode';
|
||||||
|
import sideNavStore from './sideNav';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
toastStore,
|
toastStore,
|
||||||
|
@ -26,4 +27,5 @@ export {
|
||||||
loginToContinueStore,
|
loginToContinueStore,
|
||||||
errorCodeStore,
|
errorCodeStore,
|
||||||
userCenterStore,
|
userCenterStore,
|
||||||
|
sideNavStore,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import create from 'zustand';
|
||||||
|
|
||||||
|
type reviewData = {
|
||||||
|
can_revision: boolean;
|
||||||
|
revision: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ErrorCodeType {
|
||||||
|
visible: boolean;
|
||||||
|
can_revision: boolean;
|
||||||
|
revision: number;
|
||||||
|
updateVisible: () => void;
|
||||||
|
updateReiview: (params: reviewData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Index = create<ErrorCodeType>((set) => ({
|
||||||
|
visible: false,
|
||||||
|
can_revision: false,
|
||||||
|
revision: 0,
|
||||||
|
updateVisible: () => {
|
||||||
|
set((state) => {
|
||||||
|
return { visible: !state.visible };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateReiview: (params: reviewData) => {
|
||||||
|
set(() => {
|
||||||
|
return { ...params };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default Index;
|
Loading…
Reference in New Issue