@tinyweb_dev/oe-exam-sdk 0.2.4 → 0.2.6

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 (153) hide show
  1. package/dist/components/exams/take/ExamTakingPageContainer.js +6 -0
  2. package/dist/components/exams/take/components/QuestionRenderer.js +19 -3
  3. package/dist/components/exams/take/components/question-renderers/ChooseBestAnswerImages.d.ts +10 -0
  4. package/dist/components/exams/take/components/question-renderers/ChooseBestAnswerImages.js +38 -0
  5. package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +5 -11
  6. package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.d.ts +22 -0
  7. package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +60 -0
  8. package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureRenderer.d.ts +16 -0
  9. package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureRenderer.js +75 -0
  10. package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.d.ts +13 -0
  11. package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +31 -0
  12. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseCorrectGroupRenderer.d.ts +18 -0
  13. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseCorrectGroupRenderer.js +92 -0
  14. package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.d.ts +2 -1
  15. package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.js +2 -2
  16. package/dist/components/exams/take/components/question-renderers/index.d.ts +4 -0
  17. package/dist/components/exams/take/components/question-renderers/index.js +4 -0
  18. package/dist/components/exams/take/exam-taking.utils.d.ts +1 -0
  19. package/dist/components/exams/take/exam-taking.utils.js +9 -1
  20. package/dist/components/exams/take/types.d.ts +3 -2
  21. package/dist/components/exams/take/utils/answer-transformers.js +28 -0
  22. package/dist/components/exams/take/utils/question-transformers.d.ts +73 -1
  23. package/dist/components/exams/take/utils/question-transformers.js +188 -0
  24. package/dist/components/questions/_shared/config/question-types.config.js +24 -0
  25. package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +9 -0
  26. package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +49 -1
  27. package/dist/components/questions/_shared/types/fill-in-blank-group.type.d.ts +50 -0
  28. package/dist/components/questions/_shared/types/fill-in-blank-group.type.js +2 -0
  29. package/dist/components/questions/_shared/types/index.d.ts +4 -0
  30. package/dist/components/questions/_shared/types/index.js +4 -0
  31. package/dist/components/questions/_shared/types/match-word-to-picture.type.d.ts +62 -0
  32. package/dist/components/questions/_shared/types/match-word-to-picture.type.js +1 -0
  33. package/dist/components/questions/_shared/types/matching-with-lines-group.type.d.ts +40 -0
  34. package/dist/components/questions/_shared/types/matching-with-lines-group.type.js +1 -0
  35. package/dist/components/questions/_shared/types/true-false-correct-group.type.d.ts +42 -0
  36. package/dist/components/questions/_shared/types/true-false-correct-group.type.js +2 -0
  37. package/dist/components/questions/_shared/types/write-a-short-letter.type.d.ts +4 -0
  38. package/dist/components/questions/components/QuestionSearchDropdown.js +6 -0
  39. package/dist/components/questions/components/QuestionTypeSelector.js +3 -0
  40. package/dist/components/questions/creator/question-type-registry.js +8 -0
  41. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.d.ts +13 -0
  42. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +32 -0
  43. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.d.ts +1 -1
  44. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +42 -6
  45. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +40 -20
  46. package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.d.ts +1 -1
  47. package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +5 -0
  48. package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +4 -0
  49. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.d.ts +2 -0
  50. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +64 -0
  51. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.d.ts +2 -0
  52. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +158 -0
  53. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.d.ts +7 -0
  54. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +30 -0
  55. package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +20 -0
  56. package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +99 -0
  57. package/dist/components/questions/types/fill-in-blank-group/index.d.ts +5 -0
  58. package/dist/components/questions/types/fill-in-blank-group/index.js +5 -0
  59. package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.d.ts +4 -0
  60. package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.js +102 -0
  61. package/dist/components/questions/types/fill-in-blank-group/register.d.ts +3 -0
  62. package/dist/components/questions/types/fill-in-blank-group/register.js +37 -0
  63. package/dist/components/questions/types/fill-in-blank-group/transform.d.ts +2 -0
  64. package/dist/components/questions/types/fill-in-blank-group/transform.js +68 -0
  65. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.d.ts +2 -0
  66. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +31 -0
  67. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.d.ts +2 -0
  68. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +267 -0
  69. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.d.ts +2 -0
  70. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +65 -0
  71. package/dist/components/questions/types/match-word-to-picture/WordBankEditor.d.ts +8 -0
  72. package/dist/components/questions/types/match-word-to-picture/WordBankEditor.js +15 -0
  73. package/dist/components/questions/types/match-word-to-picture/index.d.ts +6 -0
  74. package/dist/components/questions/types/match-word-to-picture/index.js +6 -0
  75. package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-data.d.ts +2 -0
  76. package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-data.js +53 -0
  77. package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-group-data.d.ts +4 -0
  78. package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-group-data.js +75 -0
  79. package/dist/components/questions/types/match-word-to-picture/register.d.ts +2 -0
  80. package/dist/components/questions/types/match-word-to-picture/register.js +55 -0
  81. package/dist/components/questions/types/match-word-to-picture/transform.d.ts +11 -0
  82. package/dist/components/questions/types/match-word-to-picture/transform.js +43 -0
  83. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.d.ts +20 -0
  84. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +109 -0
  85. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.d.ts +2 -0
  86. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +30 -0
  87. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.d.ts +2 -0
  88. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +180 -0
  89. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +6 -0
  90. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +26 -0
  91. package/dist/components/questions/types/matching-with-lines-group/index.d.ts +4 -0
  92. package/dist/components/questions/types/matching-with-lines-group/index.js +4 -0
  93. package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.d.ts +8 -0
  94. package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +104 -0
  95. package/dist/components/questions/types/matching-with-lines-group/register.d.ts +3 -0
  96. package/dist/components/questions/types/matching-with-lines-group/register.js +37 -0
  97. package/dist/components/questions/types/matching-with-lines-group/transform.d.ts +2 -0
  98. package/dist/components/questions/types/matching-with-lines-group/transform.js +44 -0
  99. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.d.ts +2 -0
  100. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +25 -0
  101. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.d.ts +2 -0
  102. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +144 -0
  103. package/dist/components/questions/types/true-false-correct-group/index.d.ts +5 -0
  104. package/dist/components/questions/types/true-false-correct-group/index.js +5 -0
  105. package/dist/components/questions/types/true-false-correct-group/map-true-false-correct-group-data.d.ts +4 -0
  106. package/dist/components/questions/types/true-false-correct-group/map-true-false-correct-group-data.js +79 -0
  107. package/dist/components/questions/types/true-false-correct-group/register.d.ts +2 -0
  108. package/dist/components/questions/types/true-false-correct-group/register.js +36 -0
  109. package/dist/components/questions/types/true-false-correct-group/transform.d.ts +2 -0
  110. package/dist/components/questions/types/true-false-correct-group/transform.js +49 -0
  111. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +11 -1
  112. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +8 -2
  113. package/dist/components/questions/types/write-a-short-letter/register.js +4 -0
  114. package/dist/components/questions/types/write-a-short-letter/transform.js +4 -0
  115. package/dist/components/questions/viewer/ChooseTheCorrectAnswerGroupViewer.d.ts +2 -1
  116. package/dist/components/questions/viewer/ChooseTheCorrectAnswerGroupViewer.js +2 -2
  117. package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +10 -0
  118. package/dist/components/questions/viewer/FillInBlankGroupViewer.js +14 -0
  119. package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.d.ts +9 -0
  120. package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.js +11 -0
  121. package/dist/components/questions/viewer/MatchWordToPictureViewer.d.ts +9 -0
  122. package/dist/components/questions/viewer/MatchWordToPictureViewer.js +8 -0
  123. package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.d.ts +7 -0
  124. package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.js +48 -0
  125. package/dist/components/questions/viewer/QuestionViewer.js +24 -3
  126. package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.d.ts +9 -0
  127. package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.js +14 -0
  128. package/dist/components/questions/viewer/index.d.ts +5 -0
  129. package/dist/components/questions/viewer/index.js +5 -0
  130. package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.js +4 -9
  131. package/dist/components/results/renderers/review-question-body-movers.js +74 -3
  132. package/dist/components/results/review-data.js +59 -0
  133. package/dist/components/themes/english-certification/EcWritingTask.js +2 -2
  134. package/dist/index.d.ts +1 -1
  135. package/dist/index.js +1 -1
  136. package/dist/shared/constants/question-skills.js +8 -0
  137. package/dist/shared/lib/hooks/usePresignedFileUrl.d.ts +3 -1
  138. package/dist/shared/lib/hooks/usePresignedFileUrl.js +30 -12
  139. package/dist/shared/lib/utils/question-reverse-transform.js +142 -0
  140. package/dist/shared/lib/utils/question-transform.js +8 -0
  141. package/dist/shared/types/common.types.d.ts +1 -1
  142. package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -0
  143. package/dist/shared/types/questions/fill-in-blank-group.d.ts +45 -0
  144. package/dist/shared/types/questions/fill-in-blank-group.js +1 -0
  145. package/dist/shared/types/questions/index.d.ts +4 -0
  146. package/dist/shared/types/questions/index.js +8 -0
  147. package/dist/shared/types/questions/match-word-to-picture.d.ts +35 -0
  148. package/dist/shared/types/questions/match-word-to-picture.js +5 -0
  149. package/dist/shared/types/questions/matching-with-lines-group.d.ts +39 -0
  150. package/dist/shared/types/questions/matching-with-lines-group.js +1 -0
  151. package/dist/shared/types/questions/true-false-correct-group.d.ts +39 -0
  152. package/dist/shared/types/questions/true-false-correct-group.js +1 -0
  153. package/package.json +7 -2
