sentence.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import type { IntlShape } from "react-intl";
  2. import type { ArticleMode, TContentType } from "./Article";
  3. import { get, put } from "../request";
  4. import { message } from "antd";
  5. import { toISentence } from "../components/sentence/utils";
  6. import type { IStudio, IUser } from "./Auth";
  7. import type { IChannel } from "./Channel";
  8. import type { ISuggestionCount } from "./Suggestion";
  9. import type { ITocPathNode } from "./pali-text";
  10. // ─── 以下是原有类型定义,保持不动 ─────────────────────────────────────────────
  11. export interface ISentence {
  12. id?: string;
  13. uid?: string;
  14. content: string | null;
  15. contentType?: TContentType;
  16. html: string;
  17. book: number;
  18. para: number;
  19. wordStart: number;
  20. wordEnd: number;
  21. editor: IUser;
  22. acceptor?: IUser;
  23. prEditAt?: string;
  24. channel: IChannel;
  25. studio?: IStudio;
  26. forkAt?: string | null;
  27. updateAt: string;
  28. createdAt?: string;
  29. suggestionCount?: ISuggestionCount;
  30. openInEditMode?: boolean;
  31. translationChannels?: string[];
  32. }
  33. export interface ISentenceDiffRequest {
  34. sentences: string[];
  35. channels: string[];
  36. }
  37. export interface ISentenceDiffData {
  38. book_id: number;
  39. paragraph: number;
  40. word_start: number;
  41. word_end: number;
  42. channel_uid: string;
  43. content: string | null;
  44. content_type: string;
  45. editor_uid: string;
  46. updated_at: string;
  47. }
  48. export interface ISentenceDiffResponse {
  49. ok: boolean;
  50. message: string;
  51. data: { rows: ISentenceDiffData[]; count: number };
  52. }
  53. export interface ISentenceRequest {
  54. book: number;
  55. para: number;
  56. wordStart: number;
  57. wordEnd: number;
  58. channel: string;
  59. content: string | null;
  60. contentType?: TContentType;
  61. prEditor?: string;
  62. prId?: string;
  63. prUuid?: string;
  64. prEditAt?: string;
  65. channels?: string;
  66. html?: boolean;
  67. token?: string | null;
  68. }
  69. export interface ISentenceData {
  70. id?: string;
  71. book: number;
  72. paragraph: number;
  73. word_start: number;
  74. word_end: number;
  75. content: string;
  76. content_type?: TContentType;
  77. html: string;
  78. editor: IUser;
  79. channel: IChannel;
  80. studio: IStudio;
  81. updated_at: string;
  82. acceptor?: IUser;
  83. pr_edit_at?: string;
  84. fork_at?: string;
  85. suggestionCount?: ISuggestionCount;
  86. }
  87. export interface ISentenceResponse {
  88. ok: boolean;
  89. message: string;
  90. data: ISentenceData;
  91. }
  92. export interface ISentenceListResponse {
  93. ok: boolean;
  94. message: string;
  95. data: { rows: ISentenceData[]; count: number };
  96. }
  97. export interface ISentenceNewRequest {
  98. sentences: ISentenceDiffData[];
  99. channel?: string;
  100. copy?: boolean;
  101. fork_from?: string;
  102. }
  103. export interface ISentenceWbwListResponse {
  104. ok: boolean;
  105. message: string;
  106. data: { rows: ISentEditData[]; count: number };
  107. }
  108. export interface IEditableSentence {
  109. ok: boolean;
  110. message: string;
  111. data: ISentEditData;
  112. }
  113. export interface ISentEditData {
  114. id: string;
  115. book: number;
  116. para: number;
  117. wordStart: number;
  118. wordEnd: number;
  119. channels?: string[];
  120. origin?: ISentence[];
  121. translation?: ISentence[];
  122. commentaries?: ISentence[];
  123. answer?: ISentence;
  124. path?: ITocPathNode[];
  125. layout?: "row" | "column";
  126. tranNum?: number;
  127. nissayaNum?: number;
  128. commNum?: number;
  129. originNum: number;
  130. simNum?: number;
  131. compact?: boolean;
  132. mode?: ArticleMode;
  133. showWbwProgress?: boolean;
  134. readonly?: boolean;
  135. wbwProgress?: number;
  136. wbwScore?: number;
  137. }
  138. // ─── 原有函数,保持不动,重构完成后再删除 ──────────────────────────────────────
  139. export const sentSave = async (
  140. sent: ISentence,
  141. intl: IntlShape,
  142. ok?: (res: ISentence) => void,
  143. finish?: () => void
  144. ): Promise<ISentenceData | null> => {
  145. //FIXME
  146. //store.dispatch(statusChange({ status: "loading" }));
  147. const id = `${sent.book}_${sent.para}_${sent.wordStart}_${sent.wordEnd}_${sent.channel.id}`;
  148. const url = `/v2/sentence/${id}?mode=edit&html=true`;
  149. console.info("SentWbwEdit url", url);
  150. try {
  151. const res = await put<ISentenceRequest, ISentenceResponse>(url, {
  152. book: sent.book,
  153. para: sent.para,
  154. wordStart: sent.wordStart,
  155. wordEnd: sent.wordEnd,
  156. channel: sent.channel.id,
  157. content: sent.content,
  158. contentType: sent.contentType,
  159. channels: sent.translationChannels?.join(),
  160. token: sessionStorage.getItem(sent.channel.id),
  161. });
  162. if (res.ok) {
  163. if (ok) {
  164. console.debug("sent save ok", res.data);
  165. const newData: ISentence = toISentence(res.data);
  166. ok(newData);
  167. }
  168. /**
  169. * FIXME
  170. store.dispatch(
  171. statusChange({
  172. status: "success",
  173. message: intl.formatMessage({ id: "flashes.success" }),
  174. })
  175. );
  176. */
  177. return res.data;
  178. } else {
  179. message.error(res.message);
  180. /**
  181. * FIXME
  182. store.dispatch(
  183. statusChange({
  184. status: "fail",
  185. message: res.message,
  186. })
  187. );
  188. */
  189. return null;
  190. }
  191. } catch (e) {
  192. console.error("catch", e);
  193. return null;
  194. } finally {
  195. finish?.();
  196. }
  197. };
  198. // ─── 新增:纯 HTTP 函数,供 hooks 层调用 ────────────────────────────────────────
  199. // 规范:只管收发,不含 UI 反馈、不含 store.dispatch、不含业务判断
  200. /**
  201. * 加载单条句子
  202. */
  203. export async function fetchSentence(
  204. book: number,
  205. para: number,
  206. wordStart: number,
  207. wordEnd: number,
  208. channelId: string
  209. ): Promise<ISentenceData> {
  210. const sentId = `${book}-${para}-${wordStart}-${wordEnd}`;
  211. const url = `/v2/sentence?view=channel&sentence=${sentId}&channel=${channelId}&html=true`;
  212. const json = await get<ISentenceListResponse>(url);
  213. if (!json.ok || json.data.count === 0) {
  214. throw new Error(json.message ?? "句子加载失败");
  215. }
  216. return json.data.rows[0];
  217. }
  218. /**
  219. * 保存句子内容(新建 or 更新)
  220. */
  221. export async function saveSentence(sent: ISentence): Promise<ISentenceData> {
  222. const id = `${sent.book}_${sent.para}_${sent.wordStart}_${sent.wordEnd}_${sent.channel.id}`;
  223. const url = `/v2/sentence/${id}?mode=edit&html=true`;
  224. const json = await put<ISentenceRequest, ISentenceResponse>(url, {
  225. book: sent.book,
  226. para: sent.para,
  227. wordStart: sent.wordStart,
  228. wordEnd: sent.wordEnd,
  229. channel: sent.channel.id,
  230. content: sent.content,
  231. contentType: sent.contentType,
  232. channels: sent.translationChannels?.join(),
  233. token: sessionStorage.getItem(sent.channel.id),
  234. });
  235. if (!json.ok) {
  236. throw new Error(json.message ?? "保存失败");
  237. }
  238. return json.data;
  239. }
  240. /**
  241. * 采纳 PR:把 PR 内容写回正式句子
  242. */
  243. export async function acceptSentencePr(
  244. prData: ISentence
  245. ): Promise<ISentenceData> {
  246. const id = `${prData.book}_${prData.para}_${prData.wordStart}_${prData.wordEnd}_${prData.channel.id}`;
  247. const url = `/v2/sentence/${id}?mode=edit&html=true`;
  248. const json = await put<ISentenceRequest, ISentenceResponse>(url, {
  249. book: prData.book,
  250. para: prData.para,
  251. wordStart: prData.wordStart,
  252. wordEnd: prData.wordEnd,
  253. channel: prData.channel.id,
  254. content: prData.content,
  255. prEditor: prData.editor?.id,
  256. prId: prData.id,
  257. prUuid: prData.uid,
  258. prEditAt: prData.updateAt,
  259. token: sessionStorage.getItem(prData.channel.id),
  260. });
  261. if (!json.ok) {
  262. throw new Error(json.message ?? "采纳失败");
  263. }
  264. return json.data;
  265. }
  266. /**
  267. * 获取 Snowflake ID(wbw 节点赋 uid 用)
  268. */
  269. export async function fetchSnowflakeIds(count: number): Promise<string[]> {
  270. const json = await get<{
  271. ok: boolean;
  272. message?: string;
  273. data: { rows: string[]; count: number };
  274. }>(`/v2/snowflake?count=${count}`);
  275. if (!json.ok) {
  276. throw new Error(json.message ?? "获取 ID 失败");
  277. }
  278. return json.data.rows;
  279. }