WbwWord.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. import { useState, useEffect, useRef } from "react";
  2. import { useAppSelector } from "../../../hooks";
  3. import { add, updateIndex, wordIndex } from "../../../reducers/inline-dict";
  4. import { get } from "../../../request";
  5. import store from "../../../store";
  6. import type { IApiResponseDictList } from "../../../api/Dict";
  7. import WbwCase from "./WbwCase";
  8. import { bookMarkColor } from "./WbwDetailBookMark";
  9. import WbwFactorMeaning from "./WbwFactorMeaning";
  10. import WbwFactors from "./WbwFactors";
  11. import WbwMeaning from "./WbwMeaning";
  12. import WbwPali from "./WbwPali";
  13. import "./wbw.css";
  14. import WbwPara from "./WbwPara";
  15. import WbwPage from "./WbwPage";
  16. import WbwRelationAdd from "./WbwRelationAdd";
  17. import type { ArticleMode } from "../../article/Article";
  18. import WbwReal from "./WbwReal";
  19. import WbwDetailFm from "./WbwDetailFm";
  20. import type { IStudio } from "../../auth/Studio";
  21. import type { IUser } from "../../auth/User";
  22. export type TFieldName =
  23. | "word"
  24. | "real"
  25. | "meaning"
  26. | "type"
  27. | "grammar"
  28. | "grammar2"
  29. | "case"
  30. | "parent"
  31. | "parent2"
  32. | "factors"
  33. | "factorMeaning"
  34. | "relation"
  35. | "note"
  36. | "bookMarkColor"
  37. | "bookMarkText"
  38. | "locked"
  39. | "attachments"
  40. | "confidence";
  41. export interface IWbwField {
  42. field: TFieldName;
  43. value: string;
  44. }
  45. export enum WbwStatus {
  46. initiate = 0,
  47. auto = 3,
  48. apply = 5,
  49. manual = 7,
  50. }
  51. export interface IWbwAttachment {
  52. id: string;
  53. content_type: string;
  54. size: number;
  55. title: string;
  56. }
  57. export interface WbwElement<R> {
  58. value: R;
  59. status: WbwStatus;
  60. }
  61. export interface IWbw {
  62. uid?: string;
  63. book: number;
  64. para: number;
  65. sn: number[];
  66. word: WbwElement<string>;
  67. real: WbwElement<string | null>;
  68. meaning?: WbwElement<string | null>;
  69. type?: WbwElement<string | null>;
  70. grammar?: WbwElement<string | null>;
  71. style?: WbwElement<string | null>;
  72. case?: WbwElement<string | null>;
  73. parent?: WbwElement<string | null>;
  74. parent2?: WbwElement<string | null>;
  75. grammar2?: WbwElement<string | null>;
  76. factors?: WbwElement<string | null>;
  77. factorMeaning?: WbwElement<string | null>;
  78. relation?: WbwElement<string | null>;
  79. note?: WbwElement<string | null>;
  80. bookMarkColor?: WbwElement<number | null>;
  81. bookMarkText?: WbwElement<string | null>;
  82. locked?: boolean;
  83. confidence: number;
  84. attachments?: IWbwAttachment[];
  85. hasComment?: boolean;
  86. grammarId?: string;
  87. bookName?: string;
  88. editor?: IUser;
  89. created_at?: string;
  90. updated_at?: string;
  91. }
  92. export interface IWbwFields {
  93. real?: boolean;
  94. meaning?: boolean;
  95. factors?: boolean;
  96. factorMeaning?: boolean;
  97. factorMeaning2?: boolean;
  98. case?: boolean;
  99. }
  100. export type TWbwDisplayMode = "block" | "inline" | "list";
  101. interface IWidget {
  102. data: IWbw;
  103. answer?: IWbw;
  104. channelId: string;
  105. display?: TWbwDisplayMode;
  106. fields?: IWbwFields;
  107. mode?: ArticleMode;
  108. wordDark?: boolean;
  109. studio?: IStudio;
  110. readonly?: boolean;
  111. onChange?: Function;
  112. onSplit?: Function;
  113. }
  114. const WbwWordWidget = ({
  115. data,
  116. answer,
  117. channelId,
  118. display,
  119. mode = "edit",
  120. fields = {
  121. real: false,
  122. meaning: true,
  123. factors: true,
  124. factorMeaning: true,
  125. factorMeaning2: false,
  126. case: true,
  127. },
  128. wordDark = false,
  129. readonly = false,
  130. studio,
  131. onChange,
  132. onSplit,
  133. }: IWidget) => {
  134. const [wordData, setWordData] = useState(data);
  135. const fieldDisplay = fields;
  136. const [newFactors, setNewFactors] = useState<string>();
  137. const [showRelationTool, setShowRelationTool] = useState(false);
  138. const intervalRef = useRef<number | null>(null); //防抖计时器句柄
  139. const inlineWordIndex = useAppSelector(wordIndex);
  140. useEffect(() => {
  141. setWordData(data);
  142. }, [data]);
  143. const color = wordData.bookMarkColor?.value
  144. ? bookMarkColor[wordData.bookMarkColor.value]
  145. : "unset";
  146. const wbwCtl = wordData.type?.value === ".ctl." ? "wbw_ctl" : "";
  147. const wbwAnchor = wordData.grammar?.value === ".a." ? "wbw_anchor" : "";
  148. const wbwDark = wordDark ? "dark" : "";
  149. const styleWbw: React.CSSProperties = {
  150. display: display === "block" ? "block" : "flex",
  151. };
  152. /**
  153. * 停止查字典计时
  154. * 在两种情况下停止计时
  155. * 1. 开始查字典
  156. * 2. 防抖时间内鼠标移出单词区
  157. */
  158. const stopLookup = () => {
  159. if (intervalRef.current) {
  160. window.clearInterval(intervalRef.current);
  161. intervalRef.current = null;
  162. }
  163. };
  164. /**
  165. * 查字典
  166. * @param word 要查的单词
  167. */
  168. const lookup = (words: string[]) => {
  169. stopLookup();
  170. //查询这个词在内存字典里是否有
  171. const searchWord = words.filter((value) => {
  172. if (inlineWordIndex.includes(value)) {
  173. //已经有了
  174. return false;
  175. } else {
  176. return true;
  177. }
  178. });
  179. if (searchWord.length === 0) {
  180. return;
  181. }
  182. get<IApiResponseDictList>(`/v2/wbwlookup?word=${searchWord.join()}`).then(
  183. (json) => {
  184. console.log("lookup ok", json.data.count);
  185. console.log("time", json.data.time);
  186. //存储到redux
  187. store.dispatch(add(json.data.rows));
  188. store.dispatch(updateIndex(searchWord));
  189. }
  190. );
  191. console.log("lookup", searchWord);
  192. };
  193. if (wordData.type?.value === ".ctl.") {
  194. if (wordData.word.value?.includes("para")) {
  195. return <WbwPara data={wordData} />;
  196. } else {
  197. return <WbwPage data={wordData} />;
  198. }
  199. } else {
  200. return (
  201. <div
  202. className={`wbw_word ${display}_${mode} display_${display} ${wbwCtl} ${wbwAnchor} ${wbwDark} `}
  203. style={styleWbw}
  204. onMouseEnter={() => {
  205. setShowRelationTool(true);
  206. if (
  207. intervalRef.current === null &&
  208. wordData.real &&
  209. wordData.real.value &&
  210. wordData.real.value.length > 0
  211. ) {
  212. //开始计时,计时结束查字典
  213. let words: string[] = [wordData.real.value];
  214. if (
  215. wordData.parent &&
  216. wordData.parent?.value !== "" &&
  217. wordData.parent?.value !== null
  218. ) {
  219. words.push(wordData.parent.value);
  220. }
  221. if (
  222. wordData.factors &&
  223. wordData.factors?.value !== "" &&
  224. wordData.factors?.value !== null
  225. ) {
  226. words = [
  227. ...words,
  228. ...wordData.factors.value.replaceAll("-", "+").split("+"),
  229. ];
  230. }
  231. intervalRef.current = window.setInterval(lookup, 300, words);
  232. }
  233. }}
  234. onMouseLeave={() => {
  235. stopLookup();
  236. setShowRelationTool(false);
  237. }}
  238. >
  239. {showRelationTool && data.real.value ? (
  240. <WbwRelationAdd data={data} />
  241. ) : undefined}
  242. <WbwPali
  243. key="pali"
  244. data={wordData}
  245. channelId={channelId}
  246. mode={mode}
  247. display={display}
  248. studio={studio}
  249. readonly={readonly}
  250. onSave={(e: IWbw, isPublish: boolean, isPublic: boolean) => {
  251. const newData: IWbw = JSON.parse(JSON.stringify(e));
  252. setWordData(newData);
  253. if (typeof onChange !== "undefined") {
  254. onChange(e, isPublish, isPublic);
  255. }
  256. }}
  257. />
  258. <div
  259. className="wbw_body"
  260. style={{
  261. background: `linear-gradient(90deg, rgba(255, 255, 255, 0), ${color})`,
  262. }}
  263. >
  264. {fieldDisplay?.real ? (
  265. <WbwReal
  266. key="real"
  267. data={wordData}
  268. display={display}
  269. onChange={(e: string) => {
  270. console.log("meaning change", e);
  271. const newData: IWbw = JSON.parse(JSON.stringify(wordData));
  272. newData.meaning = { value: e, status: 5 };
  273. setWordData(newData);
  274. if (typeof onChange !== "undefined") {
  275. onChange(newData);
  276. }
  277. }}
  278. />
  279. ) : undefined}
  280. {fieldDisplay?.meaning ? (
  281. <WbwMeaning
  282. key="meaning"
  283. mode={mode}
  284. data={wordData}
  285. answer={answer}
  286. display={display}
  287. onChange={(e: string) => {
  288. const newData: IWbw = JSON.parse(JSON.stringify(wordData));
  289. newData.meaning = { value: e, status: WbwStatus.manual };
  290. setWordData(newData);
  291. if (typeof onChange !== "undefined") {
  292. onChange(newData);
  293. }
  294. }}
  295. />
  296. ) : undefined}
  297. {fieldDisplay?.factors ? (
  298. <WbwFactors
  299. key="factors"
  300. data={wordData}
  301. answer={answer}
  302. display={display}
  303. onChange={(e: string) => {
  304. console.log("factor change", e);
  305. const newData: IWbw = JSON.parse(JSON.stringify(wordData));
  306. newData.factors = { value: e, status: 5 };
  307. setNewFactors(e);
  308. setWordData(newData);
  309. if (typeof onChange !== "undefined") {
  310. onChange(newData);
  311. }
  312. }}
  313. />
  314. ) : undefined}
  315. {fieldDisplay?.factorMeaning ? (
  316. <WbwFactorMeaning
  317. key="fm"
  318. data={wordData}
  319. answer={answer}
  320. display={display}
  321. factors={newFactors}
  322. onChange={(e: string) => {
  323. const newData: IWbw = JSON.parse(JSON.stringify(wordData));
  324. newData.factorMeaning = { value: e, status: 5 };
  325. setWordData(newData);
  326. if (typeof onChange !== "undefined") {
  327. onChange(newData);
  328. }
  329. }}
  330. />
  331. ) : undefined}
  332. {fieldDisplay?.factorMeaning2 ? (
  333. <WbwDetailFm
  334. factors={wordData.factors?.value?.split("+")}
  335. value={wordData.factorMeaning?.value?.split("+")}
  336. onChange={(value: string[]) => {
  337. const newData: IWbw = JSON.parse(JSON.stringify(wordData));
  338. newData.factorMeaning = {
  339. value: value.join("+"),
  340. status: WbwStatus.manual,
  341. };
  342. setWordData(newData);
  343. if (typeof onChange !== "undefined") {
  344. onChange(newData);
  345. }
  346. }}
  347. onJoin={(value: string) => {
  348. const newData: IWbw = JSON.parse(JSON.stringify(wordData));
  349. newData.meaning = { value: value, status: WbwStatus.manual };
  350. setWordData(newData);
  351. if (typeof onChange !== "undefined") {
  352. onChange(newData);
  353. }
  354. }}
  355. />
  356. ) : undefined}
  357. {fieldDisplay?.case ? (
  358. <WbwCase
  359. key="case"
  360. data={wordData}
  361. answer={answer}
  362. display={display}
  363. onSplit={(e: boolean) => {
  364. console.log("onSplit", wordData.factors?.value);
  365. if (typeof onSplit !== "undefined") {
  366. onSplit(e);
  367. }
  368. }}
  369. onChange={(e: string) => {
  370. const newData: IWbw = JSON.parse(JSON.stringify(wordData));
  371. newData.case = { value: e, status: 7 };
  372. setWordData(newData);
  373. if (typeof onChange !== "undefined") {
  374. onChange(newData);
  375. }
  376. }}
  377. />
  378. ) : undefined}
  379. </div>
  380. </div>
  381. );
  382. }
  383. };
  384. export default WbwWordWidget;