@@ -10,14 +10,18 @@ export { MoversLabelThePictureRenderer } from './MoversLabelThePictureRenderer';
10
10
  export { MoversFillWordHintRenderer } from './MoversFillWordHintRenderer';
11
11
  export { MoversGridFillRenderer } from './MoversGridFillRenderer';
12
12
  export { MoversTrueFalseRenderer } from './MoversTrueFalseRenderer';
13
+ export { MoversTrueFalseCorrectGroupRenderer } from './MoversTrueFalseCorrectGroupRenderer';
14
+ export { MoversFillInBlankGroupRenderer } from './MoversFillInBlankGroupRenderer';
13
15
  export { MoversCrossOutWordGroupRenderer } from './MoversCrossOutWordGroupRenderer';
14
16
  export { MoversAnswerTheQuestionRenderer } from './MoversAnswerTheQuestionRenderer';
15
17
  export { MoversWriteSentencesRenderer } from './MoversWriteSentencesRenderer';
16
18
  export { MoversWordOrderingRenderer } from './MoversWordOrderingRenderer';
17
19
  export { MoversWordFillParagraphRenderer } from './MoversWordFillParagraphRenderer';
18
20
  export { MoversMatchByWritingAnswerRenderer } from './MoversMatchByWritingAnswerRenderer';
