mirror of https://gitee.com/answerdev/answer.git
feat(permalink): permalink and permalink general setting done
This commit is contained in:
parent
1a12be4fd1
commit
ceb8f099bf
|
@ -279,7 +279,12 @@ export interface AdminSettingsGeneral {
|
|||
description: string;
|
||||
site_url: string;
|
||||
contact_email: string;
|
||||
permalink: boolean;
|
||||
/**
|
||||
* 0: not set
|
||||
* 1:with title
|
||||
* 2: no title
|
||||
*/
|
||||
permalink: number;
|
||||
}
|
||||
|
||||
export interface HeadInfo {
|
||||
|
|
|
@ -14,13 +14,13 @@ export interface JSONSchema {
|
|||
required?: string[];
|
||||
properties: {
|
||||
[key: string]: {
|
||||
type: 'string' | 'boolean';
|
||||
type: 'string' | 'boolean' | 'number';
|
||||
title: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
enum?: Array<string | boolean>;
|
||||
enum?: Array<string | boolean | number>;
|
||||
enumNames?: string[];
|
||||
default?: string | boolean;
|
||||
default?: string | boolean | number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -477,8 +477,10 @@ const SchemaForm: FC<IProps> = ({
|
|||
export const initFormData = (schema: JSONSchema): Type.FormDataType => {
|
||||
const formData: Type.FormDataType = {};
|
||||
Object.keys(schema.properties).forEach((key) => {
|
||||
const v = schema.properties[key]?.default;
|
||||
// TODO: set default value by property type
|
||||
formData[key] = {
|
||||
value: '',
|
||||
value: typeof v !== 'undefined' ? v : '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@ import { FacebookShareButton, TwitterShareButton } from 'next-share';
|
|||
import copy from 'copy-to-clipboard';
|
||||
|
||||
import { loggedUserInfoStore } from '@/stores';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
interface IProps {
|
||||
type: 'answer' | 'question';
|
||||
|
@ -23,8 +24,12 @@ const Index: FC<IProps> = ({ type, qid, aid, title }) => {
|
|||
|
||||
let baseUrl =
|
||||
type === 'question'
|
||||
? `${window.location.origin}/questions/${qid}`
|
||||
: `${window.location.origin}/questions/${qid}/${aid}`;
|
||||
? `${window.location.origin}${pathFactory.questionLanding(qid, title)}`
|
||||
: `${window.location.origin}${pathFactory.answerLanding({
|
||||
questionId: qid,
|
||||
questionTitle: title,
|
||||
answerId: aid,
|
||||
})}`;
|
||||
if (user.id) {
|
||||
baseUrl = `${baseUrl}?shareUserId=${user.username}`;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ const Index: FC<IProps> = ({
|
|||
className = '',
|
||||
textClassName = '',
|
||||
}) => {
|
||||
href ||= pathFactory.tagLanding(data);
|
||||
href ||= pathFactory.tagLanding(data?.slug_name);
|
||||
|
||||
return (
|
||||
<a
|
||||
|
|
|
@ -19,6 +19,7 @@ import { useEditStatusModal } from '@/hooks';
|
|||
import * as Type from '@/common/interface';
|
||||
import { useAnswerSearch, changeAnswerStatus } from '@/services';
|
||||
import { escapeRemove } from '@/utils';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
import '../index.scss';
|
||||
|
||||
|
@ -129,7 +130,11 @@ const Answers: FC = () => {
|
|||
<Stack>
|
||||
<Stack direction="horizontal" gap={2}>
|
||||
<a
|
||||
href={`/questions/${li.question_id}/${li.id}`}
|
||||
href={pathFactory.answerLanding({
|
||||
questionId: li.question_id,
|
||||
questionTitle: li.question_info.title,
|
||||
answerId: li.id,
|
||||
})}
|
||||
target="_blank"
|
||||
className="text-break text-wrap"
|
||||
rel="noreferrer">
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useReportModal } from '@/hooks';
|
|||
import * as Type from '@/common/interface';
|
||||
import { useFlagSearch } from '@/services';
|
||||
import { escapeRemove } from '@/utils';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
import '../index.scss';
|
||||
|
||||
|
@ -101,7 +102,10 @@ const Flags: FC = () => {
|
|||
</small>
|
||||
<BaseUserCard data={li.reported_user} className="mt-2" />
|
||||
<a
|
||||
href={`/questions/${li.question_id}`}
|
||||
href={pathFactory.questionLanding(
|
||||
li.question_id,
|
||||
li.title,
|
||||
)}
|
||||
target="_blank"
|
||||
className="text-wrap text-break mt-2"
|
||||
rel="noreferrer">
|
||||
|
|
|
@ -49,11 +49,12 @@ const General: FC = () => {
|
|||
description: t('contact_email.text'),
|
||||
},
|
||||
permalink: {
|
||||
type: 'boolean',
|
||||
type: 'number',
|
||||
title: t('permalink.label'),
|
||||
description: t('permalink.text'),
|
||||
enum: [true, false],
|
||||
enum: [1, 2],
|
||||
enumNames: ['/questions/123/post-title', '/questions/123'],
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -100,7 +101,6 @@ const General: FC = () => {
|
|||
const [formData, setFormData] = useState<Type.FormDataType>(
|
||||
initFormData(schema),
|
||||
);
|
||||
|
||||
const onSubmit = (evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
@ -110,7 +110,7 @@ const General: FC = () => {
|
|||
short_description: formData.short_description.value,
|
||||
site_url: formData.site_url.value,
|
||||
contact_email: formData.contact_email.value,
|
||||
permalink: formData.permalink.value,
|
||||
permalink: Number(formData.permalink.value),
|
||||
};
|
||||
|
||||
updateGeneralSetting(reqParams)
|
||||
|
@ -133,10 +133,13 @@ const General: FC = () => {
|
|||
if (!setting) {
|
||||
return;
|
||||
}
|
||||
const formMeta = {};
|
||||
Object.keys(setting).forEach((k) => {
|
||||
const formMeta: Type.FormDataType = {};
|
||||
Object.keys(formData).forEach((k) => {
|
||||
formMeta[k] = { ...formData[k], value: setting[k] };
|
||||
});
|
||||
if (formMeta.permalink.value !== 1 && formMeta.permalink.value !== 2) {
|
||||
formMeta.permalink.value = 1;
|
||||
}
|
||||
setFormData({ ...formData, ...formMeta });
|
||||
}, [setting]);
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import { ADMIN_LIST_STATUS } from '@/common/constants';
|
|||
import { useEditStatusModal, useReportModal } from '@/hooks';
|
||||
import * as Type from '@/common/interface';
|
||||
import { useQuestionSearch, changeQuestionStatus } from '@/services';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
import '../index.scss';
|
||||
|
||||
|
@ -138,7 +139,7 @@ const Questions: FC = () => {
|
|||
<tr key={li.id}>
|
||||
<td>
|
||||
<a
|
||||
href={`/questions/${li.id}`}
|
||||
href={pathFactory.questionLanding(li.id, li.title)}
|
||||
target="_blank"
|
||||
className="text-break text-wrap"
|
||||
rel="noreferrer">
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Accordion, ListGroup } from 'react-bootstrap';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Icon } from '@/components';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
@ -28,7 +29,7 @@ const SearchQuestion = ({ similarQuestions }) => {
|
|||
as="a"
|
||||
className="link-dark"
|
||||
key={item.id}
|
||||
href={`/questions/${item.id}`}
|
||||
href={pathFactory.questionLanding(item.id, item.title)}
|
||||
target="_blank">
|
||||
<span className="text-wrap text-break">
|
||||
{item.title}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
useQueryQuestionByTitle,
|
||||
} from '@/services';
|
||||
import { handleFormError } from '@/utils';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
import SearchQuestion from './components/SearchQuestion';
|
||||
|
||||
|
@ -235,7 +236,7 @@ const Ask = () => {
|
|||
edit_summary: formData.edit_summary.value,
|
||||
})
|
||||
.then(() => {
|
||||
navigate(`/questions/${qid}`);
|
||||
navigate(pathFactory.questionLanding(qid, params.title));
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
|
@ -260,7 +261,7 @@ const Ask = () => {
|
|||
html: editorRef2.current.getHtml(),
|
||||
})
|
||||
.then(() => {
|
||||
navigate(`/questions/${id}`);
|
||||
navigate(pathFactory.questionLanding(id, params.title));
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
|
@ -269,7 +270,7 @@ const Ask = () => {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
navigate(`/questions/${id}`);
|
||||
navigate(pathFactory.questionLanding(id, params.title));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from '@/components';
|
||||
import { formatCount } from '@/utils';
|
||||
import { following } from '@/services';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
interface Props {
|
||||
data: any;
|
||||
|
@ -57,10 +58,14 @@ const Index: FC<Props> = ({ data, initPage, hasAnswer, isLogged }) => {
|
|||
if (!data?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="h3 mb-3 text-wrap text-break">
|
||||
<Link className="link-dark" reloadDocument to={`/questions/${data.id}`}>
|
||||
<Link
|
||||
className="link-dark"
|
||||
reloadDocument
|
||||
to={pathFactory.questionLanding(data.id, data.title)}>
|
||||
{data.title}
|
||||
{data.status === 2
|
||||
? ` [${t('closed', { keyPrefix: 'question' })}]`
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import { Icon } from '@/components';
|
||||
import { useSimilarQuestion } from '@/services';
|
||||
import { loggedUserInfoStore } from '@/stores';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
@ -31,7 +32,7 @@ const Index: FC<Props> = ({ id }) => {
|
|||
action
|
||||
key={item.id}
|
||||
as={Link}
|
||||
to={`/questions/${item.id}`}>
|
||||
to={pathFactory.questionLanding(item.id, item.title)}>
|
||||
<div className="link-dark">{item.title}</div>
|
||||
{item.answer_count > 0 && (
|
||||
<div
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import dayjs from 'dayjs';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import { Editor, EditorRef, Icon, PageTitle } from '@/components';
|
||||
import type * as Type from '@/common/interface';
|
||||
import {
|
||||
|
@ -110,10 +111,15 @@ const Ask = () => {
|
|||
edit_summary: formData.description.value,
|
||||
};
|
||||
modifyAnswer(params).then(() => {
|
||||
navigate(`/questions/${qid}/${aid}`);
|
||||
navigate(
|
||||
pathFactory.answerLanding({
|
||||
questionId: qid,
|
||||
questionTitle: data?.question?.title,
|
||||
answerId: aid,
|
||||
}),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectedRevision = (e) => {
|
||||
const index = e.target.value;
|
||||
const revision = revisions[index];
|
||||
|
@ -136,7 +142,10 @@ const Ask = () => {
|
|||
</Row>
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
||||
<a href={`/questions/${qid}`} target="_blank" rel="noreferrer">
|
||||
<a
|
||||
href={pathFactory.questionLanding(qid, data?.question.title)}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
<h5 className="mb-3">{data?.question.title}</h5>
|
||||
</a>
|
||||
|
||||
|
|
|
@ -105,18 +105,18 @@ const Index: FC = () => {
|
|||
editBadge = t('question_edit');
|
||||
editSummary ||= t('edit_question');
|
||||
} else if (type === 'answer') {
|
||||
itemLink = pathFactory.answerLanding(
|
||||
itemLink = pathFactory.answerLanding({
|
||||
// @ts-ignore
|
||||
unreviewed_info.content.question_id,
|
||||
info.title,
|
||||
unreviewed_info.object_id,
|
||||
);
|
||||
questionId: unreviewed_info.content.question_id,
|
||||
questionTitle: info?.title,
|
||||
answerId: unreviewed_info.object_id,
|
||||
});
|
||||
itemTitle = info?.title;
|
||||
editBadge = t('answer_edit');
|
||||
editSummary ||= t('edit_answer');
|
||||
} else if (type === 'tag') {
|
||||
const tagInfo = unreviewed_info.content as Type.Tag;
|
||||
itemLink = pathFactory.tagLanding(tagInfo);
|
||||
itemLink = pathFactory.tagLanding(tagInfo.slug_name);
|
||||
itemTitle = tagInfo.display_name;
|
||||
editBadge = t('tag_edit');
|
||||
editSummary ||= t('edit_tag');
|
||||
|
|
|
@ -2,6 +2,7 @@ import { memo, FC } from 'react';
|
|||
import { ListGroupItem } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import { Icon, Tag, FormatTime, BaseUserCard } from '@/components';
|
||||
import type { SearchResItem } from '@/common/interface';
|
||||
import { escapeRemove } from '@/utils';
|
||||
|
@ -14,9 +15,13 @@ const Index: FC<Props> = ({ data }) => {
|
|||
if (!data?.object_type) {
|
||||
return null;
|
||||
}
|
||||
let itemUrl = `/questions/${data.object.id}`;
|
||||
if (data.object_type === 'answer') {
|
||||
itemUrl = `/questions/${data.object.question_id}/${data.object.id}`;
|
||||
let itemUrl = pathFactory.questionLanding(data.object.id, data.object.title);
|
||||
if (data.object_type === 'answer' && data.object.question_id) {
|
||||
itemUrl = pathFactory.answerLanding({
|
||||
questionId: data.object.question_id,
|
||||
questionTitle: data.object.title,
|
||||
answerId: data.object.id,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ListGroupItem className="py-3 px-0">
|
||||
|
|
|
@ -31,7 +31,9 @@ const Questions: FC = () => {
|
|||
if (tagResp) {
|
||||
const info = { ...tagResp };
|
||||
if (info.main_tag_slug_name) {
|
||||
navigate(pathFactory.tagLanding(info), { replace: true });
|
||||
navigate(pathFactory.tagLanding(info.main_tag_slug_name), {
|
||||
replace: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (followResp) {
|
||||
|
@ -63,7 +65,7 @@ const Questions: FC = () => {
|
|||
<div className="tag-box mb-5">
|
||||
<h3 className="mb-3">
|
||||
<Link
|
||||
to={pathFactory.tagLanding(tagInfo)}
|
||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||
replace
|
||||
className="link-dark">
|
||||
{tagInfo.display_name}
|
||||
|
|
|
@ -91,6 +91,7 @@ const TagIntroduction = () => {
|
|||
keyPrefix: 'page_title',
|
||||
})}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={pageTitle} />
|
||||
|
@ -99,7 +100,7 @@ const TagIntroduction = () => {
|
|||
<Col xxl={7} lg={8} sm={12}>
|
||||
<h3 className="mb-3">
|
||||
<Link
|
||||
to={pathFactory.tagLanding(tagInfo)}
|
||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||
replace
|
||||
className="link-dark">
|
||||
{tagInfo.display_name}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Container, Row, Col, Form, Table } from 'react-bootstrap';
|
|||
import { Link, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import { loggedUserInfoStore } from '@/stores';
|
||||
import { getTimelineData } from '@/services';
|
||||
import { PageTitle, Empty } from '@/components';
|
||||
|
@ -44,12 +45,19 @@ const Index: FC = () => {
|
|||
let linkUrl = '';
|
||||
let pageTitle = '';
|
||||
if (timelineData?.object_info.object_type === 'question') {
|
||||
linkUrl = `/questions/${timelineData?.object_info.question_id}`;
|
||||
linkUrl = pathFactory.questionLanding(
|
||||
timelineData?.object_info.question_id,
|
||||
timelineData?.object_info.title,
|
||||
);
|
||||
pageTitle = `${t('title_for_question')} ${timelineData?.object_info.title}`;
|
||||
}
|
||||
|
||||
if (timelineData?.object_info.object_type === 'answer') {
|
||||
linkUrl = `/questions/${timelineData?.object_info.question_id}/${timelineData?.object_info.answer_id}`;
|
||||
linkUrl = pathFactory.answerLanding({
|
||||
questionId: timelineData?.object_info.question_id,
|
||||
questionTitle: timelineData?.object_info.title,
|
||||
answerId: timelineData?.object_info.answer_id,
|
||||
});
|
||||
pageTitle = `${t('title_for_answer', {
|
||||
title: timelineData?.object_info.title,
|
||||
author: timelineData?.object_info.display_name,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Icon, FormatTime, Tag } from '@/components';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
|
@ -20,7 +21,11 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
<ListGroupItem className="py-3 px-0" key={item.answer_id}>
|
||||
<h6 className="mb-2">
|
||||
<a
|
||||
href={`/questions/${item.question_id}/${item.answer_id}`}
|
||||
href={pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.question_info?.title,
|
||||
answerId: item.answer_id,
|
||||
})}
|
||||
className="text-break">
|
||||
{item.question_info?.title}
|
||||
</a>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { FC, memo } from 'react';
|
||||
import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
||||
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import { FormatTime } from '@/components';
|
||||
|
||||
interface Props {
|
||||
|
@ -19,11 +20,15 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
<ListGroupItem className="py-3 px-0" key={item.comment_id}>
|
||||
<a
|
||||
className="text-break"
|
||||
href={`/questions/${
|
||||
href={
|
||||
item.object_type === 'question'
|
||||
? item.object_id
|
||||
: `${item.question_id}/${item.object_id}`
|
||||
}`}>
|
||||
? pathFactory.questionLanding(item.object_id, item.title)
|
||||
: pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.title,
|
||||
answerId: item.object_id,
|
||||
})
|
||||
}>
|
||||
{item.title}
|
||||
</a>
|
||||
<div
|
||||
|
|
|
@ -3,6 +3,7 @@ import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Icon, FormatTime, Tag, BaseUserCard } from '@/components';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
|
@ -15,6 +16,7 @@ const Index: FC<Props> = ({ visible, tabName, data }) => {
|
|||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListGroup variant="flush">
|
||||
{data.map((item) => {
|
||||
|
@ -25,9 +27,10 @@ const Index: FC<Props> = ({ visible, tabName, data }) => {
|
|||
<h6 className="mb-2">
|
||||
<a
|
||||
className="text-break"
|
||||
href={`/questions/${
|
||||
tabName === 'questions' ? item.question_id : item.id
|
||||
}`}>
|
||||
href={pathFactory.questionLanding(
|
||||
tabName === 'questions' ? item.question_id : item.id,
|
||||
item.title,
|
||||
)}>
|
||||
{item.title}
|
||||
{tabName === 'questions' && item.status === 'closed'
|
||||
? ` [${t('closed', { keyPrefix: 'question' })}]`
|
||||
|
|
|
@ -3,6 +3,7 @@ import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FormatTime } from '@/components';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
|
@ -30,11 +31,15 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
<div>
|
||||
<a
|
||||
className="text-break"
|
||||
href={`/questions/${
|
||||
href={
|
||||
item.object_type === 'question'
|
||||
? item.object_id
|
||||
: `${item.question_id}/${item.object_id}`
|
||||
}`}>
|
||||
? pathFactory.questionLanding(item.object_id, item.title)
|
||||
: pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.title,
|
||||
answerId: item.object_id,
|
||||
})
|
||||
}>
|
||||
{item.title}
|
||||
</a>
|
||||
<div className="d-flex align-items-center fs-14 text-secondary">
|
||||
|
|
|
@ -2,6 +2,7 @@ import { FC, memo } from 'react';
|
|||
import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import { Icon } from '@/components';
|
||||
|
||||
interface Props {
|
||||
|
@ -10,7 +11,6 @@ interface Props {
|
|||
}
|
||||
const Index: FC<Props> = ({ data, type }) => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'personal' });
|
||||
|
||||
return (
|
||||
<ListGroup variant="flush" className="mb-4">
|
||||
{data?.map((item) => {
|
||||
|
@ -19,11 +19,15 @@ const Index: FC<Props> = ({ data, type }) => {
|
|||
className="p-0 border-0 mb-2"
|
||||
key={type === 'answer' ? item.answer_id : item.question_id}>
|
||||
<a
|
||||
href={`/questions/${
|
||||
href={
|
||||
type === 'answer'
|
||||
? `${item.question_id}/${item.answer_id}`
|
||||
: item.question_id
|
||||
}`}>
|
||||
? pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.question_info?.title,
|
||||
answerId: item.answer_id,
|
||||
})
|
||||
: pathFactory.questionLanding(item.question_id, item.title)
|
||||
}>
|
||||
{type === 'answer' ? item.question_info.title : item.title}
|
||||
</a>
|
||||
<div className="d-inline-block text-secondary ms-3 fs-14">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { FC, memo } from 'react';
|
||||
import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
||||
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import { FormatTime } from '@/components';
|
||||
|
||||
interface Props {
|
||||
|
@ -12,6 +13,7 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
if (!visible || !data?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListGroup variant="flush">
|
||||
{data.map((item) => {
|
||||
|
@ -25,11 +27,15 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
<div>
|
||||
<a
|
||||
className="text-break"
|
||||
href={`/questions/${
|
||||
href={
|
||||
item.object_type === 'question'
|
||||
? item.question_id
|
||||
: `${item.question_id}/${item.answer_id}`
|
||||
}`}>
|
||||
? pathFactory.questionLanding(item.question_id, item.title)
|
||||
: pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.title,
|
||||
answerId: item.answer_id,
|
||||
})
|
||||
}>
|
||||
{item.title}
|
||||
</a>
|
||||
<div className="d-flex align-items-center fs-14 text-secondary">
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import urlcat from 'urlcat';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
import Pattern from '@/common/pattern';
|
||||
import { siteInfoStore } from '@/stores';
|
||||
|
||||
const tagLanding = (tag: Type.Tag) => {
|
||||
let slugName = tag.main_tag_slug_name || tag.slug_name || '';
|
||||
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 });
|
||||
};
|
||||
|
@ -18,25 +22,31 @@ const tagEdit = (tagId: string) => {
|
|||
};
|
||||
const questionLanding = (questionId: string, title: string = '') => {
|
||||
const { siteInfo } = siteInfoStore.getState();
|
||||
if (siteInfo.permalink) {
|
||||
if (siteInfo.permalink === 1) {
|
||||
title = title.toLowerCase();
|
||||
title = title.trim().replace(/\s+/g, '-');
|
||||
title = title.replace(Pattern.emoji, '');
|
||||
if (title) {
|
||||
return urlcat('/questions/:questionId/:title', { questionId, title });
|
||||
return urlcat('/questions/:questionId/:slugPermalink', {
|
||||
questionId,
|
||||
slugPermalink: title,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return urlcat('/questions/:questionId', { questionId });
|
||||
};
|
||||
const answerLanding = (
|
||||
questionId: string,
|
||||
questionTitle: string = '',
|
||||
answerId: string,
|
||||
) => {
|
||||
const questionLandingUrl = questionLanding(questionId, questionTitle);
|
||||
const answerLanding = (params: {
|
||||
questionId: string;
|
||||
questionTitle?: string;
|
||||
answerId: string;
|
||||
}) => {
|
||||
const questionLandingUrl = questionLanding(
|
||||
params.questionId,
|
||||
params.questionTitle,
|
||||
);
|
||||
return urlcat(`${questionLandingUrl}/:answerId`, {
|
||||
answerId,
|
||||
answerId: params.answerId,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -33,6 +33,17 @@ const routes: RouteNode[] = [
|
|||
path: 'questions',
|
||||
page: 'pages/Questions',
|
||||
},
|
||||
{
|
||||
path: 'questions/ask',
|
||||
page: 'pages/Questions/Ask',
|
||||
guard: async () => {
|
||||
return guard.activated();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'questions/:qid',
|
||||
page: 'pages/Questions/Detail',
|
||||
},
|
||||
{
|
||||
path: 'questions/:qid/:slugPermalink',
|
||||
page: 'pages/Questions/Detail',
|
||||
|
@ -41,13 +52,6 @@ const routes: RouteNode[] = [
|
|||
path: 'questions/:qid/:slugPermalink/:aid',
|
||||
page: 'pages/Questions/Detail',
|
||||
},
|
||||
{
|
||||
path: 'questions/ask',
|
||||
page: 'pages/Questions/Ask',
|
||||
guard: async () => {
|
||||
return guard.activated();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'posts/:qid/edit',
|
||||
page: 'pages/Questions/Ask',
|
||||
|
|
|
@ -14,11 +14,14 @@ const siteInfo = create<SiteInfoType>((set) => ({
|
|||
short_description: '',
|
||||
site_url: '',
|
||||
contact_email: '',
|
||||
permalink: true,
|
||||
permalink: 1,
|
||||
},
|
||||
update: (params) =>
|
||||
set((_) => {
|
||||
const o = { ..._.siteInfo, ...params };
|
||||
if (o.permalink !== 1 && o.permalink !== 2) {
|
||||
o.permalink = 1;
|
||||
}
|
||||
return {
|
||||
siteInfo: o,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue