@nualang/nualang-ui-components 0.1.1352 → 0.1.1353

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/Dialogs/GenerateDefinitions/GenerateDefinitions.js +1 -0
  2. package/dist/Dialogs/GeneratePhrases/GeneratePhrases.js +33 -3
  3. package/dist/Dialogs/UploadPhrases/UploadPhrases.js +75 -14
  4. package/dist/Dialogs/UploadPhrases/upload-phrases-csv.png +0 -0
  5. package/dist/Editors/Phrases/Phrases.js +19 -3
  6. package/dist/Lists/Phrases/Phrases.js +9 -5
  7. package/dist/Misc/ChatBubble/ChatBubble.js +7 -2
  8. package/dist/Misc/HoverText/HoverText.js +18 -7
  9. package/dist/Misc/RubyText/RubyText.js +27 -0
  10. package/dist/Screens/Courses/ViewCourse/ViewTopic/ViewTopic.js +0 -89
  11. package/dist/Tables/Progress/Header.js +557 -0
  12. package/dist/Tables/Progress/Progress.js +54 -349
  13. package/dist/Tables/Progress/ProgressList.js +21 -43
  14. package/dist/Tables/Progress/ProgressList.test.js +48 -0
  15. package/dist/Tables/Progress/ProgressTable.js +58 -1027
  16. package/dist/Tables/Progress/ProgressTable.test.js +258 -0
  17. package/dist/Tables/Progress/ProgressTableRow.js +270 -0
  18. package/dist/Tables/Progress/README.md +53 -0
  19. package/dist/Tables/Progress/TableToolbar.js +130 -0
  20. package/dist/Tables/Progress/TableToolbar.test.js +69 -0
  21. package/dist/Tables/Progress/cells/index.js +446 -0
  22. package/dist/Tables/Progress/cells/index.test.js +345 -0
  23. package/dist/Tables/Progress/constants.js +6 -0
  24. package/dist/Tables/Progress/exportToExcel.js +224 -0
  25. package/dist/Tables/Progress/exportToExcel.test.js +135 -0
  26. package/dist/Tables/Progress/helpers.js +42 -0
  27. package/dist/Tables/Progress/helpers.test.js +23 -0
  28. package/dist/Tables/Progress/sorting.js +78 -0
  29. package/dist/Tables/Progress/sorting.test.js +69 -0
  30. package/dist/Tables/Progress/styledComponents.js +156 -0
  31. package/dist/Tables/Progress/useProgressUrlParams.js +147 -0
  32. package/dist/Tables/Progress/useProgressUrlParams.test.js +183 -0
  33. package/dist/Tables/Progress/utils.js +2 -2
  34. package/dist/Tables/Progress/utils.test.js +129 -0
  35. package/dist/Tables/Progress/viewState.js +25 -0
  36. package/dist/hooks/usePhonetics.js +42 -0
  37. package/dist/utils/japaneseTokenizer.js +55 -0
  38. package/package.json +4 -1
@@ -101,6 +101,7 @@ function GenerateDefinitions({
101
101
  }
102
102
  };
103
103
  const handleCancel = () => {
104
+ handleClose();
104
105
  setGeneratedDefinitions([]);
105
106
  setErrorMessage(null);
106
107
  };
@@ -1,7 +1,7 @@
1
- import { useState } from "react";
1
+ import { useState, useEffect } from "react";
2
2
  import ResponsiveDialog from "../ResponsiveDialog/ResponsiveDialog";
3
3
  import { AutoFixHigh } from "@mui/icons-material";
4
- import { Alert, Grid, InputAdornment, LinearProgress, MenuItem, Slider, TextField, Tooltip, Typography, ListItem, ListItemText, useMediaQuery } from "@mui/material";
4
+ import { Alert, Grid, InputAdornment, LinearProgress, MenuItem, Slider, TextField, Tooltip, Typography, ListItem, ListItemText, useMediaQuery, FormControlLabel, Switch, Box } from "@mui/material";
5
5
  import { useTheme } from "@mui/material/styles";
6
6
  import { Link } from "react-router";
7
7
  import InfoIcon from "@mui/icons-material/Info";
@@ -115,6 +115,7 @@ function GeneratePhrases({
115
115
  const [errorMessage, setErrorMessage] = useState(null);
116
116
  const [phraseType, setPhraseType] = useState("words");
117
117
  const [generatedPhrases, setGeneratedPhrases] = useState([]);
118
+ const [generateDefinitions, setGenerateDefinitions] = useState(true);
118
119
  const isPhrases = currentPhrases && currentPhrases.length > 0;
119
120
  const phrases = isPhrases ? currentPhrases.map(({
120
121
  phrase,
@@ -188,7 +189,8 @@ function GeneratePhrases({
188
189
  difficulty,
189
190
  phraseType,
190
191
  isPhrases,
191
- phrases
192
+ phrases,
193
+ generateDefinitions
192
194
  }
193
195
  });
194
196
  const phrasesArray = chatGptResponse.result;
@@ -217,6 +219,9 @@ function GeneratePhrases({
217
219
  const handleCancel = () => {
218
220
  setGeneratedPhrases([]);
219
221
  };
222
+ useEffect(() => {
223
+ setGenerateDefinitions(phraseType === 'words');
224
+ }, [phraseType]);
220
225
  return /*#__PURE__*/_jsxs(ResponsiveDialog, {
221
226
  open: open,
222
227
  closeText: generatedPhrases.length > 0 ? t("cancel") : t("close"),
@@ -353,6 +358,31 @@ function GeneratePhrases({
353
358
  min: 4,
354
359
  max: 16
355
360
  })]
361
+ }), /*#__PURE__*/_jsxs(Grid, {
362
+ size: 12,
363
+ children: [/*#__PURE__*/_jsx(Typography, {
364
+ children: /*#__PURE__*/_jsx(Box, {
365
+ children: t("generate_definitions")
366
+ })
367
+ }), /*#__PURE__*/_jsx(FormControlLabel, {
368
+ label: "",
369
+ control: /*#__PURE__*/_jsx(_Fragment, {
370
+ children: /*#__PURE__*/_jsx("span", {
371
+ children: /*#__PURE__*/_jsx(Switch, {
372
+ checked: generateDefinitions,
373
+ onChange: e => {
374
+ setGenerateDefinitions(e.target.checked);
375
+ },
376
+ inputProps: {
377
+ "aria-label": "controlled"
378
+ },
379
+ sx: {
380
+ marginLeft: 0.5
381
+ }
382
+ })
383
+ })
384
+ })
385
+ })]
356
386
  })]
357
387
  }), generatedPhrases.length > 0 && /*#__PURE__*/_jsxs(_Fragment, {
358
388
  children: [/*#__PURE__*/_jsx(Typography, {
@@ -1,11 +1,13 @@
1
1
  import { useState, useRef, useEffect } from "react";
2
2
  import { CSVLink } from "react-csv";
3
- import { Box, DialogContentText, Typography, Alert, TextField, Divider } from "@mui/material";
3
+ import { Box, DialogContentText, Typography, Alert, TextField, Divider, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from "@mui/material";
4
4
  import { makeStyles } from "tss-react/mui";
5
5
  import FileCopyIcon from "@mui/icons-material/FileCopy";
6
6
  import DownloadIcon from "@mui/icons-material/Download";
7
+ import FrontHandIcon from '@mui/icons-material/FrontHand';
8
+ import WavingHandIcon from '@mui/icons-material/WavingHand';
9
+ import BadgeIcon from '@mui/icons-material/Badge';
7
10
  import PhraseList from "../../Lists/Phrases/Phrases";
8
- import ExampleCSVImage from "./upload-phrases-csv.png";
9
11
  import ParseScriptErrors from "../../Lists/ParseScriptErrors/ParseScriptErrors";
10
12
  import DefaultButton from "../../Misc/DefaultColourButton/DefaultColourButton";
11
13
  import CSVUploader from "../../Misc/CSVUploader/CSVUploader";
@@ -19,7 +21,6 @@ import DialogContent from "@mui/material/DialogContent";
19
21
  import useMediaQuery from "@mui/material/useMediaQuery";
20
22
  import { useTheme } from "@mui/material/styles";
21
23
  import Popper from "@mui/material/Popper";
22
- import Paper from "@mui/material/Paper";
23
24
  import Grow from "@mui/material/Grow";
24
25
  import ClickAwayListener from "@mui/material/ClickAwayListener";
25
26
  import MenuItem from "@mui/material/MenuItem";
@@ -29,14 +30,17 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
29
30
  const phrasesData = [{
30
31
  phrase: "Hello",
31
32
  translation: "hola",
33
+ definitions: "a greeting",
32
34
  image: "https://images.nualang.com/eyJidWNrZXQiOiJudWFsYW5nLWltYWdlcy1wcm9kIiwia2V5IjoicGhyYXNlcy9EYTNXcThzUUktMTYwMzg4Nzg3MjU2NS53ZWJwIn0="
33
35
  }, {
34
36
  phrase: "Goodbye",
35
37
  translation: "adios",
38
+ definitions: "a farewell",
36
39
  image: "https://images.nualang.com/eyJidWNrZXQiOiJudWFsYW5nLWltYWdlcy1wcm9kIiwia2V5IjoicGhyYXNlcy9RbXBFcWZYdHItMTYwMzg4Nzk2MjAzMC53ZWJwIn0="
37
40
  }, {
38
41
  phrase: "My name is",
39
42
  translation: "mi nombre es | me llamo",
43
+ definitions: "used to introduce yourself",
40
44
  image: "https://images.nualang.com/eyJidWNrZXQiOiJudWFsYW5nLWltYWdlcy1wcm9kIiwia2V5IjoicGhyYXNlcy9hREFmczhFeTEtMTYwMzg5MDM2MjAxNy53ZWJwIn0="
41
45
  }];
42
46
  const phrasesHeaders = [{
@@ -45,6 +49,9 @@ const phrasesHeaders = [{
45
49
  }, {
46
50
  label: "translations",
47
51
  key: "translation"
52
+ }, {
53
+ label: "definitions",
54
+ key: "definitions"
48
55
  }, {
49
56
  label: "image",
50
57
  key: "image"
@@ -54,6 +61,22 @@ const PhrasesCsvLink = {
54
61
  data: phrasesData,
55
62
  filename: "phrases_template.csv"
56
63
  };
64
+ const csvStructureExampleRows = [{
65
+ phrase: "hello",
66
+ translations: "hola",
67
+ definitions: "A word used to greet someone",
68
+ image: FrontHandIcon
69
+ }, {
70
+ phrase: "goodbye",
71
+ translations: "adios",
72
+ definitions: "A word used when leaving someone",
73
+ image: WavingHandIcon
74
+ }, {
75
+ phrase: "my name is",
76
+ translations: "mi nombre es | me llamo",
77
+ definitions: "A phrase used to tell someone your name",
78
+ image: BadgeIcon
79
+ }];
57
80
  const useStyles = makeStyles()(theme => ({
58
81
  parseErrorsBox: {
59
82
  marginTop: theme.spacing()
@@ -65,10 +88,6 @@ const useStyles = makeStyles()(theme => ({
65
88
  dialog: {
66
89
  marginBottom: "15px"
67
90
  },
68
- image: {
69
- height: "auto",
70
- width: "100%"
71
- },
72
91
  csvButton: {
73
92
  textDecoration: "none"
74
93
  },
@@ -92,6 +111,7 @@ function parsePhraseWithAlternatives(phraseString) {
92
111
  * - phrase: Primary phrase with optional alternatives separated by '|'
93
112
  * Example: "Hello|Hi|Hey"
94
113
  * - translations: Multiple translations separated by '|'
114
+ * - definitions: Multiple definitions separated by '|'
95
115
  * - image: Optional image URL
96
116
  */
97
117
 
@@ -135,10 +155,12 @@ function UploadPhrases({
135
155
  alternatives
136
156
  } = parsePhraseWithAlternatives(data.phrase);
137
157
  const formattedTranslations = data.translations.split("|").map(t => t.trim().replace(/^"|"$/g, "").trim());
158
+ const formattedDefinitions = data.definitions ? data.definitions.split("|").map(d => d.trim().replace(/^"|"$/g, "").trim()).filter(Boolean) : [];
138
159
  return {
139
160
  phrase: primary,
140
161
  alternativeVersions: alternatives,
141
162
  translations: formattedTranslations,
163
+ definitions: formattedDefinitions,
142
164
  image: data.image ? data.image.trim() : "",
143
165
  voices: [],
144
166
  idx: index
@@ -175,8 +197,6 @@ function UploadPhrases({
175
197
  setErrorMessage("File must be in the .csv format");
176
198
  return;
177
199
  } else if (results.errors && results.errors.length) {
178
- // formats errors coming from CSVReader into common error format
179
- // used for validating rows
180
200
  const formattedErrors = results.errors.reduce((acc, cur) => {
181
201
  acc.push({
182
202
  row: cur.row,
@@ -212,10 +232,51 @@ function UploadPhrases({
212
232
  children: t("upload_phrases_desc")
213
233
  }), /*#__PURE__*/_jsx(Box, {
214
234
  py: 1,
215
- children: /*#__PURE__*/_jsx("img", {
216
- className: classes.image,
217
- src: ExampleCSVImage,
218
- alt: ""
235
+ children: /*#__PURE__*/_jsx(TableContainer, {
236
+ component: Paper,
237
+ variant: "outlined",
238
+ children: /*#__PURE__*/_jsxs(Table, {
239
+ size: "small",
240
+ "aria-label": "CSV structure example",
241
+ children: [/*#__PURE__*/_jsx(TableHead, {
242
+ children: /*#__PURE__*/_jsxs(TableRow, {
243
+ children: [/*#__PURE__*/_jsx(TableCell, {
244
+ children: /*#__PURE__*/_jsx("strong", {
245
+ children: t("phrase")
246
+ })
247
+ }), /*#__PURE__*/_jsx(TableCell, {
248
+ children: /*#__PURE__*/_jsx("strong", {
249
+ children: t("translations")
250
+ })
251
+ }), /*#__PURE__*/_jsx(TableCell, {
252
+ children: /*#__PURE__*/_jsx("strong", {
253
+ children: t("definitions")
254
+ })
255
+ }), /*#__PURE__*/_jsx(TableCell, {
256
+ children: /*#__PURE__*/_jsx("strong", {
257
+ children: t("image")
258
+ })
259
+ })]
260
+ })
261
+ }), /*#__PURE__*/_jsx(TableBody, {
262
+ children: csvStructureExampleRows.map((row, index) => {
263
+ const ImageIcon = row.image;
264
+ return /*#__PURE__*/_jsxs(TableRow, {
265
+ children: [/*#__PURE__*/_jsx(TableCell, {
266
+ children: row.phrase
267
+ }), /*#__PURE__*/_jsx(TableCell, {
268
+ children: row.translations
269
+ }), /*#__PURE__*/_jsx(TableCell, {
270
+ children: row.definitions
271
+ }), /*#__PURE__*/_jsx(TableCell, {
272
+ children: /*#__PURE__*/_jsx(ImageIcon, {
273
+ fontSize: "small"
274
+ })
275
+ })]
276
+ }, `${row.phrase}-${index}`);
277
+ })
278
+ })]
279
+ })
219
280
  })
220
281
  }), /*#__PURE__*/_jsxs(Box, {
221
282
  pb: 2,
@@ -257,7 +318,7 @@ function UploadPhrases({
257
318
  id: "csvDataInput",
258
319
  name: "csvDataInput",
259
320
  label: t("paste_your_csv_data"),
260
- placeholder: `phrase, translations\n"Hola", "Hello | Hi"\n"Adios", "Goodbye"`,
321
+ placeholder: `phrase, translations, definitions\n"Hola", "Hello | Hi", "a greeting"\n"Adios", "Goodbye", "a farewell"`,
261
322
  value: csvDataInput,
262
323
  fullWidth: true,
263
324
  multiline: true,
@@ -315,7 +315,8 @@ function PhrasesEditor(props) {
315
315
  ...r,
316
316
  phrase: r.phrase.split("|")[0].trim() || "",
317
317
  alternativeVersions: updatedAlternativeVersions,
318
- translations: (r.translations || "").split("|").map(t => t.trim()).filter(Boolean)
318
+ translations: (r.translations || "").split("|").map(t => t.trim()).filter(Boolean),
319
+ definitions: (r.definitions || "").split("|").map(d => d.trim()).filter(Boolean)
319
320
  };
320
321
  });
321
322
  };
@@ -405,7 +406,22 @@ function PhrasesEditor(props) {
405
406
  onChange: onFieldChange(row.id, "translations")
406
407
  });
407
408
  },
408
- width: 30
409
+ width: 25
410
+ }, {
411
+ id: "definitions",
412
+ title: () => /*#__PURE__*/_jsx(Typography, {
413
+ children: t("definitions")
414
+ }),
415
+ value: (row, {
416
+ focus
417
+ }) => {
418
+ return /*#__PURE__*/_jsx(Input, {
419
+ value: row.definitions,
420
+ focus: focus,
421
+ onChange: onFieldChange(row.id, "definitions")
422
+ });
423
+ },
424
+ width: 25
409
425
  }, {
410
426
  id: "image",
411
427
  title: () => /*#__PURE__*/_jsx(Typography, {
@@ -475,7 +491,7 @@ function PhrasesEditor(props) {
475
491
  phrase: [row.phrase || "", ...(row.alternativeVersions || [])].filter(Boolean).join(" | ")
476
492
  }));
477
493
  const parser = new Parser({
478
- fields: ["phrase", "translations", "image"],
494
+ fields: ["phrase", "translations", "definitions", "image"],
479
495
  defaultValue: ""
480
496
  });
481
497
  const csv = parser.parse(formattedRows);
@@ -70,6 +70,11 @@ const useStyles = makeStyles()(theme => ({
70
70
  overflow: "hidden",
71
71
  whiteSpace: "nowrap",
72
72
  textOverflow: "ellipsis"
73
+ },
74
+ actionButton: {
75
+ marginTop: theme.spacing(0.5),
76
+ marginBottom: theme.spacing(0.5),
77
+ marginRight: theme.spacing()
73
78
  }
74
79
  }));
75
80
  function Phrase(props) {
@@ -144,11 +149,6 @@ function Phrase(props) {
144
149
  return;
145
150
  }
146
151
  };
147
- console.log("Phrase render", {
148
- phrase,
149
- translations,
150
- alternativeVersions
151
- });
152
152
  const handleCloseMenu = () => {
153
153
  setAnchorEl(null);
154
154
  };
@@ -593,6 +593,7 @@ function PhraseList({
593
593
  color: "primary",
594
594
  onClick: () => handleDeletePhrases(),
595
595
  startIcon: isLargeScreen ? /*#__PURE__*/_jsx(DeleteIcon, {}) : null,
596
+ className: classes.actionButton,
596
597
  style: {
597
598
  marginLeft: "10px"
598
599
  },
@@ -601,6 +602,7 @@ function PhraseList({
601
602
  disabled: selectedPhrases.length === 0,
602
603
  variant: "contained",
603
604
  color: "secondary",
605
+ className: classes.actionButton,
604
606
  "aria-label": t("generate_definitions"),
605
607
  onClick: () => setIsGenerateDefinitionsOpen(true),
606
608
  startIcon: isLargeScreen ? /*#__PURE__*/_jsx(AutoFixHighIcon, {}) : null,
@@ -705,10 +707,12 @@ function PhraseList({
705
707
  style: {
706
708
  marginLeft: "10px"
707
709
  },
710
+ className: classes.actionButton,
708
711
  children: isLargeScreen ? t("Remove Phrases") : /*#__PURE__*/_jsx(DeleteIcon, {})
709
712
  }), /*#__PURE__*/_jsx(Button, {
710
713
  variant: "contained",
711
714
  color: "secondary",
715
+ className: classes.actionButton,
712
716
  disabled: selectedPhrases.length === 0,
713
717
  "aria-label": t("generate_definitions"),
714
718
  onClick: () => setIsGenerateDefinitionsOpen(true),
@@ -5,7 +5,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
5
5
  const useStyles = makeStyles()(theme => ({
6
6
  grid: {
7
7
  backgroundColor: theme.palette.background.paper,
8
- padding: 15,
9
8
  marginTop: 5,
10
9
  marginBottom: 10,
11
10
  marginRight: 5,
@@ -41,9 +40,14 @@ function ChatBubble({
41
40
  const {
42
41
  classes
43
42
  } = useStyles();
43
+ const showPhonetics = learnLang === "chinese" || learnLang === "japanese";
44
44
  return /*#__PURE__*/_jsx(Grid, {
45
45
  className: classes.grid,
46
46
  id: id,
47
+ sx: {
48
+ padding: "15px",
49
+ paddingTop: showPhonetics ? "30px" : "15px"
50
+ },
47
51
  children: /*#__PURE__*/_jsx(HoverText, {
48
52
  handleTranslate: handleTranslate,
49
53
  handleSpeak: handleSpeak,
@@ -59,7 +63,8 @@ function ChatBubble({
59
63
  isMessage: isMessage,
60
64
  muteSound: muteSound,
61
65
  t: t,
62
- languageTag: languageTag
66
+ languageTag: languageTag,
67
+ showPhonetics: showPhonetics
63
68
  })
64
69
  });
65
70
  }
@@ -3,9 +3,11 @@ import { Typography, Tooltip } from "@mui/material";
3
3
  import { useTheme } from "@emotion/react";
4
4
  import ReactMarkdown from "react-markdown";
5
5
  import { removeExtraWhiteSpaces } from "../../utils";
6
+ import { usePhonetics } from "../../hooks/usePhonetics";
7
+ import RubyText from "../RubyText/RubyText";
6
8
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
7
9
  function wordTextPropsAreEqual(prevProps, nextProps) {
8
- return prevProps.value === nextProps.value && prevProps.disableTranslation === nextProps.disableTranslation && prevProps.learnLang === nextProps.learnLang && prevProps.forLang === nextProps.forLang && prevProps.voice === nextProps.voice && prevProps.voicePitch === nextProps.voicePitch && prevProps.voiceSpeed === nextProps.voiceSpeed && prevProps.muteSound === nextProps.muteSound && prevProps.disableHover === nextProps.disableHover;
10
+ return prevProps.value === nextProps.value && prevProps.disableTranslation === nextProps.disableTranslation && prevProps.learnLang === nextProps.learnLang && prevProps.forLang === nextProps.forLang && prevProps.voice === nextProps.voice && prevProps.voicePitch === nextProps.voicePitch && prevProps.voiceSpeed === nextProps.voiceSpeed && prevProps.muteSound === nextProps.muteSound && prevProps.disableHover === nextProps.disableHover && prevProps.showPhonetics === nextProps.showPhonetics;
9
11
  }
10
12
  const isOnlyUnderscore = str => /^([_])\1+$/.test(str);
11
13
  const WordText = /*#__PURE__*/memo(props => {
@@ -20,7 +22,8 @@ const WordText = /*#__PURE__*/memo(props => {
20
22
  voicePitch,
21
23
  voiceSpeed,
22
24
  muteSound,
23
- disableHover
25
+ disableHover,
26
+ showPhonetics
24
27
  } = props;
25
28
  const [translationWord, setTranslationWord] = useState({
26
29
  translatedText: ""
@@ -28,6 +31,8 @@ const WordText = /*#__PURE__*/memo(props => {
28
31
  const {
29
32
  translatedText
30
33
  } = translationWord;
34
+ const isPhoneticLanguage = learnLang === "chinese" || learnLang === "japanese";
35
+ const phoneticSegments = usePhonetics(showPhonetics && isPhoneticLanguage ? value?.trim() : null, learnLang);
31
36
  const translateWord = async word => {
32
37
  try {
33
38
  const translationResult = await handleTranslate(word, learnLang, forLang);
@@ -73,6 +78,9 @@ const WordText = /*#__PURE__*/memo(props => {
73
78
  children: value
74
79
  });
75
80
  }
81
+ const displayContent = showPhonetics && phoneticSegments ? /*#__PURE__*/_jsx(RubyText, {
82
+ segments: phoneticSegments
83
+ }) : value;
76
84
  return /*#__PURE__*/_jsx(Tooltip, {
77
85
  onOpen: () => handleHover(value),
78
86
  enterDelay: 500,
@@ -82,7 +90,7 @@ const WordText = /*#__PURE__*/memo(props => {
82
90
  children: /*#__PURE__*/_jsx("span", {
83
91
  translate: "no",
84
92
  "data-text": value.trim(),
85
- children: value
93
+ children: displayContent
86
94
  })
87
95
  });
88
96
  }, wordTextPropsAreEqual);
@@ -113,7 +121,8 @@ function HoverText({
113
121
  muteSound,
114
122
  disableHover,
115
123
  t,
116
- languageTag
124
+ languageTag,
125
+ showPhonetics
117
126
  }) {
118
127
  const theme = useTheme();
119
128
  let formattedSource = formatUnderscores ? detectAndFormatUnderscores(source) : source;
@@ -163,7 +172,8 @@ function HoverText({
163
172
  voicePitch: voicePitch,
164
173
  voiceSpeed: voiceSpeed,
165
174
  muteSound: muteSound,
166
- disableHover: disableHover
175
+ disableHover: disableHover,
176
+ showPhonetics: showPhonetics
167
177
  }, index);
168
178
  }
169
179
  return word;
@@ -233,7 +243,8 @@ function HoverText({
233
243
  voice: voice,
234
244
  voicePitch: voicePitch,
235
245
  voiceSpeed: voiceSpeed,
236
- muteSound: muteSound
246
+ muteSound: muteSound,
247
+ showPhonetics: showPhonetics
237
248
  }, index)), formattedSource && /*#__PURE__*/_jsx(ReactMarkdown, {
238
249
  components: components,
239
250
  children: formattedSource
@@ -242,6 +253,6 @@ function HoverText({
242
253
  });
243
254
  }
244
255
  function hoverTextPropsAreEqual(prevProps, nextProps) {
245
- return prevProps.source === nextProps.source && prevProps.wordList === nextProps.wordList && prevProps.disableTranslation === nextProps.disableTranslation && prevProps.learnLang === nextProps.learnLang && prevProps.forLang === nextProps.forLang && prevProps.voice === nextProps.voice && prevProps.voicePitch === nextProps.voicePitch && prevProps.voiceSpeed === nextProps.voiceSpeed && prevProps.isMessage === nextProps.isMessage && prevProps.gutterBottom === nextProps.gutterBottom && prevProps.muteSound === nextProps.muteSound && prevProps.disableHover === nextProps.disableHover;
256
+ return prevProps.source === nextProps.source && prevProps.wordList === nextProps.wordList && prevProps.disableTranslation === nextProps.disableTranslation && prevProps.learnLang === nextProps.learnLang && prevProps.forLang === nextProps.forLang && prevProps.voice === nextProps.voice && prevProps.voicePitch === nextProps.voicePitch && prevProps.voiceSpeed === nextProps.voiceSpeed && prevProps.isMessage === nextProps.isMessage && prevProps.gutterBottom === nextProps.gutterBottom && prevProps.muteSound === nextProps.muteSound && prevProps.disableHover === nextProps.disableHover && prevProps.showPhonetics === nextProps.showPhonetics;
246
257
  }
247
258
  export default /*#__PURE__*/memo(HoverText, hoverTextPropsAreEqual);
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ function RubyText({
3
+ segments
4
+ }) {
5
+ if (!segments) return null;
6
+ return /*#__PURE__*/_jsx(_Fragment, {
7
+ children: segments.map((seg, i) => seg.phonetic ? /*#__PURE__*/_jsxs("ruby", {
8
+ style: {
9
+ rubyAlign: "center"
10
+ },
11
+ children: [seg.text, /*#__PURE__*/_jsx("rp", {
12
+ children: "("
13
+ }), /*#__PURE__*/_jsx("rt", {
14
+ style: {
15
+ fontSize: "0.6em",
16
+ opacity: 0.75
17
+ },
18
+ children: seg.phonetic
19
+ }), /*#__PURE__*/_jsx("rp", {
20
+ children: ")"
21
+ })]
22
+ }, i) : /*#__PURE__*/_jsx("span", {
23
+ children: seg.text
24
+ }, i))
25
+ });
26
+ }
27
+ export default RubyText;
@@ -23,11 +23,9 @@ import PhraseList from "../../../../Lists/Phrases/Phrases";
23
23
  import UpdateTopic from "../../../../Forms/UpdateTopic/UpdateTopic";
24
24
  import UpgradeSubscription from "../../../../Dialogs/UpgradeSubscription/UpgradeSubscription";
25
25
  import ShareDrawer from "../../../../Misc/ShareButton/ShareDrawer";
26
- import Notes from "../../../../Misc/Notes/Notes";
27
26
  import { randomId } from "../../../../utils/index";
28
27
  import PlaceholderImages from "../../../../utils/placeholder-images/index";
29
28
  import useConfirm from "../../../../hooks/useConfirm";
30
- import PhrasesEditor from "../../../../Editors/Phrases/Phrases";
31
29
  import ProgressiveCardMedia from "../../../../Misc/ProgressiveCardMedia/ProgressiveCardMedia";
32
30
  import CreatorNotSubscribed from "../../../../Dialogs/CreatorNotSubscribed/CreatorNotSubscribed";
33
31
  import Overlay from "../../../../Misc/Overlay/Overlay";
@@ -520,8 +518,6 @@ function Topic({
520
518
  const [isViewPdfOpen, setIsViewPdfOpen] = useState(false);
521
519
  const handleOpenViewPDF = () => setIsViewPdfOpen(true);
522
520
  const handleCloseViewPdf = () => setIsViewPdfOpen(false);
523
- const [isCreatePhraseDialogOpen, setIsCreatePhraseDialogOpen] = useState(false);
524
- const [isUploadPhrasesDialogOpen, setIsUploadPhrasesDialogOpen] = useState(false);
525
521
  const topicCompletion = useMemo(() => {
526
522
  if (isMember && topic?.topicId && topic?.completions?.length) {
527
523
  return calcTopicCompletions(topic, topic.completions, false, null, isChallengeModeStudent).completion;
@@ -653,34 +649,6 @@ function Topic({
653
649
  bannerXs,
654
650
  difficulty
655
651
  } = topic;
656
- const handleAddPhrase = async values => {
657
- try {
658
- const response = await handleCreatePhrase(courseId, sectionId, topicId, values);
659
- if (response) {
660
- setIsCreatePhraseDialogOpen(false);
661
- return response;
662
- }
663
- return null;
664
- } catch (error) {
665
- console.error(error);
666
- return null;
667
- }
668
- };
669
- const updatePhrase = async (index, updatedPhrase) => {
670
- try {
671
- const response = await handleUpdatePhrase(courseId, sectionId, topicId, index, updatedPhrase);
672
- return response;
673
- } catch (error) {
674
- console.error(error);
675
- }
676
- };
677
- const updatePhrases = async phrases => {
678
- try {
679
- handleUpdatePhrases(courseId, sectionId, topicId, phrases);
680
- } catch (error) {
681
- console.error(error);
682
- }
683
- };
684
652
  const updateRoleplays = async roleplays => {
685
653
  try {
686
654
  handleUpdateRoleplays(courseId, sectionId, topicId, roleplays);
@@ -740,13 +708,6 @@ function Topic({
740
708
  }), t("confirmDeletionText"));
741
709
  if (confirmed) handleDeleteTopic(topic);
742
710
  };
743
- const handleRemovePhrase = async phraseId => {
744
- try {
745
- await handleDeletePhrase(courseId, sectionId, topicId, phraseId);
746
- } catch (error) {
747
- console.error(error);
748
- }
749
- };
750
711
  const deletePDF = async () => {
751
712
  const confirmed = await confirm(t("delete_pdf"), t("delete_pdf_confirmation"));
752
713
  if (confirmed) {
@@ -1061,56 +1022,6 @@ function Topic({
1061
1022
  buttonText: !authenticated ? t("sign_in") : t("join"),
1062
1023
  onClick: !authenticated ? handleSignIn : handleJoin
1063
1024
  })]
1064
- }), /*#__PURE__*/_jsx(Box, {
1065
- py: 1,
1066
- children: /*#__PURE__*/_jsx(Paper, {
1067
- children: /*#__PURE__*/_jsx(Box, {
1068
- py: 1,
1069
- children: /*#__PURE__*/_jsx(PhrasesEditor, {
1070
- t: t,
1071
- phrases: phrases,
1072
- isCreator: isCreator,
1073
- isMember: isMember,
1074
- handleTranslate: handleTranslate,
1075
- handleUpdatePhrase: updatePhrase,
1076
- handleRemovePhrase: handleRemovePhrase,
1077
- uploadAudio: uploadAudio,
1078
- handleSpeak: handleSpeak,
1079
- updatePhrases: updatePhrases,
1080
- learnLang: learnLang,
1081
- forLang: forLang,
1082
- voices: voices,
1083
- userAttributes: userAttributes,
1084
- disablePadding: true,
1085
- forLangCharacters: forLangCharacters,
1086
- learnLangCharacters: learnLangCharacters,
1087
- fileSizeLimit: fileSizeLimit,
1088
- handleSpreadsheetSaveChanges: handleSpreadsheetSaveChanges,
1089
- isUploadPhrasesDialogOpen: isUploadPhrasesDialogOpen,
1090
- setIsUploadPhrasesDialogOpen: setIsUploadPhrasesDialogOpen,
1091
- isCreatePhraseDialogOpen: isCreatePhraseDialogOpen,
1092
- setIsCreatePhraseDialogOpen: setIsCreatePhraseDialogOpen,
1093
- handleAddPhrase: handleAddPhrase,
1094
- topicName: topicName,
1095
- validatePhraseRecognition: validatePhraseRecognition,
1096
- siteLanguage: siteLanguage,
1097
- makeChatGptApiRequest: makeChatGptApiRequest,
1098
- difficulty: difficulty,
1099
- subscription: subscription,
1100
- topicGoal: topicGoal,
1101
- verificationStatus: verificationStatus
1102
- })
1103
- })
1104
- })
1105
- }), /*#__PURE__*/_jsx(Box, {
1106
- py: 1,
1107
- children: /*#__PURE__*/_jsx(Notes, {
1108
- t: t,
1109
- isCreator: isCreator,
1110
- initialValues: topic,
1111
- onSubmit: handleUpdateTopic,
1112
- handleNotesImageUpload: handleNotesImageUpload
1113
- })
1114
1025
  }), isCreator && /*#__PURE__*/_jsx(Box, {
1115
1026
  py: 1,
1116
1027
  children: /*#__PURE__*/_jsx(UpdateTopic, {