21
+ export { MoversMatchWordToPictureRenderer } from './MoversMatchWordToPictureRenderer';
19
22
  export { MoversWordFillStructuredFormRenderer } from './MoversWordFillStructuredFormRenderer';
20
23
  export { MoversWordOrderAndMatchGroupRenderer } from './MoversWordOrderAndMatchGroupRenderer';
24
+ export { MoversMatchingWithLinesGroupRenderer } from './MoversMatchingWithLinesGroupRenderer';
21
25
  export { MoversCrosswordPuzzleRenderer } from './MoversCrosswordPuzzleRenderer';
22
26
  export { FindWordsInMatrixRenderer } from './FindWordsInMatrixRenderer';
23
27
  export { ArticlesViewer } from './ArticlesViewer';
@@ -14,14 +14,18 @@ export { MoversFillWordHintRenderer } from './MoversFillWordHintRenderer';
14
14
  export { MoversGridFillRenderer } from './MoversGridFillRenderer';
15
15
  // Starters & Movers renderers
16
16
  export { MoversTrueFalseRenderer } from './MoversTrueFalseRenderer';
17
+ export { MoversTrueFalseCorrectGroupRenderer } from './MoversTrueFalseCorrectGroupRenderer';
18
+ export { MoversFillInBlankGroupRenderer } from './MoversFillInBlankGroupRenderer';
17
19
  export { MoversCrossOutWordGroupRenderer } from './MoversCrossOutWordGroupRenderer';
18
20
  export { MoversAnswerTheQuestionRenderer } from './MoversAnswerTheQuestionRenderer';
19
21
  export { MoversWriteSentencesRenderer } from './MoversWriteSentencesRenderer';
20
22
  export { MoversWordOrderingRenderer } from './MoversWordOrderingRenderer';
21
23
  export { MoversWordFillParagraphRenderer } from './MoversWordFillParagraphRenderer';
22
24
  export { MoversMatchByWritingAnswerRenderer } from './MoversMatchByWritingAnswerRenderer';
25
+ export { MoversMatchWordToPictureRenderer } from './MoversMatchWordToPictureRenderer';
23
26
  export { MoversWordFillStructuredFormRenderer } from './MoversWordFillStructuredFormRenderer';
24
27
  export { MoversWordOrderAndMatchGroupRenderer } from './MoversWordOrderAndMatchGroupRenderer';
28
+ export { MoversMatchingWithLinesGroupRenderer } from './MoversMatchingWithLinesGroupRenderer';
25
29
  export { MoversCrosswordPuzzleRenderer } from './MoversCrosswordPuzzleRenderer';
26
30
  export { FindWordsInMatrixRenderer } from './FindWordsInMatrixRenderer';
27
31
  export { ArticlesViewer } from './ArticlesViewer';
@@ -113,6 +113,7 @@ export declare function transformWriteShortLetter(question: ExamTakingQuestion |
113
113
  minWords: number | undefined;
114
114
  maxWords: number | undefined;
115
115
  guideImages: string[];
116
+ passage: string | undefined;
116
117
  };
