DiscussionShow.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import { useIntl } from "react-intl";
  2. import {
  3. Button,
  4. Card,
  5. Dropdown,
  6. message,
  7. Modal,
  8. notification,
  9. Space,
  10. Tag,
  11. Typography,
  12. } from "antd";
  13. import {
  14. MoreOutlined,
  15. EditOutlined,
  16. DeleteOutlined,
  17. LinkOutlined,
  18. CheckOutlined,
  19. MessageOutlined,
  20. ExclamationCircleOutlined,
  21. CloseOutlined,
  22. SyncOutlined,
  23. } from "@ant-design/icons";
  24. import type { MenuProps } from "antd";
  25. import type { IComment } from "./DiscussionItem";
  26. import TimeShow from "../general/TimeShow";
  27. import Marked from "../general/Marked";
  28. import { delete_, put } from "../../request";
  29. import type { IDeleteResponse } from "../../api/Article";
  30. import { fullUrl } from "../../utils";
  31. import type { ICommentRequest, ICommentResponse } from "../../api/Comment";
  32. import { useState } from "react";
  33. import MdView from "../template/MdView";
  34. import type { TDiscussionType } from "./Discussion";
  35. import { discussionCountUpgrade } from "./DiscussionCount";
  36. const { Text } = Typography;
  37. interface IWidget {
  38. data: IComment;
  39. hideTitle?: boolean;
  40. onEdit?: Function;
  41. onSelect?: Function;
  42. onDelete?: Function;
  43. onReply?: Function;
  44. onClose?: Function;
  45. onConvert?: Function;
  46. }
  47. const DiscussionShowWidget = ({
  48. data,
  49. hideTitle = false,
  50. onEdit,
  51. onSelect,
  52. onDelete,
  53. ___onReply,
  54. onClose,
  55. onConvert,
  56. }: IWidget) => {
  57. const intl = useIntl();
  58. const [closed, setClosed] = useState(data.status);
  59. const showDeleteConfirm = (id: string, resId: string, title: string) => {
  60. Modal.confirm({
  61. icon: <ExclamationCircleOutlined />,
  62. title:
  63. intl.formatMessage({
  64. id: "message.delete.confirm",
  65. }) +
  66. intl.formatMessage({
  67. id: "message.irrevocable",
  68. }),
  69. content: title,
  70. okText: intl.formatMessage({
  71. id: "buttons.delete",
  72. }),
  73. okType: "danger",
  74. cancelText: intl.formatMessage({
  75. id: "buttons.no",
  76. }),
  77. onOk() {
  78. const url = `/v2/discussion/${id}`;
  79. console.info("Discussion delete api request", url);
  80. return delete_<IDeleteResponse>(url)
  81. .then((json) => {
  82. console.debug("api response", json);
  83. if (json.ok) {
  84. message.success("删除成功");
  85. discussionCountUpgrade(resId);
  86. if (typeof onDelete !== "undefined") {
  87. onDelete(id);
  88. }
  89. } else {
  90. message.error(json.message);
  91. }
  92. })
  93. .catch((e) => console.log("Oops errors!", e));
  94. },
  95. });
  96. };
  97. const close = (value: boolean) => {
  98. const url = `/v2/discussion/${data.id}`;
  99. const newData: ICommentRequest = {
  100. title: data.title,
  101. content: data.content,
  102. status: value ? "close" : "active",
  103. };
  104. console.info("api request", url, newData);
  105. put<ICommentRequest, ICommentResponse>(url, newData).then((json) => {
  106. console.log(json);
  107. if (json.ok) {
  108. setClosed(json.data.status);
  109. discussionCountUpgrade(data.resId);
  110. if (typeof onClose !== "undefined") {
  111. onClose(value);
  112. }
  113. } else {
  114. message.error(json.message);
  115. }
  116. });
  117. };
  118. const convert = (newType: TDiscussionType) => {
  119. const url = `/v2/discussion/${data.id}`;
  120. const newData: ICommentRequest = {
  121. title: data.title,
  122. content: data.content,
  123. status: data.status,
  124. type: newType,
  125. };
  126. console.info("api response", url, newData);
  127. put<ICommentRequest, ICommentResponse>(url, newData).then((json) => {
  128. console.info("api response", json);
  129. if (json.ok) {
  130. notification.info({ message: "转换成功" });
  131. if (typeof onConvert !== "undefined") {
  132. onConvert(newType);
  133. }
  134. }
  135. });
  136. };
  137. const onClick: MenuProps["onClick"] = (e) => {
  138. console.log("click ", e);
  139. switch (e.key) {
  140. case "copy-link":
  141. let url = `/discussion/topic/`;
  142. if (data.id) {
  143. if (data.parent) {
  144. url += `${data.parent}#${data.id}`;
  145. } else {
  146. url += data.id;
  147. }
  148. } else {
  149. url += `${data.tplId}?tpl=true&resId=${data.resId}&resType=${data.resType}`;
  150. }
  151. navigator.clipboard.writeText(fullUrl(url)).then(() => {
  152. message.success("链接地址已经拷贝到剪贴板");
  153. });
  154. break;
  155. case "copy-tpl":
  156. const tpl = `{{qa|id=${data.id}|style=collapse}}`;
  157. navigator.clipboard.writeText(tpl).then(() => {
  158. notification.success({ message: "链接地址已经拷贝到剪贴板" });
  159. });
  160. break;
  161. case "edit":
  162. if (typeof onEdit !== "undefined") {
  163. onEdit();
  164. }
  165. break;
  166. case "close":
  167. close(true);
  168. break;
  169. case "reopen":
  170. close(false);
  171. break;
  172. case "convert_qa":
  173. convert("qa");
  174. break;
  175. case "convert_help":
  176. convert("help");
  177. break;
  178. case "convert_discussion":
  179. convert("discussion");
  180. break;
  181. case "delete":
  182. if (data.id && data.resId) {
  183. showDeleteConfirm(data.id, data.resId, data.title ?? "");
  184. }
  185. break;
  186. default:
  187. break;
  188. }
  189. };
  190. console.log("children", data.childrenCount);
  191. const items: MenuProps["items"] = [
  192. {
  193. key: "copy-link",
  194. label: intl.formatMessage({
  195. id: "buttons.copy.link",
  196. }),
  197. icon: <LinkOutlined />,
  198. },
  199. {
  200. key: "copy-tpl",
  201. label: intl.formatMessage({
  202. id: "buttons.copy.tpl",
  203. }),
  204. icon: <LinkOutlined />,
  205. disabled: data.type !== "qa",
  206. },
  207. {
  208. type: "divider",
  209. },
  210. {
  211. key: "edit",
  212. label: intl.formatMessage({
  213. id: "buttons.edit",
  214. }),
  215. icon: <EditOutlined />,
  216. },
  217. {
  218. key: "close",
  219. label: intl.formatMessage({
  220. id: "buttons.close",
  221. }),
  222. icon: <CloseOutlined />,
  223. disabled: closed === "close",
  224. },
  225. {
  226. key: "reopen",
  227. label: intl.formatMessage({
  228. id: "buttons.open",
  229. }),
  230. icon: <CheckOutlined />,
  231. disabled: closed === "active",
  232. },
  233. {
  234. type: "divider",
  235. },
  236. {
  237. key: "convert",
  238. label: intl.formatMessage({
  239. id: "buttons.convert",
  240. }),
  241. icon: <SyncOutlined />,
  242. disabled: data.parent ? true : false,
  243. children: [
  244. { key: "convert_qa", label: "qa", disabled: data.type === "qa" },
  245. { key: "convert_help", label: "help", disabled: data.type === "help" },
  246. {
  247. key: "convert_discussion",
  248. label: "discussion",
  249. disabled: data.type === "discussion",
  250. },
  251. ],
  252. },
  253. {
  254. type: "divider",
  255. },
  256. {
  257. key: "delete",
  258. label: intl.formatMessage({
  259. id: "buttons.delete",
  260. }),
  261. icon: <DeleteOutlined />,
  262. danger: true,
  263. disabled: data.childrenCount && data.childrenCount > 0 ? true : false,
  264. },
  265. ];
  266. const editInfo = () => {
  267. return (
  268. <Space orientation="vertical" size={"small"}>
  269. {data.title && !hideTitle ? (
  270. <Text
  271. style={{ fontSize: 16 }}
  272. strong
  273. onClick={(e) => {
  274. if (typeof onSelect !== "undefined") {
  275. onSelect(e);
  276. }
  277. }}
  278. >
  279. {data.title}
  280. </Text>
  281. ) : undefined}
  282. <Text type="secondary" style={{ fontSize: "80%" }}>
  283. <Space>
  284. {!data.parent && closed === "close" ? (
  285. <Tag style={{ backgroundColor: "#8250df", color: "white" }}>
  286. {"closed"}
  287. </Tag>
  288. ) : undefined}
  289. {data.user.nickName}
  290. <TimeShow
  291. type="secondary"
  292. updatedAt={data.updatedAt}
  293. createdAt={data.createdAt}
  294. />
  295. </Space>
  296. </Text>
  297. </Space>
  298. );
  299. };
  300. const editMenu = () => {
  301. return (
  302. <Space>
  303. <span
  304. style={{
  305. display: data.childrenCount === 0 ? "none" : "inline",
  306. cursor: "pointer",
  307. }}
  308. onClick={(e) => {
  309. if (typeof onSelect !== "undefined") {
  310. onSelect(e, data);
  311. }
  312. }}
  313. >
  314. {data.childrenCount ? (
  315. <>
  316. <MessageOutlined /> {data.childrenCount}
  317. </>
  318. ) : undefined}
  319. </span>
  320. <Dropdown
  321. menu={{ items, onClick }}
  322. placement="bottomRight"
  323. trigger={["click"]}
  324. >
  325. <Button shape="circle" size="small" icon={<MoreOutlined />}></Button>
  326. </Dropdown>
  327. </Space>
  328. );
  329. };
  330. return (
  331. <Card
  332. size="small"
  333. title={data.type === "qa" && data.parent ? undefined : editInfo()}
  334. extra={data.type === "qa" && data.parent ? undefined : editMenu()}
  335. style={{ width: "100%" }}
  336. >
  337. <div>
  338. {data.html ? (
  339. <MdView html={data.html} />
  340. ) : (
  341. <Marked text={data.content} />
  342. )}
  343. </div>
  344. {data.type === "qa" && data.parent ? (
  345. <div style={{ display: "flex", justifyContent: "space-between" }}>
  346. <div></div>
  347. <div>
  348. {editInfo()}
  349. {editMenu()}
  350. </div>
  351. </div>
  352. ) : (
  353. <></>
  354. )}
  355. </Card>
  356. );
  357. };
  358. export default DiscussionShowWidget;