Merge branch 'feat/ui-0.7.0' into 'test'
Feat/ui 0.7.0 See merge request opensource/answer!353
This commit is contained in:
+65
-9
@@ -4,7 +4,7 @@
|
||||
|
||||
To learn more about the philosophy and goals of the project, visit [Answer](https://answer.dev).
|
||||
|
||||
### 📦 Prerequisites
|
||||
## ⚙️ Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) `>=16.17`
|
||||
- [pnpm](https://pnpm.io/) `>=7`
|
||||
@@ -40,22 +40,78 @@ you can also manually visit it.
|
||||
|
||||
when cloning repo, and run `pnpm install` to init dependencies. you can use project commands below:
|
||||
|
||||
- `pnpm run start` run Answer web locally.
|
||||
- `pnpm run build` build Answer for production
|
||||
- `pnpm run lint` lint and fix the code style
|
||||
- `pnpm start` run Answer web locally.
|
||||
- `pnpm build` build Answer for production
|
||||
- `pnpm lint` lint and fix the code style
|
||||
|
||||
## 🌍 I18n(Multi-language)
|
||||
If you need to add or edit a language entry, just go to the `/i18n/en_US.yaml` file,
|
||||
all front-end language entries are placed under the `ui` field.
|
||||
|
||||
## 🖥 Environment Support
|
||||
If you would like to help us with the i18n translation, please visit [Answer@Crowdin](https://crowdin.com/translate/answer)
|
||||
|
||||
## 💡 Project instructions
|
||||
|
||||
```
|
||||
.
|
||||
├── cmd
|
||||
├── configs
|
||||
├── docs
|
||||
├── i18n
|
||||
├── en_US.yaml (basic language file)
|
||||
├── i18n.yaml (language list)
|
||||
├── internal
|
||||
├── ...
|
||||
└── ui (front-end project starts here)
|
||||
├── build (built results directory, usually without concern)
|
||||
├── public (html template for public)
|
||||
├── scripts (some scripting tools on front-end project)
|
||||
├── src (almost all front-end resources are here)
|
||||
├── assets (static resources)
|
||||
├── common (project information/data defined here)
|
||||
├── components (all components of the project)
|
||||
├── hooks (all hooks of the project)
|
||||
├── i18n (Initialize the front-end i18n)
|
||||
├── pages (all pages of the project)
|
||||
├── router (Project routing definition)
|
||||
├── services (all data api of the project)
|
||||
├── stores (all data stores of the project)
|
||||
├── utils (all utils of the project)
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
#### Fix Bug
|
||||
If you find a bug, please don't hesitate to [submit an issue](https://github.com/answerdev/answer/issues) to us.
|
||||
If you can fix it, please include a note with your issue submission.
|
||||
If it is a bug definitely, you can submit your PR after we confirm it, which will ensure you don't do anything useless.
|
||||
|
||||
#### Code Review & Comment
|
||||
In our development, some codes are not logical we know. If you find it, please don't hesitate to submit PR to us.
|
||||
In the same way, some function has no comment. We would appreciate it if you could help us supplement it.
|
||||
|
||||
#### Translation
|
||||
All our translations are placed in the i18n directory.
|
||||
|
||||
1. If you find that the corresponding key in the language you are using does not have a translation, you can submit your translation.
|
||||
2. If you want to submit a new language translation, please add your language to the `i18n.yaml` file.
|
||||
|
||||
#### Features or Plugin
|
||||
1. We developed the features for the plan based on the [roadmap](https://github.com/orgs/answerdev/projects/1). If you are suggestions for new functions, please confirm whether they have been planned.
|
||||
2. Plugins will be available in the future, so stay tuned.
|
||||
|
||||
## 📱Environment Support
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Safari |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## 🧱 Build with
|
||||
|
||||
- [React.js](https://reactjs.org/) - Our front end is a React.js app.
|
||||
## ⭐ Built with
|
||||
- [TypeScript](https://www.typescriptlang.org/) - strongly typed JavaScript
|
||||
- [React.js](https://reactjs.org/) - Our front end is a React.js app
|
||||
- [React Router](https://reactrouter.com/en/main) - Router library
|
||||
- [Bootstrap](https://getbootstrap.com/) - UI library.
|
||||
- [React Bootstrap](https://react-bootstrap.github.io/) - UI Library(rebuilt for React.)
|
||||
- [React Bootstrap](https://react-bootstrap.github.io/) - UI Library(rebuilt for React)
|
||||
- [axios](https://github.com/axios/axios) - Request library
|
||||
- [SWR](https://swr.bootcss.com/) - Request library
|
||||
- [react-i18next](https://react.i18next.com/) - International library
|
||||
|
||||
@@ -54,6 +54,7 @@ export interface TagInfo extends TagBase {
|
||||
}
|
||||
export interface QuestionParams {
|
||||
title: string;
|
||||
url_title?: string;
|
||||
content: string;
|
||||
html: string;
|
||||
tags: Tag[];
|
||||
@@ -396,6 +397,7 @@ export interface SearchParams {
|
||||
export interface SearchResItem {
|
||||
object_type: string;
|
||||
object: {
|
||||
url_title?: string;
|
||||
id: string;
|
||||
question_id?: string;
|
||||
title: string;
|
||||
@@ -455,6 +457,7 @@ export interface TimelineItem {
|
||||
|
||||
export interface TimelineObject {
|
||||
title: string;
|
||||
url_title?: string;
|
||||
object_type: string;
|
||||
question_id: string;
|
||||
answer_id: string;
|
||||
@@ -470,6 +473,7 @@ export interface TimelineRes {
|
||||
export interface ReviewItem {
|
||||
type: 'question' | 'answer' | 'tag';
|
||||
info: {
|
||||
url_title?: string;
|
||||
object_id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
|
||||
@@ -30,7 +30,7 @@ const HotQuestions: FC = () => {
|
||||
<ListGroupItem
|
||||
key={li.id}
|
||||
as={Link}
|
||||
to={pathFactory.questionLanding(li.id, li.title)}
|
||||
to={pathFactory.questionLanding(li.id, li.url_title)}
|
||||
action>
|
||||
<div className="link-dark">{li.title}</div>
|
||||
{li.answer_count > 0 ? (
|
||||
|
||||
@@ -19,6 +19,7 @@ interface IProps {
|
||||
qid: string;
|
||||
aid?: string;
|
||||
title: string;
|
||||
slugTitle: string;
|
||||
hasAnswer?: boolean;
|
||||
isAccepted: boolean;
|
||||
callback: (type: string) => void;
|
||||
@@ -29,6 +30,7 @@ const Index: FC<IProps> = ({
|
||||
qid,
|
||||
aid = '',
|
||||
title,
|
||||
slugTitle,
|
||||
isAccepted = false,
|
||||
hasAnswer = false,
|
||||
memberActions = [],
|
||||
@@ -159,7 +161,13 @@ const Index: FC<IProps> = ({
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-center">
|
||||
<Share type={type} qid={qid} aid={aid} title={title} />
|
||||
<Share
|
||||
type={type}
|
||||
qid={qid}
|
||||
aid={aid}
|
||||
title={title}
|
||||
slugTitle={slugTitle}
|
||||
/>
|
||||
{memberActions?.map((item) => {
|
||||
if (item.action === 'edit') {
|
||||
return (
|
||||
|
||||
@@ -123,7 +123,7 @@ const QuestionList: FC<Props> = ({ source }) => {
|
||||
<ListGroup.Item key={li.id} className="border-bottom py-3 px-0">
|
||||
<h5 className="text-wrap text-break">
|
||||
<NavLink
|
||||
to={pathFactory.questionLanding(li.id, li.title)}
|
||||
to={pathFactory.questionLanding(li.id, li.url_title)}
|
||||
className="link-dark">
|
||||
{li.title}
|
||||
{li.status === 2 ? ` [${t('closed')}]` : ''}
|
||||
|
||||
@@ -13,21 +13,25 @@ interface IProps {
|
||||
qid: any;
|
||||
aid?: any;
|
||||
title: string;
|
||||
slugTitle: string;
|
||||
}
|
||||
|
||||
const Index: FC<IProps> = ({ type, qid, aid, title }) => {
|
||||
const Index: FC<IProps> = ({ type, qid, aid, title, slugTitle = '' }) => {
|
||||
const user = loggedUserInfoStore((state) => state.user);
|
||||
const [show, setShow] = useState(false);
|
||||
const [showTip, setShowTip] = useState(false);
|
||||
const [canSystemShare, setSystemShareState] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// FIXME: pathFactory
|
||||
let baseUrl =
|
||||
type === 'question'
|
||||
? `${window.location.origin}${pathFactory.questionLanding(qid, title)}`
|
||||
? `${window.location.origin}${pathFactory.questionLanding(
|
||||
qid,
|
||||
slugTitle,
|
||||
)}`
|
||||
: `${window.location.origin}${pathFactory.answerLanding({
|
||||
questionId: qid,
|
||||
questionTitle: title,
|
||||
slugTitle,
|
||||
answerId: aid,
|
||||
})}`;
|
||||
if (user.id) {
|
||||
|
||||
@@ -130,7 +130,7 @@ const Answers: FC = () => {
|
||||
<a
|
||||
href={pathFactory.answerLanding({
|
||||
questionId: li.question_id,
|
||||
questionTitle: li.question_info.title,
|
||||
slugTitle: li.question_info.url_title,
|
||||
answerId: li.id,
|
||||
})}
|
||||
target="_blank"
|
||||
|
||||
@@ -102,7 +102,7 @@ const Flags: FC = () => {
|
||||
<a
|
||||
href={pathFactory.questionLanding(
|
||||
li.question_id,
|
||||
li.title,
|
||||
li.url_title,
|
||||
)}
|
||||
target="_blank"
|
||||
className="text-wrap text-break mt-2"
|
||||
|
||||
@@ -137,7 +137,7 @@ const Questions: FC = () => {
|
||||
<tr key={li.id}>
|
||||
<td>
|
||||
<a
|
||||
href={pathFactory.questionLanding(li.id, li.title)}
|
||||
href={pathFactory.questionLanding(li.id, li.url_title)}
|
||||
target="_blank"
|
||||
className="text-break text-wrap"
|
||||
rel="noreferrer">
|
||||
|
||||
@@ -29,7 +29,7 @@ const SearchQuestion = ({ similarQuestions }) => {
|
||||
as="a"
|
||||
className="link-dark"
|
||||
key={item.id}
|
||||
href={pathFactory.questionLanding(item.id, item.title)}
|
||||
href={pathFactory.questionLanding(item.id, item.url_title)}
|
||||
target="_blank">
|
||||
<span className="text-wrap text-break">
|
||||
{item.title}
|
||||
|
||||
@@ -237,7 +237,7 @@ const Ask = () => {
|
||||
edit_summary: formData.edit_summary.value,
|
||||
})
|
||||
.then((res) => {
|
||||
navigate(pathFactory.questionLanding(qid, params.title), {
|
||||
navigate(pathFactory.questionLanding(qid, params.url_title), {
|
||||
state: { isReview: res?.wait_for_review },
|
||||
});
|
||||
})
|
||||
@@ -264,7 +264,7 @@ const Ask = () => {
|
||||
html: editorRef2.current.getHtml(),
|
||||
})
|
||||
.then(() => {
|
||||
navigate(pathFactory.questionLanding(id, params.title));
|
||||
navigate(pathFactory.questionLanding(id, params.url_title));
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
@@ -273,7 +273,7 @@ const Ask = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
navigate(pathFactory.questionLanding(id, params.title));
|
||||
navigate(pathFactory.questionLanding(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ interface Props {
|
||||
/** is author */
|
||||
isAuthor: boolean;
|
||||
questionTitle: string;
|
||||
slugTitle: string;
|
||||
isLogged: boolean;
|
||||
callback: (type: string) => void;
|
||||
}
|
||||
@@ -32,6 +33,7 @@ const Index: FC<Props> = ({
|
||||
isAuthor,
|
||||
isLogged,
|
||||
questionTitle = '',
|
||||
slugTitle,
|
||||
callback,
|
||||
}) => {
|
||||
const { t } = useTranslation('translation', {
|
||||
@@ -114,6 +116,7 @@ const Index: FC<Props> = ({
|
||||
type="answer"
|
||||
isAccepted={data.adopted === 2}
|
||||
title={questionTitle}
|
||||
slugTitle={slugTitle}
|
||||
callback={callback}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
@@ -65,7 +65,7 @@ const Index: FC<Props> = ({ data, initPage, hasAnswer, isLogged }) => {
|
||||
<Link
|
||||
className="link-dark"
|
||||
reloadDocument
|
||||
to={pathFactory.questionLanding(data.id, data.title)}>
|
||||
to={pathFactory.questionLanding(data.id, data.url_title)}>
|
||||
{data.title}
|
||||
{data.status === 2
|
||||
? ` [${t('closed', { keyPrefix: 'question' })}]`
|
||||
@@ -129,6 +129,7 @@ const Index: FC<Props> = ({ data, initPage, hasAnswer, isLogged }) => {
|
||||
type="question"
|
||||
memberActions={data?.member_actions}
|
||||
title={data.title}
|
||||
slugTitle={data.url_title}
|
||||
hasAnswer={hasAnswer}
|
||||
isAccepted={Boolean(data?.accepted_answer_id)}
|
||||
callback={initPage}
|
||||
|
||||
@@ -32,7 +32,7 @@ const Index: FC<Props> = ({ id }) => {
|
||||
action
|
||||
key={item.id}
|
||||
as={Link}
|
||||
to={pathFactory.questionLanding(item.id, item.title)}>
|
||||
to={pathFactory.questionLanding(item.id, item.url_title)}>
|
||||
<div className="link-dark">{item.title}</div>
|
||||
{item.answer_count > 0 && (
|
||||
<div
|
||||
|
||||
@@ -178,6 +178,7 @@ const Index = () => {
|
||||
key={item?.id}
|
||||
data={item}
|
||||
questionTitle={question?.title || ''}
|
||||
slugTitle={question?.url_title}
|
||||
isAuthor={isAuthor}
|
||||
callback={initPage}
|
||||
isLogged={isLogged}
|
||||
|
||||
@@ -115,7 +115,7 @@ const Index = () => {
|
||||
navigate(
|
||||
pathFactory.answerLanding({
|
||||
questionId: qid,
|
||||
questionTitle: data?.question?.title,
|
||||
slugTitle: data?.question?.url_title,
|
||||
answerId: aid,
|
||||
}),
|
||||
{
|
||||
@@ -147,7 +147,7 @@ const Index = () => {
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-4 mb-md-0">
|
||||
<a
|
||||
href={pathFactory.questionLanding(qid, data?.question.title)}
|
||||
href={pathFactory.questionLanding(qid, data?.question.url_title)}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
<h5 className="mb-3">{data?.question.title}</h5>
|
||||
|
||||
@@ -95,7 +95,7 @@ const Index: FC = () => {
|
||||
const editor = unreviewed_info?.user_info;
|
||||
const editTime = unreviewed_info?.create_at;
|
||||
if (type === 'question') {
|
||||
itemLink = pathFactory.questionLanding(info?.object_id, info?.title);
|
||||
itemLink = pathFactory.questionLanding(info?.object_id, info?.url_title);
|
||||
itemTitle = info?.title;
|
||||
editBadge = t('question_edit');
|
||||
editSummary ||= t('edit_question');
|
||||
@@ -103,7 +103,7 @@ const Index: FC = () => {
|
||||
itemLink = pathFactory.answerLanding({
|
||||
// @ts-ignore
|
||||
questionId: unreviewed_info.content.question_id,
|
||||
questionTitle: info?.title,
|
||||
slugTitle: info?.url_title,
|
||||
answerId: unreviewed_info.object_id,
|
||||
});
|
||||
itemTitle = info?.title;
|
||||
|
||||
@@ -15,11 +15,14 @@ const Index: FC<Props> = ({ data }) => {
|
||||
if (!data?.object_type) {
|
||||
return null;
|
||||
}
|
||||
let itemUrl = pathFactory.questionLanding(data.object.id, data.object.title);
|
||||
let itemUrl = pathFactory.questionLanding(
|
||||
data.object.id,
|
||||
data.object.url_title,
|
||||
);
|
||||
if (data.object_type === 'answer' && data.object.question_id) {
|
||||
itemUrl = pathFactory.answerLanding({
|
||||
questionId: data.object.question_id,
|
||||
questionTitle: data.object.title,
|
||||
slugTitle: data.object.url_title,
|
||||
answerId: data.object.id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ const Index: FC = () => {
|
||||
if (timelineData?.object_info.object_type === 'question') {
|
||||
linkUrl = pathFactory.questionLanding(
|
||||
timelineData?.object_info.question_id,
|
||||
timelineData?.object_info.title,
|
||||
timelineData?.object_info.url_title,
|
||||
);
|
||||
pageTitle = `${t('title_for_question')} ${timelineData?.object_info.title}`;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ const Index: FC = () => {
|
||||
if (timelineData?.object_info.object_type === 'answer') {
|
||||
linkUrl = pathFactory.answerLanding({
|
||||
questionId: timelineData?.object_info.question_id,
|
||||
questionTitle: timelineData?.object_info.title,
|
||||
slugTitle: timelineData?.object_info.url_title,
|
||||
answerId: timelineData?.object_info.answer_id,
|
||||
});
|
||||
pageTitle = `${t('title_for_answer', {
|
||||
|
||||
@@ -23,7 +23,7 @@ const Index: FC<Props> = ({ visible, data }) => {
|
||||
<a
|
||||
href={pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.question_info?.title,
|
||||
slugTitle: item.question_info?.url_title,
|
||||
answerId: item.answer_id,
|
||||
})}
|
||||
className="text-break">
|
||||
|
||||
@@ -22,10 +22,10 @@ const Index: FC<Props> = ({ visible, data }) => {
|
||||
className="text-break"
|
||||
href={
|
||||
item.object_type === 'question'
|
||||
? pathFactory.questionLanding(item.object_id, item.title)
|
||||
? pathFactory.questionLanding(item.object_id, item.url_title)
|
||||
: pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.title,
|
||||
slugTitle: item.url_title,
|
||||
answerId: item.object_id,
|
||||
})
|
||||
}>
|
||||
|
||||
@@ -29,7 +29,7 @@ const Index: FC<Props> = ({ visible, tabName, data }) => {
|
||||
className="text-break"
|
||||
href={pathFactory.questionLanding(
|
||||
tabName === 'questions' ? item.question_id : item.id,
|
||||
item.title,
|
||||
item.url_title,
|
||||
)}>
|
||||
{item.title}
|
||||
{tabName === 'questions' && item.status === 'closed'
|
||||
|
||||
@@ -33,10 +33,13 @@ const Index: FC<Props> = ({ visible, data }) => {
|
||||
className="text-break"
|
||||
href={
|
||||
item.object_type === 'question'
|
||||
? pathFactory.questionLanding(item.object_id, item.title)
|
||||
? pathFactory.questionLanding(
|
||||
item.object_id,
|
||||
item.url_title,
|
||||
)
|
||||
: pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.title,
|
||||
slugTitle: item.url_title,
|
||||
answerId: item.object_id,
|
||||
})
|
||||
}>
|
||||
|
||||
@@ -23,10 +23,13 @@ const Index: FC<Props> = ({ data, type }) => {
|
||||
type === 'answer'
|
||||
? pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.question_info?.title,
|
||||
slugTitle: item.question_info?.url_title,
|
||||
answerId: item.answer_id,
|
||||
})
|
||||
: pathFactory.questionLanding(item.question_id, item.title)
|
||||
: pathFactory.questionLanding(
|
||||
item.question_id,
|
||||
item.url_title,
|
||||
)
|
||||
}>
|
||||
{type === 'answer' ? item.question_info.title : item.title}
|
||||
</a>
|
||||
|
||||
@@ -29,10 +29,13 @@ const Index: FC<Props> = ({ visible, data }) => {
|
||||
className="text-break"
|
||||
href={
|
||||
item.object_type === 'question'
|
||||
? pathFactory.questionLanding(item.question_id, item.title)
|
||||
? pathFactory.questionLanding(
|
||||
item.question_id,
|
||||
item.url_title,
|
||||
)
|
||||
: pathFactory.answerLanding({
|
||||
questionId: item.question_id,
|
||||
questionTitle: item.title,
|
||||
slugTitle: item.url_title,
|
||||
answerId: item.answer_id,
|
||||
})
|
||||
}>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import urlcat from 'urlcat';
|
||||
|
||||
import Pattern from '@/common/pattern';
|
||||
import { seoSettingStore } from '@/stores';
|
||||
|
||||
const tagLanding = (slugName: string) => {
|
||||
@@ -20,30 +19,25 @@ const tagInfo = (slugName: string) => {
|
||||
const tagEdit = (tagId: string) => {
|
||||
return urlcat('/tags/:tagId/edit', { tagId });
|
||||
};
|
||||
const questionLanding = (questionId: string, title: string = '') => {
|
||||
const questionLanding = (questionId: string, slugTitle: string = '') => {
|
||||
const { seo } = seoSettingStore.getState();
|
||||
if (seo.permalink === 1) {
|
||||
title = title.toLowerCase();
|
||||
title = title.trim().replace(/\s+/g, '-');
|
||||
title = title.replace(Pattern.emoji, '');
|
||||
if (title) {
|
||||
return urlcat('/questions/:questionId/:slugPermalink', {
|
||||
questionId,
|
||||
slugPermalink: title,
|
||||
});
|
||||
}
|
||||
if (seo.permalink === 1 && slugTitle) {
|
||||
return urlcat('/questions/:questionId/:slugPermalink', {
|
||||
questionId,
|
||||
slugPermalink: slugTitle,
|
||||
});
|
||||
}
|
||||
|
||||
return urlcat('/questions/:questionId', { questionId });
|
||||
};
|
||||
const answerLanding = (params: {
|
||||
questionId: string;
|
||||
questionTitle?: string;
|
||||
slugTitle?: string;
|
||||
answerId: string;
|
||||
}) => {
|
||||
const questionLandingUrl = questionLanding(
|
||||
params.questionId,
|
||||
params.questionTitle,
|
||||
params.slugTitle,
|
||||
);
|
||||
return urlcat(`${questionLandingUrl}/:answerId`, {
|
||||
answerId: params.answerId,
|
||||
|
||||
Reference in New Issue
Block a user