fix(ui): resolve conflict

This commit is contained in:
shuai 2023-01-16 18:38:27 +08:00
commit b35d2564b5
38 changed files with 287 additions and 107 deletions

View File

@ -608,7 +608,8 @@ ui:
msg:
empty: Cannot be empty.
login:
page_title: Welcome to Answer
page_title: Welcome to {{site_name}}
login_to_continue: Log in to continue
info_sign: Don't have an account? <1>Sign up</1>
info_login: Already have an account? <1>Log in</1>
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
@ -1288,14 +1289,14 @@ ui:
branding:
page_title: Branding
logo:
label: Logo
label: Logo (optional)
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
square_icon:
label: Square Icon
label: Square Icon (optional)
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:

View File

@ -71,7 +71,7 @@ If you would like to help us with the i18n translation, please visit [Answer@Cro
├── common (project information/data defined here)
├── components (all components of the project)
├── hooks (all hooks of the project)
├── i18n (Initialize the front-end i18n)
├── i18n (Used only to initialize the front-end i18n tool)
├── pages (all pages of the project)
├── router (Project routing definition)
├── services (all data api of the project)

View File

@ -1,3 +1,5 @@
$link-hover-decoration: none;
$enable-negative-margins: true;
$blue: #0033FF !default;
$placeholder-opacity-max: .2;
$placeholder-opacity-min: .1;

View File

@ -104,6 +104,7 @@ export interface ModifyUserReq {
}
export interface UserInfoBase {
id?: string;
avatar: any;
username: string;
display_name: string;

View File

@ -27,7 +27,6 @@ import './index.scss';
const Comment = ({ objectId, mode, commentId }) => {
const pageUsers = usePageUsers();
const [pageIndex, setPageIndex] = useState(0);
const [comments, setComments] = useState<any>([]);
const [visibleComment, setVisibleComment] = useState(false);
const pageSize = pageIndex === 0 ? 3 : 15;
const { data, mutate } = useQueryComments({
@ -36,6 +35,7 @@ const Comment = ({ objectId, mode, commentId }) => {
page: pageIndex,
page_size: pageSize,
});
const [comments, setComments] = useState<any>([]);
const reportModal = useReportModal();
@ -75,6 +75,9 @@ const Comment = ({ objectId, mode, commentId }) => {
}, [data]);
const handleReply = (id) => {
if (!tryNormalLogged(true)) {
return;
}
setComments(
comments.map((item) => {
if (item.comment_id === id) {
@ -100,6 +103,9 @@ const Comment = ({ objectId, mode, commentId }) => {
const users = matchedUsers(item.value);
const userNames = unionBy(users.map((user) => user.userName));
const html = marked.parse(parseUserInfo(item.value));
if (!item.value || !html) {
return;
}
const params = {
object_id: objectId,
original_text: item.value,
@ -164,9 +170,8 @@ const Comment = ({ objectId, mode, commentId }) => {
deleteComment(id).then(() => {
if (pageIndex === 0) {
mutate();
} else {
setComments(comments.filter((item) => item.comment_id !== id));
}
setComments(comments.filter((item) => item.comment_id !== id));
});
},
});
@ -303,7 +308,9 @@ const Comment = ({ objectId, mode, commentId }) => {
variant="link"
className="p-0 fs-14 btn-no-border"
onClick={() => {
setVisibleComment(!visibleComment);
if (tryNormalLogged(true)) {
setVisibleComment(!visibleComment);
}
}}>
{t('btn_add_comment')}
</Button>

View File

@ -251,7 +251,7 @@ const Image: FC<IEditorContext> = ({ editor }) => {
<Form.Group controlId="editor.imgDescription" className="mb-3">
<Form.Label>
{t('image.form_image.fields.description.label')}
{t('image.form_image.fields.desc.label')}
</Form.Label>
<Form.Control
type="text"

View File

@ -7,28 +7,31 @@ import {
useImperativeHandle,
} from 'react';
import { marked } from 'marked';
import { markdownToHtml } from '@/services';
import { htmlRender } from './utils';
let scrollTop = 0;
marked.setOptions({
breaks: true,
});
let renderTimer;
const Index = ({ value }, ref) => {
const [html, setHtml] = useState('');
const previewRef = useRef<HTMLDivElement>(null);
const renderMarkdown = (markdown) => {
clearTimeout(renderTimer);
const timeout = renderTimer ? 1000 : 0;
renderTimer = setTimeout(() => {
markdownToHtml(markdown).then((resp) => {
scrollTop = previewRef.current?.scrollTop || 0;
setHtml(resp);
});
}, timeout);
};
useEffect(() => {
const previewHtml = marked(value).replace(
/<img/gi,
'<img referrerpolicy="no-referrer"',
);
scrollTop = previewRef.current?.scrollTop || 0;
setHtml(previewHtml);
renderMarkdown(value);
}, [value]);
useEffect(() => {
if (!html) {
return;

View File

@ -56,6 +56,7 @@ const ToolItem: FC<IProps> = (props) => {
disable ? 'disabled' : ''
} `}
disabled={disable}
tabIndex={-1}
onClick={(e) => {
e.preventDefault();
if (typeof onClick === 'function') {

View File

@ -5,12 +5,10 @@ import { Trans } from 'react-i18next';
import dayjs from 'dayjs';
import { siteInfoStore } from '@/stores';
import { DEFAULT_SITE_NAME } from '@/common/constants';
const Index = () => {
const fullYear = dayjs().format('YYYY');
const siteName =
siteInfoStore((state) => state.siteInfo.name) || DEFAULT_SITE_NAME;
const siteName = siteInfoStore((state) => state.siteInfo.name);
const cc = `${fullYear} ${siteName}`;
return (
<footer className="bg-light py-3">

View File

@ -28,7 +28,6 @@ import {
themeSettingStore,
} from '@/stores';
import { logout, useQueryNotificationStatus } from '@/services';
import { DEFAULT_SITE_NAME } from '@/common/constants';
import NavItems from './components/NavItems';
@ -61,6 +60,7 @@ const Header: FC = () => {
const handleLogout = async () => {
await logout();
clearUserStore();
window.location.replace(window.location.href);
};
const onLoginClick = (evt) => {
evt.preventDefault();
@ -121,7 +121,7 @@ const Header: FC = () => {
/>
</>
) : (
<span>{siteInfo.name || DEFAULT_SITE_NAME}</span>
<span>{siteInfo.name}</span>
)}
</Navbar.Brand>

View File

@ -0,0 +1,56 @@
import React from 'react';
import { Modal } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { loginToContinueStore, siteInfoStore } from '@/stores';
interface IProps {
visible: boolean;
}
const Index: React.FC<IProps> = ({ visible = false }) => {
const { t } = useTranslation('translation', { keyPrefix: 'login' });
const { update: updateStore } = loginToContinueStore();
const { siteInfo } = siteInfoStore((_) => _);
const closeModal = () => {
updateStore({ show: false });
};
const linkClick = (evt) => {
evt.stopPropagation();
closeModal();
};
return (
<Modal
title="LoginToContinue"
show={visible}
onHide={closeModal}
centered
fullscreen="sm-down">
<Modal.Header closeButton>
<Modal.Title as="h5">{t('login_to_continue')}</Modal.Title>
</Modal.Header>
<Modal.Body className="p-5">
<div className="d-flex flex-column align-items-center text-center text-body">
<h3>{t('page_title', { site_name: siteInfo.name })}</h3>
<p>{siteInfo.description}</p>
</div>
<div className="d-grid gap-2">
<Link
to="/users/login"
className="btn btn-primary"
onClick={linkClick}>
{t('login', { keyPrefix: 'btns' })}
</Link>
<Link
to="/users/register"
className="btn btn-link"
onClick={linkClick}>
{t('signup', { keyPrefix: 'btns' })}
</Link>
</div>
</Modal.Body>
</Modal>
);
};
export default Index;

View File

@ -1,6 +1,7 @@
import DefaultModal from './Modal';
import confirm, { Config } from './Confirm';
import PicAuthCodeModal from './PicAuthCodeModal';
import LoginToContinueModal from './LoginToContinueModal';
type ModalType = typeof DefaultModal & {
confirm: (config: Config) => void;
@ -14,3 +15,4 @@ Modal.confirm = function (props: Config) {
export default Modal;
export { PicAuthCodeModal };
export { LoginToContinueModal };

View File

@ -41,10 +41,10 @@ const Index: FC<IProps> = ({
const navigate = useNavigate();
const reportModal = useReportModal();
const refershQuestion = () => {
const refreshQuestion = () => {
callback?.('default');
};
const closeModal = useReportModal(refershQuestion);
const closeModal = useReportModal(refreshQuestion);
const editUrl =
type === 'answer' ? `/posts/${qid}/${aid}/edit` : `/posts/${qid}/edit`;
@ -97,7 +97,7 @@ const Index: FC<IProps> = ({
deleteAnswer({
id: aid,
}).then(() => {
// refersh page
// refresh page
toast.onShow({
msg: t('tip_answer_deleted'),
variant: 'success',
@ -132,7 +132,7 @@ const Index: FC<IProps> = ({
msg: t('success', { keyPrefix: 'question_detail.reopen' }),
variant: 'success',
});
refershQuestion();
refreshQuestion();
});
},
});

View File

@ -1,10 +1,4 @@
.tag-selector-wrap {
.btn {
&.warning {
-webkit-animation: tag-input-warning 2s;
animation: tag-input-warning 2s;
}
}
.dropdown-menu {
min-width: 15rem;
}
@ -18,23 +12,5 @@
color: #212529;
background-color: #e9ecef;
}
@-webkit-keyframes tag-input-warning {
0% {
background-color: #ffc107;
}
100% {
background-color: #f8f9fa;
}
}
@keyframes tag-input-warning {
0% {
background-color: #ffc107;
}
100% {
background-color: #f8f9fa;
}
}
}

View File

@ -188,7 +188,7 @@ const TagSelector: FC<IProps> = ({
key={item.slug_name}
className={classNames(
'm-1 text-nowrap d-flex align-items-center',
index === repeatIndex && 'warning',
index === repeatIndex && 'bg-fade-out',
)}
variant={`outline-${
item.reserved ? 'danger' : item.recommend ? 'dark' : 'secondary'

View File

@ -14,6 +14,9 @@ const usePageUsers = () => {
getUsers,
setUsers: (data: Types.PageUser | Types.PageUser[]) => {
if (data instanceof Array) {
if (data.length === 0) {
return;
}
setUsers(uniqBy([...users, ...data], 'userName'));
globalUsers = uniqBy([...globalUsers, ...data], 'userName');
} else {

View File

@ -18,10 +18,6 @@
}
}
:root {
overflow-y: scroll;
}
html,
body {
padding: 0;
@ -300,7 +296,7 @@ a {
}
}
.bg-fade-out {
animation: 2s ease 1s bg-fade-out;
animation: bg-fade-out 2s ease 0.3s;
}

View File

@ -38,6 +38,7 @@ const Index: FC = () => {
'ui:widget': 'textarea',
'ui:options': {
rows: 10,
className: 'font-monospace',
},
},
};

View File

@ -18,7 +18,7 @@ const Index: FC<Props> = ({ visible, siteUrl = '' }) => {
<p>
<Trans i18nKey="install.ready_description">
If you ever feel like changing more settings, visit
<a href={`${siteUrl}/users/login`}>admin section</a>; find it in the
<a href={`${siteUrl}/users/login`}> admin section</a>; find it in the
site menu.
</Trans>
</p>

View File

@ -23,7 +23,7 @@ const Index: FC<Props> = ({ visible, errorMsg, nextCallback }) => {
<div className="fmt">
<p>
<Trans
i18nKey="install.config_yaml.description"
i18nKey="install.config_yaml.desc"
components={{ 1: <code /> }}
/>
</p>

View File

@ -4,7 +4,7 @@ import { HelmetProvider } from 'react-helmet-async';
import { SWRConfig } from 'swr';
import { toastStore } from '@/stores';
import { toastStore, loginToContinueStore } from '@/stores';
import {
Header,
Footer,
@ -13,13 +13,14 @@ import {
CustomizeTheme,
PageTags,
} from '@/components';
import { LoginToContinueModal } from '@/components/Modal';
const Layout: FC = () => {
const { msg: toastMsg, variant, clear: toastClear } = toastStore();
const closeToast = () => {
toastClear();
};
const { show: showLoginToContinueModal } = loginToContinueStore();
return (
<HelmetProvider>
<PageTags />
@ -35,6 +36,7 @@ const Layout: FC = () => {
<Toast msg={toastMsg} variant={variant} onClose={closeToast} />
<Footer />
<Customize />
<LoginToContinueModal visible={showLoginToContinueModal} />
</SWRConfig>
</HelmetProvider>
);

View File

@ -54,14 +54,18 @@ const Index: FC<Props> = ({
if (!answerRef?.current) {
return;
}
htmlRender(answerRef.current.querySelector('.fmt'));
if (aid === data.id) {
setTimeout(() => {
const element = answerRef.current;
scrollTop(element);
if (!searchParams.get('commentId')) {
bgFadeOut(answerRef.current);
}
}, 100);
}
htmlRender(answerRef.current.querySelector('.fmt'));
bgFadeOut(answerRef.current);
}, [data.id, answerRef.current]);
if (!data?.id) {
return null;

View File

@ -12,10 +12,54 @@ const Index: FC = () => {
style={{ height: '24px' }}
/>
<div
className="placeholder w-100 align-top"
style={{ height: '450px', marginBottom: '2rem' }}
/>
<div style={{ marginBottom: '2rem' }}>
<p>
<span
className="placeholder w-100 d-block align-top mb-1"
style={{ height: '24px' }}
/>
<span
className="placeholder w-100 d-block align-top mb-1"
style={{ height: '24px' }}
/>
<span
className="placeholder w-100 d-block align-top mb-1"
style={{ height: '24px' }}
/>
<span
className="placeholder w-100 d-block align-top mb-1"
style={{ height: '24px' }}
/>
<span
className="placeholder w-75 d-block align-top"
style={{ height: '24px' }}
/>
</p>
<p>
<span
className="placeholder w-100 d-block align-top mb-1"
style={{ height: '24px' }}
/>
<span
className="placeholder w-100 d-block align-top mb-1"
style={{ height: '24px' }}
/>
<span
className="placeholder w-100 d-block align-top mb-1"
style={{ height: '24px' }}
/>
<span
className="placeholder w-100 d-block align-top mb-1"
style={{ height: '24px' }}
/>
<span
className="placeholder w-50 d-block align-top"
style={{ height: '24px' }}
/>
</p>
</div>
<div className="d-flex">
<div

View File

@ -12,7 +12,7 @@ import {
FormatTime,
htmlRender,
} from '@/components';
import { formatCount } from '@/utils';
import { formatCount, guard } from '@/utils';
import { following } from '@/services';
import { pathFactory } from '@/router/pathFactory';
@ -33,6 +33,9 @@ const Index: FC<Props> = ({ data, initPage, hasAnswer, isLogged }) => {
const handleFollow = (e) => {
e.preventDefault();
if (!guard.tryNormalLogged(true)) {
return;
}
following({
object_id: data?.id,
is_cancel: followed,

View File

@ -8,6 +8,7 @@ import classNames from 'classnames';
import { Editor, Modal, TextArea } from '@/components';
import { FormDataType } from '@/common/interface';
import { postAnswer } from '@/services';
import { guard } from '@/utils';
interface Props {
visible?: boolean;
@ -31,9 +32,13 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
},
});
const [showEditor, setShowEditor] = useState<boolean>(visible);
const [focusType, setForceType] = useState('');
const [focusType, setFocusType] = useState('');
const [editorFocusState, setEditorFocusState] = useState(false);
const handleSubmit = () => {
if (!guard.tryNormalLogged(true)) {
return;
}
if (!formData.content.value) {
setFormData({
content: {
@ -62,6 +67,9 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
};
const clickBtn = () => {
if (!guard.tryNormalLogged(true)) {
return;
}
if (data?.answered && !showEditor) {
Modal.confirm({
title: t('confirm_title'),
@ -81,9 +89,14 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
handleSubmit();
};
const handleFocusForTextArea = () => {
setForceType('answer');
const handleFocusForTextArea = (evt) => {
if (!guard.tryNormalLogged(true)) {
evt.currentTarget.blur();
return;
}
setFocusType('answer');
setShowEditor(true);
setEditorFocusState(true);
};
return (
@ -114,7 +127,7 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
focusType === 'answer' && 'focus',
)}
value={formData.content.value}
autoFocus
autoFocus={editorFocusState}
onChange={(val) => {
setFormData({
content: {
@ -125,10 +138,10 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
});
}}
onFocus={() => {
setForceType('answer');
setFocusType('answer');
}}
onBlur={() => {
setForceType('');
setFocusType('');
}}
/>
)}

View File

@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
import Pattern from '@/common/pattern';
import { Pagination } from '@/components';
import { loggedUserInfoStore, toastStore } from '@/stores';
import { scrollTop, bgFadeOut } from '@/utils';
import { scrollTop } from '@/utils';
import { usePageTags, usePageUsers } from '@/hooks';
import type {
ListResult,
@ -81,7 +81,6 @@ const Index = () => {
// scroll into view;
const element = document.getElementById('answerHeader');
scrollTop(element);
bgFadeOut(element);
}
res.list.forEach((item) => {
@ -105,9 +104,24 @@ const Index = () => {
const res = await questionDetail(qid);
if (res) {
setUsers([
res.user_info,
res?.update_user_info,
res?.last_answered_user_info,
{
id: res.user_info.id,
displayName: res.user_info.display_name,
userName: res.user_info.username,
avatar_url: res.user_info.avatar,
},
{
id: res?.update_user_info?.id,
displayName: res?.update_user_info?.display_name,
userName: res?.update_user_info?.username,
avatar_url: res?.update_user_info?.avatar,
},
{
id: res?.last_answered_user_info?.id,
displayName: res?.last_answered_user_info?.display_name,
userName: res?.last_answered_user_info?.username,
avatar_url: res?.last_answered_user_info?.avatar,
},
]);
setQuestion(res);
}
@ -124,7 +138,6 @@ const Index = () => {
}, 1000);
return;
}
if (type === 'default') {
window.scrollTo(0, 0);
getDetail();
@ -151,6 +164,7 @@ const Index = () => {
if (!qid) {
return;
}
window.scrollTo(0, 0);
getDetail();
requestAnswers();
}, [qid]);
@ -214,7 +228,6 @@ const Index = () => {
{!isLoading && !question?.operation?.operation_type && (
<WriteAnswer
visible={answers.count === 0}
data={{
qid,
answered: question?.answered,

View File

@ -1,17 +1,17 @@
import { FC } from 'react';
import { Container, Row, Col } from 'react-bootstrap';
import { useMatch } from 'react-router-dom';
import { useMatch, Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { usePageTags } from '@/hooks';
import { FollowingTags } from '@/components';
import QuestionList from '@/components/QuestionList';
import HotQuestions from '@/components/HotQuestions';
import { siteInfoStore } from '@/stores';
import { siteInfoStore, loggedUserInfoStore } from '@/stores';
const Questions: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'question' });
const { user: loggedUser } = loggedUserInfoStore((_) => _);
const isIndexPage = useMatch('/');
let pageTitle = t('questions', { keyPrefix: 'page_title' });
let slogan = '';
@ -29,7 +29,26 @@ const Questions: FC = () => {
<QuestionList source="questions" />
</Col>
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
<FollowingTags />
{!loggedUser.access_token && (
<div className="card mb-4">
<div className="card-body">
<h5 className="card-title">
{t('page_title', {
keyPrefix: 'login',
site_name: siteInfo.name,
})}
</h5>
<p className="card-text">{siteInfo.description}</p>
<Link to="/users/login" className="btn btn-primary">
{t('login', { keyPrefix: 'btns' })}
</Link>
<Link to="/users/register" className="btn btn-link ms-2">
{t('signup', { keyPrefix: 'btns' })}
</Link>
</div>
</div>
)}
{loggedUser.access_token && <FollowingTags />}
<HotQuestions />
</Col>
</Row>

View File

@ -9,7 +9,7 @@ import { FollowingTags } from '@/components';
import { useTagInfo, useFollow, useQuerySynonymsTags } from '@/services';
import QuestionList from '@/components/QuestionList';
import HotQuestions from '@/components/HotQuestions';
import { escapeRemove } from '@/utils';
import { escapeRemove, guard } from '@/utils';
import { pathFactory } from '@/router/pathFactory';
const Questions: FC = () => {
@ -23,6 +23,9 @@ const Questions: FC = () => {
const { data: followResp } = useFollow(tagFollow);
const { data: synonymsRes } = useQuerySynonymsTags(tagInfo?.tag_id);
const toggleFollow = () => {
if (!guard.tryNormalLogged(true)) {
return;
}
setTagFollow({
is_cancel: tagInfo.is_follower,
object_id: tagInfo.tag_id,

View File

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import { usePageTags } from '@/hooks';
import { Tag, Pagination, QueryGroup, TagsLoader } from '@/components';
import { formatCount } from '@/utils';
import { tryNormalLogged } from '@/utils/guard';
import { useQueryTags, following } from '@/services';
const sortBtns = ['popular', 'name', 'newest'];
@ -35,6 +36,9 @@ const Tags = () => {
};
const handleFollow = (tag) => {
if (!tryNormalLogged(true)) {
return;
}
following({
object_id: tag.tag_id,
is_cancel: tag.is_follower,

View File

@ -13,7 +13,11 @@ import type {
} from '@/common/interface';
import { Unactivate } from '@/components';
import { PluginOauth } from '@/plugins';
import { loggedUserInfoStore, loginSettingStore } from '@/stores';
import {
loggedUserInfoStore,
loginSettingStore,
siteInfoStore,
} from '@/stores';
import { guard, floppyNavigation, handleFormError } from '@/utils';
import { login, checkImgCode } from '@/services';
import { PicAuthCodeModal } from '@/components/Modal';
@ -24,8 +28,8 @@ const Index: React.FC = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const [refresh, setRefresh] = useState(0);
const updateUser = loggedUserInfoStore((state) => state.update);
const storeUser = loggedUserInfoStore((state) => state.user);
const { name: siteName } = siteInfoStore((_) => _.siteInfo);
const { user: storeUser, update: updateUser } = loggedUserInfoStore((_) => _);
const loginSetting = loginSettingStore((state) => state.login);
const [formData, setFormData] = useState<FormDataType>({
e_mail: {
@ -171,7 +175,9 @@ const Index: React.FC = () => {
});
return (
<Container style={{ paddingTop: '4rem', paddingBottom: '5rem' }}>
<h3 className="text-center mb-5">{t('page_title')}</h3>
<h3 className="text-center mb-5">
{t('page_title', { site_name: siteName })}
</h3>
{step === 1 && (
<Col className="mx-auto" md={3}>
<PluginOauth className="mb-5" />

View File

@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next';
import { usePageTags } from '@/hooks';
import { Unactivate } from '@/components';
import { siteInfoStore } from '@/stores';
import SignUpForm from './components/SignUpForm';
const Index: React.FC = () => {
const [showForm, setShowForm] = useState(true);
const { t } = useTranslation('translation', { keyPrefix: 'login' });
const { name: siteName } = siteInfoStore((_) => _.siteInfo);
const onStep = () => {
setShowForm((bol) => !bol);
};
@ -19,7 +20,9 @@ const Index: React.FC = () => {
});
return (
<Container style={{ paddingTop: '4rem', paddingBottom: '5rem' }}>
<h3 className="text-center mb-5">{t('page_title')}</h3>
<h3 className="text-center mb-5">
{t('page_title', { site_name: siteName })}
</h3>
{showForm ? (
<SignUpForm callback={onStep} />
) : (

View File

@ -3,7 +3,6 @@ import { useLocation, useNavigate } from 'react-router-dom';
import { floppyNavigation } from '@/utils';
import { TGuardFunc } from '@/utils/guard';
import { loggedUserInfoStore } from '@/stores';
const Index: FC<{
children: ReactNode;
@ -17,8 +16,7 @@ const Index: FC<{
}) => {
const navigate = useNavigate();
const location = useLocation();
const { user } = loggedUserInfoStore();
const runGuards = () => {
const callGuards = () => {
if (onEnter) {
const gr = onEnter();
const redirectUrl = gr.redirect;
@ -30,13 +28,8 @@ const Index: FC<{
}
};
useEffect(() => {
runGuards();
callGuards();
}, [location]);
useEffect(() => {
if (!user.access_token) {
runGuards();
}
}, [user]);
return (
<>
{/* Route Guard */}

View File

@ -6,14 +6,12 @@ const tagLanding = (slugName: string) => {
if (!slugName) {
return '/tags';
}
slugName = slugName.toLowerCase();
return urlcat('/tags/:slugName', { slugName });
};
const tagInfo = (slugName: string) => {
if (!slugName) {
return '/tags';
}
slugName = slugName.toLowerCase();
return urlcat('/tags/:slugName/info', { slugName });
};
const tagEdit = (tagId: string) => {

View File

@ -268,3 +268,8 @@ export const unsubscribe = (code: string) => {
const apiUrl = '/answer/api/v1/user/email/notification';
return request.put(apiUrl, { code });
};
export const markdownToHtml = (content: string) => {
const apiUrl = '/answer/api/v1/post/render';
return request.post(apiUrl, { content });
};

View File

@ -9,6 +9,7 @@ import brandingStore from './branding';
import pageTagStore from './pageTags';
import customizeStore from './customize';
import themeSettingStore from './themeSetting';
import loginToContinueStore from './loginToContinue';
export {
toastStore,
@ -21,4 +22,5 @@ export {
customizeStore,
themeSettingStore,
seoSettingStore,
loginToContinueStore,
};

View File

@ -0,0 +1,16 @@
import create from 'zustand';
interface IProps {
show: boolean;
update: (params: { show: boolean }) => void;
}
const loginToContinueStore = create<IProps>((set) => ({
show: false,
update: (params) =>
set({
...params,
}),
}));
export default loginToContinueStore;

View File

@ -1,6 +1,7 @@
import create from 'zustand';
import { AdminSettingsGeneral } from '@/common/interface';
import { DEFAULT_SITE_NAME } from '@/common/constants';
interface SiteInfoType {
siteInfo: AdminSettingsGeneral;
@ -9,7 +10,7 @@ interface SiteInfoType {
const siteInfo = create<SiteInfoType>((set) => ({
siteInfo: {
name: '',
name: DEFAULT_SITE_NAME,
description: '',
short_description: '',
site_url: '',
@ -19,6 +20,9 @@ const siteInfo = create<SiteInfoType>((set) => ({
update: (params) =>
set((_) => {
const o = { ..._.siteInfo, ...params };
if (!o.name) {
o.name = DEFAULT_SITE_NAME;
}
return {
siteInfo: o,
};

View File

@ -8,6 +8,7 @@ import {
customizeStore,
themeSettingStore,
seoSettingStore,
loginToContinueStore,
} from '@/stores';
import { RouteAlias } from '@/router/alias';
import Storage from '@/utils/storage';
@ -225,7 +226,7 @@ export const tryNormalLogged = (canNavigate: boolean = false) => {
// must assert logged state first and return
if (!us.isLogged) {
if (canNavigate) {
floppyNavigation.navigateToLogin();
loginToContinueStore.getState().update({ show: true });
}
return false;
}