117
118
  export declare function transformWordFillStructuredForm(questions: ExamTakingQuestion[]): {
118
119
  instruction: string;
@@ -61,7 +61,7 @@ export function transformWordFillParagraph(questions) {
61
61
  */
62
62
  export function transformWriteShortLetter(question) {
63
63
  if (!question) {
64
- return { prompt: '', requirements: [], minWords: undefined, maxWords: undefined, guideImages: [] };
64
+ return { prompt: '', requirements: [], minWords: undefined, maxWords: undefined, guideImages: [], passage: undefined };
65
65
  }
66
66
  const content = asRecord(question.content);
67
67
  const instructions = asRecord(content.instructions);
@@ -72,6 +72,12 @@ export function transformWriteShortLetter(question) {
72
72
  const guideImages = readStringArray(question.content, 'guideImages');
73
73
  const minWords = readNumber(question.content, 'minWords');
74
74
  const maxWords = readNumber(question.content, 'maxWords');
75
+ const passageRaw = content.passage ?? content.passages;
76
+ const passage = typeof passageRaw === 'string' && passageRaw.trim()
77
+ ? passageRaw
78
+ : Array.isArray(passageRaw)
79
+ ? passageRaw.filter((item) => typeof item === 'string' && item.trim().length > 0).join('\n\n') || undefined
80
+ : undefined;
75
81
  // New structure: use instructions.mainInstruction as prompt, informations as requirements
76
82
  if (mainInstruction || informations.length) {
77
83
  return {
@@ -80,6 +86,7 @@ export function transformWriteShortLetter(question) {
80
86
  minWords,
81
87
  maxWords,
82
88
  guideImages,
89
+ passage,
83
90
  };
84
91
  }
85
92
  // Legacy structure: build prompt from contexts array
@@ -99,6 +106,7 @@ export function transformWriteShortLetter(question) {
99
106
  minWords,
100
107
  maxWords,
101
108
  guideImages,
109
+ passage,
102
110
  };
103
111
  }
104
112
  export function transformWordFillStructuredForm(questions) {
@@ -115,6 +115,7 @@ export interface ChooseBestAnswerOption {
115
115
  id: string;
116
116
  label: string;
117
117
  text: string;
118
+ imageUrl?: string;
118
119
  }
119
120
  export interface ChooseBestAnswerQuestion {
120
121
  id: string;
@@ -123,7 +124,7 @@ export interface ChooseBestAnswerQuestion {
123
124
  options: ChooseBestAnswerOption[];
124
125
  correctOptionId?: string;
125
126
  correctOptionIds?: string[];
126
- imageUrl?: string;
127
+ imageUrl?: string | string[];
127
128
  audioUrl?: string;
128
129
  optionType?: 'text' | 'image';
129
130
  multipleAnswers?: boolean;
@@ -135,7 +136,7 @@ export interface ChooseBestAnswerExample {
135
136
  options: ChooseBestAnswerOption[];
136
137
  correctOptionId: string;
137
138
  optionType?: 'text' | 'image';
138
- imageUrl?: string;
139
+ imageUrl?: string | string[];
139
140
  audioUrl?: string;
140
141
  }
141
142
  export interface MoversChooseBestAnswerRendererProps {
@@ -87,6 +87,9 @@ function findParentQuestion(subQuestionId, parts) {
87
87
  if (part.questionType === 'CHOOSE_THE_CORRECT_ANSWER_GROUP' ||
88
88
  part.questionType === 'CROSS_OUT_WORD_GROUP' ||
89
89
  part.questionType === 'TRUE_FALSE_GROUP' ||
90
+ part.questionType === 'TRUE_FALSE_CORRECT_GROUP' ||
91
+ part.questionType === 'FILL_IN_BLANK_GROUP' ||
92
+ part.questionType === 'MATCHING_WITH_LINES_GROUP' ||
90
93
  part.questionType === 'WORD_ORDER_AND_MATCH_GROUP' ||
91
94
  part.questionType === 'SPONTANEOUS_QA_GROUP' ||
92
95
  part.questionType === 'ANSWER_THE_QUESTION_GROUP') {
@@ -369,6 +372,31 @@ export function transformAnswersForSubmission(answers, parts) {
369
372
  if (parentType === 'CROSS_OUT_WORD_GROUP') {
370
373
  return normalizeCrossOutWordGroupAnswer(raw);
371
374
  }
375
+ if (parentType === 'TRUE_FALSE_CORRECT_GROUP') {
376
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
377
+ const obj = raw;
378
+ const isTrue = obj.isTrue === true ? true : obj.isTrue === false ? false : null;
379
+ return {
380
+ isTrue,
381
+ correction: typeof obj.correction === 'string' ? obj.correction : '',
382
+ };
383
+ }
384
+ return { isTrue: null, correction: '' };
385
+ }
386
+ if (parentType === 'FILL_IN_BLANK_GROUP') {
387
+ if (raw && typeof raw === 'object' && !Array.isArray(raw) && Array.isArray(raw.answers)) {
388
+ return {
389
+ answers: raw.answers.map((value) => String(value ?? '')),
390
+ };
391
+ }
392
+ if (Array.isArray(raw)) {
393
+ return { answers: raw.map((value) => String(value ?? '')) };
394
+ }
395
+ if (typeof raw === 'string') {
396
+ return { answers: [raw] };
397
+ }
398
+ return { answers: [] };
399
+ }
372
400
  if (parentType === 'WORD_ORDER_AND_MATCH_GROUP') {
373
401
  if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
374
402
  const obj = raw;
@@ -5,7 +5,7 @@
5
5
  * Each transformer handles specific question type data structure.
6
6
  */
7
7
  import type { FillBlankQuestion, ChooseBestAnswerQuestion, PictureChooseSubQuestion, PictureFillBlankSubQuestion, ReadingPassageQuestion, LabelThePictureLabel, FillWordHintSubQuestion, ColoringHintItem, ColoringRegion } from '../types';
8
- export type QuestionType = 'FILL_IN_BLANK' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'CHOOSE_CORRECT_ADJECTIVE' | 'WRITE_A_SHORT_LETTER' | 'READ_AND_COLOR_OBJECTS' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'LABEL_THE_PICTURE' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'FILL_MISSING_WORDS_IN_GRID' | 'IMAGE_OBJECT_MATCHING' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'TRUE_FALSE' | 'TRUE_FALSE_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'WRITE_SENTENCES' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'WORD_ORDER_AND_MATCH_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPONTANEOUS_QA_GROUP' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD';
8
+ export type QuestionType = 'FILL_IN_BLANK' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'CHOOSE_CORRECT_ADJECTIVE' | 'WRITE_A_SHORT_LETTER' | 'READ_AND_COLOR_OBJECTS' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'LABEL_THE_PICTURE' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'FILL_MISSING_WORDS_IN_GRID' | 'IMAGE_OBJECT_MATCHING' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'TRUE_FALSE' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'WORD_ORDER_AND_MATCH_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPONTANEOUS_QA_GROUP' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD';
9
9
  export interface ApiQuestion {
10
10
  id: string;
11
11
  content: any;
@@ -184,6 +184,7 @@ export declare function transformWriteShortLetter(question: ApiQuestion): {
184
184
  minWords?: number;
185
185
  maxWords?: number;
186
186
  guideImages?: string[];
187
+ passage?: string;
187
188
  };
188
189
  export declare function transformColorObjects(questions: ApiQuestion[]): {
189
190
  instruction: string;
@@ -269,6 +270,46 @@ export declare function transformTrueFalseGroup(questions: ApiQuestion[]): {
269
270
  imageUrl?: string;
270
271
  groupContent?: string;
271
272
  };
273
+ export interface TrueFalseCorrectItemAnswer {
274
+ isTrue: boolean | null;
275
+ correction: string;
276
+ }
277
+ export interface TrueFalseCorrectGroupQuestion {
278
+ id: string;
279
+ questionNumber: number;
280
+ question: string;
281
+ isExample: boolean;
282
+ exampleCorrectAnswer?: TrueFalseCorrectItemAnswer;
283
+ }
284
+ export declare function transformTrueFalseCorrectGroup(questions: ApiQuestion[]): {
285
+ questions: TrueFalseCorrectGroupQuestion[];
286
+ instruction: string;
287
+ };
288
+ export interface FillInBlankGroupItemAnswer {
289
+ answers: string[][];
290
+ caseSensitive?: boolean;
291
+ }
292
+ export interface FillInBlankGroupStudentItemAnswer {
293
+ answers: string[];
294
+ }
295
+ export interface FillInBlankGroupQuestion {
296
+ id: string;
297
+ questionNumber: number;
298
+ question: string;
299
+ imageUrl?: string;
300
+ audioUrl?: string;
301
+ isExample: boolean;
302
+ blankCount: number;
303
+ exampleAnswers?: string[];
304
+ }
305
+ export declare function transformFillInBlankGroup(questions: ApiQuestion[]): {
306
+ questions: FillInBlankGroupQuestion[];
307
+ instruction: string;
308
+ audioUrl?: string;
309
+ imageUrl?: string;
310
+ showHints?: boolean;
311
+ hints?: string[];
312
+ };
272
313
  export interface WordOrderingQuestion {
273
314
  id: string;
274
315
  questionNumber: number;
@@ -321,6 +362,18 @@ export declare function transformMatchByWritingAnswer(questions: ApiQuestion[]):
321
362
  askerName?: string;
322
363
  answererName?: string;
323
364
  };
365
+ export interface MatchWordToPictureQuestion {
366
+ id: string;
367
+ questionNumber: number;
368
+ imageUrl?: string;
369
+ isExample: boolean;
370
+ exampleAnswer?: string;
371
+ }
372
+ export declare function transformMatchWordToPicture(questions: ApiQuestion[]): {
373
+ questions: MatchWordToPictureQuestion[];
374
+ wordBank: string[];
375
+ instruction: string;
376
+ };
324
377
  export declare function transformWriteSentences(questions: ApiQuestion[]): {
325
378
  questions: Array<{
326
379
  id: string;
@@ -477,3 +530,22 @@ export interface FindWordsInMatrixPartData {
477
530
  explanation?: string;
478
531
  }
479
532
  export declare function transformFindWordsInMatrix(questions: ApiQuestion[]): FindWordsInMatrixPartData;
533
+ export interface MatchingWithLinesGroupQuestionItem {
534
+ id: string;
535
+ questionNumber: number;
536
+ imageUrl: string;
537
+ correctRightId: string;
538
+ isExample: boolean;
539
+ points: number;
540
+ }
541
+ export interface MatchingWithLinesGroupPartData {
542
+ questions: MatchingWithLinesGroupQuestionItem[];
543
+ rightItems: Array<{
544
+ id: string;
545
+ text: string;
546
+ }>;
547
+ title?: string;
548
+ instruction: string;
549
+ explanation?: string;
550
+ }
551
+ export declare function transformMatchingWithLinesGroup(questions: ApiQuestion[]): MatchingWithLinesGroupPartData;
@@ -428,23 +428,43 @@ export function transformChooseTheCorrectAnswerGroup(questions) {
428
428
  ...(isObj && opt.imageUrl ? { imageUrl: opt.imageUrl } : {}),
429
429
  };
430
430
  });
431
+ const toQuestionImageUrl = (value) => {
432
+ if (typeof value === 'string') {
433
+ const trimmed = value.trim();
434
+ return trimmed !== '' ? trimmed : undefined;
435
+ }
436
+ if (!Array.isArray(value)) {
437
+ return undefined;
438
+ }
439
+ const urls = value
440
+ .filter((url) => typeof url === 'string')
441
+ .map((url) => url.trim())
442
+ .filter((url) => url !== '');
443
+ if (urls.length === 0) {
444
+ return undefined;
445
+ }
446
+ return urls.length === 1 ? urls[0] : urls;
447
+ };
431
448
  let example;
432
449
  if (exampleItem) {
433
450
  const exampleIndex = items.indexOf(exampleItem);
434
451
  const exOptions = mapOptions(exampleItem.content?.options, `${parent.id}-ex`);
435
452
  const exCorrect = exampleItem.correctAnswer?.answer || parentCorrect[exampleIndex] || exOptions[0]?.id || '';
453
+ const exampleImageUrl = toQuestionImageUrl(exampleItem.content?.imageUrl);
436
454
  example = {
437
455
  questionNumber: exampleItem.questionNumber || exampleIndex + 1,
438
456
  questionText: exampleItem.content?.question || '',
439
457
  options: exOptions,
440
458
  correctOptionId: exCorrect,
441
459
  optionType: (exampleItem.content?.optionType || 'text'),
460
+ ...(exampleImageUrl !== undefined ? { imageUrl: exampleImageUrl } : {}),
442
461
  };
443
462
  }
444
463
  const transformed = regularItems.map((item) => {
445
464
  const itemIndex = items.indexOf(item);
446
465
  const options = mapOptions(item.content?.options, `${parent.id}-item-${itemIndex}`);
447
466
  const correctId = item.correctAnswer?.answer || parentCorrect[itemIndex] || '';
467
+ const itemImageUrl = toQuestionImageUrl(item.content?.imageUrl);
448
468
  return {
449
469
  id: `${parent.id}-item-${itemIndex}`,
450
470
  questionNumber: item.questionNumber || itemIndex + 1,
@@ -453,6 +473,7 @@ export function transformChooseTheCorrectAnswerGroup(questions) {
453
473
  correctOptionId: correctId,
454
474
  correctOptionIds: correctId ? [correctId] : [],
455
475
  optionType: (item.content?.optionType || 'text'),
476
+ ...(itemImageUrl !== undefined ? { imageUrl: itemImageUrl } : {}),
456
477
  };
457
478
  });
458
479
  return {
@@ -486,8 +507,19 @@ export function transformChooseCorrectAdjective(questions) {
486
507
  instruction: 'CHOOSE THE CORRECT ADJECTIVE',
487
508
  };
488
509
  }
510
+ function extractWriteLetterPassage(content) {
511
+ const raw = content?.passage ?? content?.passages;
512
+ if (typeof raw === 'string' && raw.trim())
513
+ return raw;
514
+ if (Array.isArray(raw)) {
515
+ const parts = raw.filter((item) => typeof item === 'string' && item.trim().length > 0);
516
+ return parts.length ? parts.join('\n\n') : undefined;
517
+ }
518
+ return undefined;
519
+ }
489
520
  export function transformWriteShortLetter(question) {
490
521
  const content = question.content;
522
+ const passage = extractWriteLetterPassage(content);
491
523
  // New structure: use instructions.mainInstruction as prompt, informations as requirements
492
524
  if (content.instructions?.mainInstruction || content.informations?.length) {
493
525
  return {
@@ -496,6 +528,7 @@ export function transformWriteShortLetter(question) {
496
528
  minWords: content.minWords,
497
529
  maxWords: content.maxWords,
498
530
  guideImages: content.guideImages,
531
+ passage,
499
532
  };
500
533
  }
501
534
  // Legacy structure: build prompt from contexts array
@@ -508,6 +541,7 @@ export function transformWriteShortLetter(question) {
508
541
  minWords: content.minWords,
509
542
  maxWords: content.maxWords,
510
543
  guideImages: content.guideImages,
544
+ passage,
511
545
  };
512
546
  }
513
547
  export function transformColorObjects(questions) {
@@ -889,6 +923,102 @@ export function transformTrueFalseGroup(questions) {
889
923
  instruction: content?.meta?.instruction || 'READ AND CHOOSE TRUE OR FALSE',
890
924
  };
891
925
  }
926
+ function parseTrueFalseCorrectAnswer(raw) {
927
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
928
+ return undefined;
929
+ }
930
+ const obj = raw;
931
+ if (typeof obj.isTrue !== 'boolean') {
932
+ return undefined;
933
+ }
934
+ return {
935
+ isTrue: obj.isTrue,
936
+ correction: obj.isTrue ? '' : (typeof obj.correction === 'string' ? obj.correction : ''),
937
+ };
938
+ }
939
+ export function transformTrueFalseCorrectGroup(questions) {
940
+ const parent = questions[0];
941
+ const content = parent?.content;
942
+ const items = Array.isArray(content?.items) ? content.items : [];
943
+ const parentCorrect = Array.isArray(parent?.correct_answer?.answer)
944
+ ? parent.correct_answer.answer
945
+ : [];
946
+ const transformed = items.map((item, index) => {
947
+ const isExample = item.isExample === true;
948
+ const exampleCorrectAnswer = isExample
949
+ ? parseTrueFalseCorrectAnswer(item.correctAnswer?.answer ?? parentCorrect[index])
950
+ : undefined;
951
+ return {
952
+ id: `${parent.id}-item-${index}`,
953
+ questionNumber: item.questionNumber || index + 1,
954
+ question: item.content?.question || '',
955
+ isExample,
956
+ exampleCorrectAnswer,
957
+ };
958
+ });
959
+ return {
960
+ questions: transformed,
961
+ instruction: content?.meta?.instruction
962
+ || 'Tick (✓) the true and cross (✗) the false sentences. Correct the false sentences.',
963
+ };
964
+ }
965
+ function parseFillInBlankGroupAnswer(raw) {
966
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
967
+ return undefined;
968
+ }
969
+ const obj = raw;
970
+ if (!Array.isArray(obj.answers)) {
971
+ return undefined;
972
+ }
973
+ return {
974
+ answers: obj.answers.map((blank) => (Array.isArray(blank)
975
+ ? blank.map((alt) => String(alt ?? '').trim()).filter(Boolean)
976
+ : [String(blank ?? '').trim()].filter(Boolean))),
977
+ caseSensitive: obj.caseSensitive === true,
978
+ };
979
+ }
980
+ function blankCountFromMatches(matches) {
981
+ const indices = matches.map((match) => Number.parseInt(match[1], 10));
982
+ return Math.max(...indices, 0) + 1;
983
+ }
984
+ export function transformFillInBlankGroup(questions) {
985
+ const parent = questions[0];
986
+ const content = parent?.content;
987
+ const items = Array.isArray(content?.items) ? content.items : [];
988
+ const parentCorrect = Array.isArray(parent?.correct_answer?.answer)
989
+ ? parent.correct_answer.answer
990
+ : [];
991
+ const groupAudioUrl = content?.meta?.audioUrl?.trim() || '';
992
+ const transformed = items.map((item, index) => {
993
+ const parsed = parseFillInBlankGroupAnswer(item.correctAnswer ?? parentCorrect[index]);
994
+ const question = item.content?.question || '{0}';
995
+ const blankMatches = [...question.matchAll(/\{(\d+)\}/g)];
996
+ const blankCount = blankMatches.length > 0 ? blankCountFromMatches(blankMatches) : 1;
997
+ const isExample = item.isExample === true;
998
+ return {
999
+ id: `${parent.id}-item-${index}`,
1000
+ questionNumber: item.questionNumber || index + 1,
1001
+ question,
1002
+ imageUrl: item.content?.imageUrl,
1003
+ audioUrl: groupAudioUrl ? undefined : item.content?.audioUrl,
1004
+ isExample,
1005
+ blankCount,
1006
+ exampleAnswers: isExample
1007
+ ? Array.from({ length: blankCount }, (_, slot) => parsed?.answers?.[slot]?.[0] || '')
1008
+ : undefined,
1009
+ };
1010
+ });
1011
+ return {
1012
+ questions: transformed,
1013
+ instruction: content?.meta?.instruction || 'Complete the sentences.',
1014
+ audioUrl: groupAudioUrl || undefined,
1015
+ imageUrl: content?.meta?.imageUrl,
1016
+ showHints: content?.meta?.showHints === true,
1017
+ hints: Array.isArray(content?.meta?.hints)
1018
+ ? content.meta.hints.map((hint) => String(hint ?? '').trim()).filter(Boolean)
1019
+ : [],
1020
+ };
1021
+ }
892
1022
  export function transformWordOrdering(questions) {
893
1023
  const transformed = questions.map((q) => {
894
1024
  const content = q.content;
@@ -993,6 +1123,31 @@ export function transformMatchByWritingAnswer(questions) {
993
1123
  answererName: firstConversationQ?.answererName,
994
1124
  };
995
1125
  }
1126
+ export function transformMatchWordToPicture(questions) {
1127
+ const groupPayload = questions[0]?.questionGroup?.payload;
1128
+ const wordBank = Array.isArray(groupPayload?.wordBank)
1129
+ ? groupPayload.wordBank.filter((word) => typeof word === 'string' && word.trim().length > 0)
1130
+ : [];
1131
+ const instruction = typeof groupPayload?.instruction === 'string' && groupPayload.instruction.trim()
1132
+ ? groupPayload.instruction.trim()
1133
+ : 'Look and complete.';
1134
+ const transformed = questions.map((q) => {
1135
+ const content = q.content;
1136
+ const isExample = q.is_example || content.isExample || false;
1137
+ return {
1138
+ id: q.id,
1139
+ questionNumber: q.question_number,
1140
+ imageUrl: content.imageUrl,
1141
+ isExample,
1142
+ exampleAnswer: isExample ? q.correct_answer?.answer : undefined,
1143
+ };
1144
+ });
1145
+ return {
1146
+ questions: transformed,
1147
+ wordBank,
1148
+ instruction,
1149
+ };
1150
+ }
996
1151
  export function transformWriteSentences(questions) {
997
1152
  const firstQuestion = questions[0];
998
1153
  const content = firstQuestion?.content;
@@ -1228,3 +1383,36 @@ export function transformFindWordsInMatrix(questions) {
1228
1383
  explanation: question?.explanation,
1229
1384
  };
1230
1385
  }
1386
+ export function transformMatchingWithLinesGroup(questions) {
1387
+ const parent = questions[0];
1388
+ const content = (parent?.content || {});
1389
+ const items = Array.isArray(content.items) ? content.items : [];
1390
+ const parentCorrect = Array.isArray(parent?.correct_answer?.answer)
1391
+ ? parent.correct_answer.answer
1392
+ : [];
1393
+ const rightItems = Array.isArray(content.rightItems)
1394
+ ? content.rightItems.map((item, index) => ({
1395
+ id: typeof item?.id === 'string' ? item.id : `r${index + 1}`,
1396
+ text: typeof item?.text === 'string' ? item.text : '',
1397
+ }))
1398
+ : [];
1399
+ return {
1400
+ questions: items.map((item, index) => {
1401
+ const isExample = item.isExample === true;
1402
+ const fromItem = typeof item.correctAnswer?.answer === 'string' ? item.correctAnswer.answer : '';
1403
+ const fromParent = typeof parentCorrect[index] === 'string' ? parentCorrect[index] : '';
1404
+ return {
1405
+ id: `${parent?.id || 'q'}-item-${index}`,
1406
+ questionNumber: item.questionNumber || index + 1,
1407
+ imageUrl: item.content?.imageUrl || '',
1408
+ correctRightId: fromItem || fromParent,
1409
+ isExample,
1410
+ points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
1411
+ };
1412
+ }),
1413
+ rightItems,
1414
+ title: content.meta?.title || '',
1415
+ instruction: content.meta?.instruction || 'Look and match.',
1416
+ explanation: parent?.explanation,
1417
+ };
1418
+ }
@@ -172,6 +172,16 @@ export const QUESTION_TYPE_CONFIG = {
172
172
  requiresContent: true,
173
173
  hasDedicatedComponent: true,
174
174
  },
175
+ TRUE_FALSE_CORRECT_GROUP: {
176
+ label: 'Đúng/Sai + sửa câu (nhóm)',
177
+ requiresContent: true,
178
+ hasDedicatedComponent: true,
179
+ },
180
+ FILL_IN_BLANK_GROUP: {
181
+ label: 'Điền chỗ trống (nhóm)',
182
+ requiresContent: true,
183
+ hasDedicatedComponent: true,
184
+ },
175
185
  CROSS_OUT_WORD_GROUP: {
176
186
  label: 'Cross out word (group)',
177
187
  requiresContent: true,
@@ -227,6 +237,16 @@ export const QUESTION_TYPE_CONFIG = {
227
237
  requiresContent: true,
228
238
  hasDedicatedComponent: true,
229
239
  },
240
+ MATCH_WORD_TO_PICTURE: {
241
+ label: 'Nối từ với tranh',
242
+ requiresContent: true,
243
+ hasDedicatedComponent: true,
244
+ },
245
+ MATCHING_WITH_LINES_GROUP: {
246
+ label: 'Nối bằng đường kẻ (nhóm)',
247
+ requiresContent: true,
248
+ hasDedicatedComponent: true,
249
+ },
230
250
  WRITE_SENTENCES: {
231
251
  label: 'Viết câu',
232
252
  requiresContent: true,
@@ -343,6 +363,8 @@ export const QUESTION_TYPES_WITH_DEDICATED_COMPONENTS = [
343
363
  'CHOOSE_THE_CORRECT_ANSWER',
344
364
  'CHOOSE_THE_CORRECT_ANSWER_GROUP',
345
365
  'TRUE_FALSE_GROUP',
366
+ 'TRUE_FALSE_CORRECT_GROUP',
367
+ 'FILL_IN_BLANK_GROUP',
346
368
  'CROSS_OUT_WORD_GROUP',
347
369
  'WRITE_A_SHORT_LETTER',
348
370
  'WRITE_CORRECT_VERB_FORM',
@@ -353,6 +375,8 @@ export const QUESTION_TYPES_WITH_DEDICATED_COMPONENTS = [
353
375
  'WORD_ORDERING',
354
376
  'WORD_FILL_PARAGRAPH',
355
377
  'MATCH_BY_WRITING_ANSWER',
378
+ 'MATCH_WORD_TO_PICTURE',
379
+ 'MATCHING_WITH_LINES_GROUP',
356
380
  'WRITE_SENTENCES',
357
381
  'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP',
358
382
  'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE',
@@ -1,7 +1,14 @@
1
1
  export interface ChooseTheCorrectAnswerGroupOptionData {
2
2
  id: string;
3
3
  text: string;
4
+ imageUrl?: string;
4
5
  }
6
+ export declare function getChooseAnswerGroupOptionImageSrc(option: Pick<ChooseTheCorrectAnswerGroupOptionData, 'imageUrl' | 'text'>): string;
7
+ export declare function isChooseAnswerGroupOptionFilled(option: Pick<ChooseTheCorrectAnswerGroupOptionData, 'imageUrl' | 'text'>, optionType?: 'text' | 'image'): boolean;
8
+ export type ChooseTheCorrectAnswerGroupImageUrl = string | string[];
9
+ export declare function toChooseAnswerGroupImageUrls(value?: ChooseTheCorrectAnswerGroupImageUrl): string[];
10
+ export declare function toEditableChooseAnswerGroupImageUrls(value?: ChooseTheCorrectAnswerGroupImageUrl): string[];
11
+ export declare function fromChooseAnswerGroupImageUrls(urls: string[]): ChooseTheCorrectAnswerGroupImageUrl | undefined;
5
12
  export interface ChooseTheCorrectAnswerGroupItemData {
6
13
  question: string;
7
14
  optionType: 'text' | 'image';
@@ -10,6 +17,7 @@ export interface ChooseTheCorrectAnswerGroupItemData {
10
17
  isExample?: boolean;
11
18
  points: number;
12
19
  questionNumber: number;
20
+ imageUrl?: ChooseTheCorrectAnswerGroupImageUrl;
13
21
  }
14
22
  export interface ChooseTheCorrectAnswerGroupData {
15
23
  instruction: string;
@@ -34,4 +42,5 @@ export interface ChooseTheCorrectAnswerGroupClientProps {
34
42
  questionData: ChooseTheCorrectAnswerGroupData;
35
43
  isReviewMode?: boolean;
36
44
  userAnswers?: string[];
45
+ onAnswerChange?: (answers: string[]) => void;
37
46
  }
@@ -1 +1,49 @@
1
- export {};
1
+ export function getChooseAnswerGroupOptionImageSrc(option) {
2
+ if (typeof option.imageUrl === 'string' && option.imageUrl.trim() !== '') {
3
+ return option.imageUrl.trim();
4
+ }
5
+ if (typeof option.text === 'string' && option.text.trim() !== '') {
6
+ return option.text.trim();
7
+ }
8
+ return '';
9
+ }
10
+ export function isChooseAnswerGroupOptionFilled(option, optionType = 'text') {
11
+ if (optionType === 'image') {
12
+ return getChooseAnswerGroupOptionImageSrc(option) !== '';
13
+ }
14
+ return typeof option.text === 'string' && option.text.trim() !== '';
15
+ }
16
+ export function toChooseAnswerGroupImageUrls(value) {
17
+ if (Array.isArray(value)) {
18
+ return value
19
+ .filter((url) => typeof url === 'string')
20
+ .map((url) => url.trim())
21
+ .filter((url) => url !== '');
22
+ }
23
+ if (typeof value === 'string' && value.trim() !== '') {
24
+ return [value.trim()];
25
+ }
26
+ return [];
27
+ }
28
+ export function toEditableChooseAnswerGroupImageUrls(value) {
29
+ if (Array.isArray(value)) {
30
+ return value.filter((url) => typeof url === 'string');
31
+ }
32
+ if (typeof value === 'string') {
33
+ return [value];
34
+ }
35
+ return [];
36
+ }
37
+ export function fromChooseAnswerGroupImageUrls(urls) {
38
+ const cleaned = urls
39
+ .filter((url) => typeof url === 'string')
40
+ .map((url) => url.trim())
41
+ .filter((url) => url !== '');
42
+ if (cleaned.length === 0) {
43
+ return undefined;
44
+ }
45
+ if (cleaned.length === 1) {
46
+ return cleaned[0];
47
+ }
48
+ return cleaned;
49
+ }