@tinyweb_dev/oe-exam-sdk 0.2.5 → 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 (75) hide show
  1. package/dist/components/exams/take/ExamTakingPageContainer.js +6 -0
  2. package/dist/components/exams/take/components/QuestionRenderer.js +10 -2
  3. package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.d.ts +22 -0
  4. package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +60 -0
  5. package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.d.ts +13 -0
  6. package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +31 -0
  7. package/dist/components/exams/take/components/question-renderers/index.d.ts +2 -0
  8. package/dist/components/exams/take/components/question-renderers/index.js +2 -0
  9. package/dist/components/exams/take/utils/answer-transformers.js +16 -0
  10. package/dist/components/exams/take/utils/question-transformers.d.ts +45 -1
  11. package/dist/components/exams/take/utils/question-transformers.js +90 -0
  12. package/dist/components/questions/_shared/config/question-types.config.js +12 -0
  13. package/dist/components/questions/_shared/types/fill-in-blank-group.type.d.ts +50 -0
  14. package/dist/components/questions/_shared/types/fill-in-blank-group.type.js +2 -0
  15. package/dist/components/questions/_shared/types/index.d.ts +2 -0
  16. package/dist/components/questions/_shared/types/index.js +2 -0
  17. package/dist/components/questions/_shared/types/matching-with-lines-group.type.d.ts +40 -0
  18. package/dist/components/questions/_shared/types/matching-with-lines-group.type.js +1 -0
  19. package/dist/components/questions/components/QuestionSearchDropdown.js +4 -0
  20. package/dist/components/questions/components/QuestionTypeSelector.js +2 -0
  21. package/dist/components/questions/creator/question-type-registry.js +4 -0
  22. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.d.ts +2 -0
  23. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +64 -0
  24. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.d.ts +2 -0
  25. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +158 -0
  26. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.d.ts +7 -0
  27. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +30 -0
  28. package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +20 -0
  29. package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +99 -0
  30. package/dist/components/questions/types/fill-in-blank-group/index.d.ts +5 -0
  31. package/dist/components/questions/types/fill-in-blank-group/index.js +5 -0
  32. package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.d.ts +4 -0
  33. package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.js +102 -0
  34. package/dist/components/questions/types/fill-in-blank-group/register.d.ts +3 -0
  35. package/dist/components/questions/types/fill-in-blank-group/register.js +37 -0
  36. package/dist/components/questions/types/fill-in-blank-group/transform.d.ts +2 -0
  37. package/dist/components/questions/types/fill-in-blank-group/transform.js +68 -0
  38. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.d.ts +20 -0
  39. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +109 -0
  40. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.d.ts +2 -0
  41. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +30 -0
  42. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.d.ts +2 -0
  43. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +180 -0
  44. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +6 -0
  45. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +26 -0
  46. package/dist/components/questions/types/matching-with-lines-group/index.d.ts +4 -0
  47. package/dist/components/questions/types/matching-with-lines-group/index.js +4 -0
  48. package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.d.ts +8 -0
  49. package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +104 -0
  50. package/dist/components/questions/types/matching-with-lines-group/register.d.ts +3 -0
  51. package/dist/components/questions/types/matching-with-lines-group/register.js +37 -0
  52. package/dist/components/questions/types/matching-with-lines-group/transform.d.ts +2 -0
  53. package/dist/components/questions/types/matching-with-lines-group/transform.js +44 -0
  54. package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +10 -0
  55. package/dist/components/questions/viewer/FillInBlankGroupViewer.js +14 -0
  56. package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.d.ts +7 -0
  57. package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.js +48 -0
  58. package/dist/components/questions/viewer/QuestionViewer.js +6 -0
  59. package/dist/components/questions/viewer/index.d.ts +2 -0
  60. package/dist/components/questions/viewer/index.js +2 -0
  61. package/dist/components/results/renderers/review-question-body-movers.js +37 -2
  62. package/dist/components/results/review-data.js +37 -0
  63. package/dist/index.d.ts +1 -1
  64. package/dist/index.js +1 -1
  65. package/dist/shared/constants/question-skills.js +4 -0
  66. package/dist/shared/lib/utils/question-reverse-transform.js +86 -0
  67. package/dist/shared/lib/utils/question-transform.js +4 -0
  68. package/dist/shared/types/common.types.d.ts +1 -1
  69. package/dist/shared/types/questions/fill-in-blank-group.d.ts +45 -0
  70. package/dist/shared/types/questions/fill-in-blank-group.js +1 -0
  71. package/dist/shared/types/questions/index.d.ts +2 -0
  72. package/dist/shared/types/questions/index.js +4 -0
  73. package/dist/shared/types/questions/matching-with-lines-group.d.ts +39 -0
  74. package/dist/shared/types/questions/matching-with-lines-group.js +1 -0
  75. package/package.json +1 -1
@@ -0,0 +1,44 @@
1
+ import { MATCHING_WITH_LINES_GROUP_DEFAULT_INSTRUCTION } from '../../_shared/types/matching-with-lines-group.type';
2
+ function sumGradablePoints(items) {
3
+ return items.reduce((total, item) => {
4
+ if (item.isExample)
5
+ return total;
6
+ const points = typeof item.points === 'number' ? item.points : 1;
7
+ return total + points;
8
+ }, 0);
9
+ }
10
+ export const transformMatchingWithLinesGroup = (question) => {
11
+ const answerData = question.answer;
12
+ const items = Array.isArray(answerData?.items) ? answerData.items : [];
13
+ const rightItems = Array.isArray(answerData?.rightItems) ? answerData.rightItems : [];
14
+ if (items.length === 0) {
15
+ return { apiContent: { rightItems: [], items: [] }, apiCorrectAnswer: { answer: [] } };
16
+ }
17
+ const apiItems = items.map((item, index) => ({
18
+ ...(item.isExample ? { isExample: true } : {}),
19
+ content: {
20
+ imageUrl: item.imageUrl || '',
21
+ },
22
+ correctAnswer: { answer: item.correctAnswer || '' },
23
+ points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
24
+ questionNumber: item.questionNumber || index + 1,
25
+ }));
26
+ const title = typeof answerData?.title === 'string' ? answerData.title.trim() : '';
27
+ return {
28
+ apiContent: {
29
+ meta: {
30
+ instruction: answerData?.instruction || MATCHING_WITH_LINES_GROUP_DEFAULT_INSTRUCTION,
31
+ ...(title ? { title } : {}),
32
+ },
33
+ rightItems: rightItems.map((item) => ({
34
+ id: item.id || '',
35
+ text: item.text || '',
36
+ })),
37
+ items: apiItems,
38
+ },
39
+ apiCorrectAnswer: {
40
+ answer: apiItems.map((item) => item.correctAnswer.answer),
41
+ },
42
+ totalPoints: sumGradablePoints(items),
43
+ };
44
+ };
@@ -0,0 +1,10 @@
1
+ import type { Question } from '../../../shared/types/entities/question.types';
2
+ import type { FillInBlankGroupStudentItemAnswer } from '../_shared/types/fill-in-blank-group.type';
3
+ type FillInBlankGroupViewerProps = {
4
+ question: Question;
5
+ isReviewMode?: boolean;
6
+ userAnswer?: FillInBlankGroupStudentItemAnswer[];
7
+ onSubmitAnswer?: (answer: FillInBlankGroupStudentItemAnswer[]) => void;
8
+ };
9
+ export declare function FillInBlankGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: FillInBlankGroupViewerProps): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,14 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { FillInBlankGroupClient } from '../types/fill-in-blank-group/FillInBlankGroupClient';
4
+ import { mapQuestionToFillInBlankGroupData } from '../types/fill-in-blank-group/map-fill-in-blank-group-data';
5
+ export function FillInBlankGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
6
+ const questionData = mapQuestionToFillInBlankGroupData(question);
7
+ const studentAnswers = Array.isArray(userAnswer) ? userAnswer : undefined;
8
+ const reviewAnswers = isReviewMode && !studentAnswers
9
+ ? questionData.items.map((item) => ({
10
+ answers: item.answers.map((alts) => alts[0] || ''),
11
+ }))
12
+ : studentAnswers;
13
+ return (_jsx(FillInBlankGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
14
+ }
@@ -0,0 +1,7 @@
1
+ import type { Question } from '../../../shared/types/entities/question.types';
2
+ export declare function MatchingWithLinesGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
3
+ question: Question;
4
+ isReviewMode?: boolean;
5
+ userAnswer?: unknown;
6
+ onSubmitAnswer?: (answer: string[]) => void;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { MatchingWithLinesGroupClient } from '../types/matching-with-lines-group/MatchingWithLinesGroupClient';
5
+ import { mapQuestionToMatchingWithLinesGroupData } from '../types/matching-with-lines-group/map-matching-with-lines-group-data';
6
+ function isRecord(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+ function emptyAnswers(length) {
10
+ return Array.from({ length }, () => '');
11
+ }
12
+ function parseMatchingAnswers(value, length) {
13
+ const source = Array.isArray(value)
14
+ ? value
15
+ : isRecord(value) && Array.isArray(value.answer)
16
+ ? value.answer
17
+ : null;
18
+ if (!source)
19
+ return undefined;
20
+ return Array.from({ length }, (_, index) => typeof source[index] === 'string' ? source[index] : '');
21
+ }
22
+ export function MatchingWithLinesGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
23
+ const questionData = mapQuestionToMatchingWithLinesGroupData(question);
24
+ const itemCount = questionData.items.length;
25
+ const parsedUser = parseMatchingAnswers(userAnswer, itemCount);
26
+ const questionKey = `${question.id}:${question.type}:${itemCount}:${questionData.rightItems
27
+ .map((item) => `${item.id}:${item.text}`)
28
+ .join(',')}`;
29
+ const [cachedKey, setCachedKey] = useState(questionKey);
30
+ const [localAnswers, setLocalAnswers] = useState(() => parsedUser ?? emptyAnswers(itemCount));
31
+ if (cachedKey !== questionKey) {
32
+ setCachedKey(questionKey);
33
+ setLocalAnswers(parsedUser ?? emptyAnswers(itemCount));
34
+ }
35
+ const userAnswers = isReviewMode && !parsedUser
36
+ ? questionData.items.map((item) => item.correctAnswer)
37
+ : parsedUser ?? localAnswers;
38
+ const handleAnswersChange = (next) => {
39
+ const padded = questionData.items.map((item, index) => {
40
+ if (item.isExample)
41
+ return item.correctAnswer || '';
42
+ return typeof next[index] === 'string' ? next[index] : '';
43
+ });
44
+ setLocalAnswers(padded);
45
+ onSubmitAnswer?.(padded);
46
+ };
47
+ return (_jsx(MatchingWithLinesGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswersChange: isReviewMode ? undefined : handleAnswersChange }));
48
+ }
@@ -26,6 +26,8 @@ import { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
26
26
  import { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
27
27
  import { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
28
28
  import { TrueFalseCorrectGroupViewer } from './TrueFalseCorrectGroupViewer';
29
+ import { FillInBlankGroupViewer } from './FillInBlankGroupViewer';
30
+ import { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
29
31
  import { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
30
32
  import { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
31
33
  import { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
@@ -635,6 +637,10 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
635
637
  return (_jsx(TrueFalseGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
636
638
  case 'TRUE_FALSE_CORRECT_GROUP':
637
639
  return (_jsx(TrueFalseCorrectGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
640
+ case 'FILL_IN_BLANK_GROUP':
641
+ return (_jsx(FillInBlankGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
642
+ case 'MATCHING_WITH_LINES_GROUP':
643
+ return (_jsx(MatchingWithLinesGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
638
644
  case 'CROSS_OUT_WORD_GROUP':
639
645
  return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
640
646
  case 'ANSWER_THE_QUESTION_GROUP':
@@ -4,6 +4,8 @@ export { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
4
4
  export { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
5
5
  export { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
6
6
  export { TrueFalseCorrectGroupViewer } from './TrueFalseCorrectGroupViewer';
7
+ export { FillInBlankGroupViewer } from './FillInBlankGroupViewer';
8
+ export { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
7
9
  export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
8
10
  export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
9
11
  export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
@@ -4,6 +4,8 @@ export { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
4
4
  export { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
5
5
  export { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
6
6
  export { TrueFalseCorrectGroupViewer } from './TrueFalseCorrectGroupViewer';
7
+ export { FillInBlankGroupViewer } from './FillInBlankGroupViewer';
8
+ export { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
7
9
  export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
8
10
  export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
9
11
  export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
@@ -4,8 +4,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
4
4
  * Movers fallback review cases (isReviewMode=true).
5
5
  * Source: oe-exam-fe ReviewQuestionRenderer.tsx
6
6
  */
7
- import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
8
- import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } from '../../../components/exams/take/utils/question-transformers';
7
+ import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
8
+ import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } from '../../../components/exams/take/utils/question-transformers';
9
9
  import { normalizeCrossOutWordGroupAnswer, transformCrossOutWordGroup, } from '../../../components/exams/take/utils/cross-out-word-group.transformer';
10
10
  function asApiQuestions(questions) {
11
11
  return questions;
@@ -142,6 +142,41 @@ export function renderMoversReviewBody(ctx) {
142
142
  });
143
143
  return (_jsx(MoversTrueFalseCorrectGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerMap: correctAnswerMap }));
144
144
  }
145
+ case 'FILL_IN_BLANK_GROUP': {
146
+ const data = transformFillInBlankGroup(apiQuestions);
147
+ const correctAnswerMap = {};
148
+ const parent = apiQuestions[0];
149
+ const rawCorrect = parent?.id ? rawCorrectAnswerMap[parent.id] : undefined;
150
+ const rawCorrectRecord = rawCorrect && typeof rawCorrect === 'object' && !Array.isArray(rawCorrect)
151
+ ? rawCorrect
152
+ : undefined;
153
+ const correctArr = Array.isArray(rawCorrectRecord?.answer)
154
+ ? rawCorrectRecord.answer
155
+ : Array.isArray(parent?.correct_answer?.answer)
156
+ ? parent.correct_answer.answer
157
+ : [];
158
+ data.questions.forEach((question, index) => {
159
+ const rawItem = correctArr[index];
160
+ if (!rawItem || typeof rawItem !== 'object' || Array.isArray(rawItem)) {
161
+ return;
162
+ }
163
+ const obj = rawItem;
164
+ if (!Array.isArray(obj.answers)) {
165
+ return;
166
+ }
167
+ correctAnswerMap[question.id] = {
168
+ answers: obj.answers.map((blank) => (Array.isArray(blank)
169
+ ? blank.map((alt) => String(alt ?? '').trim()).filter(Boolean)
170
+ : [String(blank ?? '').trim()].filter(Boolean))),
171
+ caseSensitive: obj.caseSensitive === true,
172
+ };
173
+ });
174
+ return (_jsx(MoversFillInBlankGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: data.questions.filter((question) => !question.isExample).length, audioUrl: data.audioUrl, imageUrl: data.imageUrl, showHints: data.showHints, hints: data.hints, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerMap: correctAnswerMap }));
175
+ }
176
+ case 'MATCHING_WITH_LINES_GROUP': {
177
+ const data = transformMatchingWithLinesGroup(apiQuestions);
178
+ return (_jsx(MoversMatchingWithLinesGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, data: data, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true }));
179
+ }
145
180
  case 'TRUE_FALSE': {
146
181
  const data = transformTrueFalse(apiQuestions);
147
182
  return (_jsx(MoversTrueFalseRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, imageUrl: data.imageUrl, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerTextMap: correctAnswerTextMap }));
@@ -9,6 +9,8 @@ const FLATTENED_ANSWER_TYPES = new Set([
9
9
  'ANSWER_THE_QUESTION_GROUP',
10
10
  'CROSS_OUT_WORD_GROUP',
11
11
  'TRUE_FALSE_CORRECT_GROUP',
12
+ 'FILL_IN_BLANK_GROUP',
13
+ 'MATCHING_WITH_LINES_GROUP',
12
14
  ]);
13
15
  export function transformResultAnswersForReview(answers, templateParts = []) {
14
16
  const groups = groupAnswers(answers);
@@ -205,6 +207,41 @@ function transformStudentAnswer(questionId, questionType, value) {
205
207
  return [`${questionId}-item-${index}`, { isTrue: null, correction: '' }];
206
208
  }));
207
209
  }
210
+ if (questionType === 'FILL_IN_BLANK_GROUP') {
211
+ const answer = Array.isArray(record.answer)
212
+ ? record.answer
213
+ : Array.isArray(value)
214
+ ? value
215
+ : [];
216
+ return Object.fromEntries(answer.map((item, index) => {
217
+ if (item && typeof item === 'object' && !Array.isArray(item) && Array.isArray(item.answers)) {
218
+ return [
219
+ `${questionId}-item-${index}`,
220
+ {
221
+ answers: item.answers.map((value) => String(value ?? '')),
222
+ },
223
+ ];
224
+ }
225
+ if (Array.isArray(item)) {
226
+ return [`${questionId}-item-${index}`, { answers: item.map((value) => String(value ?? '')) }];
227
+ }
228
+ if (typeof item === 'string') {
229
+ return [`${questionId}-item-${index}`, { answers: [item] }];
230
+ }
231
+ return [`${questionId}-item-${index}`, { answers: [] }];
232
+ }));
233
+ }
234
+ if (questionType === 'MATCHING_WITH_LINES_GROUP') {
235
+ const answer = Array.isArray(record.answer)
236
+ ? record.answer
237
+ : Array.isArray(value)
238
+ ? value
239
+ : [];
240
+ return Object.fromEntries(answer.map((item, index) => [
241
+ `${questionId}-item-${index}`,
242
+ typeof item === 'string' ? item : '',
243
+ ]));
244
+ }
208
245
  if (questionType === 'CHOOSE_THE_CORRECT_ANSWER' || questionType === 'CHOOSE_CORRECT_ADJECTIVE') {
209
246
  const rawAns = record.answer ?? record.optionId ?? record.optionIds;
210
247
  const ids = getOptionIds(questionId, rawAns);
package/dist/index.d.ts CHANGED
@@ -26,4 +26,4 @@ export { validateQuestion } from './components/questions/_shared/utils/question-
26
26
  export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
27
27
  export type { QuestionFormState } from './components/questions/_shared/hooks/useQuestionFormState';
28
28
  export * from './components/questions/_shared/types';
29
- export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
29
+ export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
package/dist/index.js CHANGED
@@ -21,4 +21,4 @@ export { QUESTION_TYPE_CONFIG, QUESTION_TYPES_WITH_DEDICATED_COMPONENTS } from '
21
21
  export { validateQuestion } from './components/questions/_shared/utils/question-validation';
22
22
  export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
23
23
  export * from './components/questions/_shared/types';
24
- export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
24
+ export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
@@ -45,6 +45,7 @@ export const QUESTION_TYPE_SKILLS = {
45
45
  'CHOOSE_THE_CORRECT_ANSWER_GROUP': SkillEnum.READING,
46
46
  'CHOOSE_CORRECT_ADJECTIVE': SkillEnum.READING,
47
47
  'FILL_IN_BLANK': SkillEnum.READING,
48
+ 'FILL_IN_BLANK_GROUP': SkillEnum.READING,
48
49
  'FILL_MISSING_WORDS_IN_GRID': SkillEnum.READING,
49
50
  'READ_PASSAGE_AND_ANSWER_QUESTIONS': SkillEnum.READING,
50
51
  'ANSWER_THE_QUESTION': SkillEnum.READING,
@@ -53,6 +54,7 @@ export const QUESTION_TYPE_SKILLS = {
53
54
  'WORD_FILL_PARAGRAPH': SkillEnum.READING,
54
55
  'MATCH_BY_WRITING_ANSWER': SkillEnum.READING,
55
56
  'MATCH_WORD_TO_PICTURE': SkillEnum.READING,
57
+ 'MATCHING_WITH_LINES_GROUP': SkillEnum.READING,
56
58
  // Visual-based reading questions
57
59
  'LABEL_THE_PICTURE': SkillEnum.READING,
58
60
  'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER': SkillEnum.READING,
@@ -177,6 +179,7 @@ export const QUESTION_TYPE_LABELS = {
177
179
  'CHOOSE_THE_CORRECT_ANSWER_GROUP': 'Chọn đáp án đúng (nhóm)',
178
180
  'CHOOSE_CORRECT_ADJECTIVE': 'Chọn tính từ',
179
181
  'FILL_IN_BLANK': 'Điền vào chỗ trống',
182
+ 'FILL_IN_BLANK_GROUP': 'Điền chỗ trống (nhóm)',
180
183
  'FILL_MISSING_WORDS_IN_GRID': 'Điền từ vào lưới',
181
184
  'READ_PASSAGE_AND_ANSWER_QUESTIONS': 'Đọc hiểu - Trả lời',
182
185
  'LABEL_THE_PICTURE': 'Gắn nhãn hình ảnh',
@@ -209,6 +212,7 @@ export const QUESTION_TYPE_LABELS = {
209
212
  'WORD_FILL_PARAGRAPH': 'Điền từ vào đoạn văn',
210
213
  'MATCH_BY_WRITING_ANSWER': 'Ghép đáp án bằng cách viết',
211
214
  'MATCH_WORD_TO_PICTURE': 'Nối từ với tranh',
215
+ 'MATCHING_WITH_LINES_GROUP': 'Nối bằng đường kẻ (nhóm)',
212
216
  'WORD_FILL_STRUCTURED_FORM': 'Điền từ vào biểu mẫu có cấu trúc',
213
217
  'CROSSWORD_PUZZLE': 'Giải ô chữ (Crossword)',
214
218
  'FIND_WORDS_IN_MATRIX': 'Tìm từ trong ma trận (Word Search)',
@@ -308,6 +308,90 @@ const reverseTrueFalseCorrectGroup = (apiQuestion) => {
308
308
  },
309
309
  };
310
310
  };
311
+ const reverseFillInBlankGroup = (apiQuestion) => {
312
+ const content = asRecord(apiQuestion.content);
313
+ const answer = readAnswer(apiQuestion);
314
+ const fallbackAnswers = asArray(answer.answer);
315
+ const meta = asRecord(content.meta);
316
+ const items = asArray(content.items).map((rawItem, index) => {
317
+ const item = asRecord(rawItem);
318
+ const itemContent = asRecord(item.content);
319
+ const itemAnswer = asRecord(item.correctAnswer);
320
+ const fallback = asRecord(fallbackAnswers[index]);
321
+ const isExample = item.isExample === true;
322
+ const question = asString(itemContent.question, '{0}');
323
+ const rawAnswers = itemAnswer.answers ?? fallback.answers;
324
+ const answers = asArray(rawAnswers).map((blank) => {
325
+ const alts = Array.isArray(blank)
326
+ ? blank.map((alt) => String(alt ?? '').trim()).filter(Boolean)
327
+ : [String(blank ?? '').trim()].filter(Boolean);
328
+ return alts.length > 0 ? alts : [''];
329
+ });
330
+ return {
331
+ question,
332
+ answers: answers.length > 0 ? answers : [['']],
333
+ caseSensitive: itemAnswer.caseSensitive === true || fallback.caseSensitive === true,
334
+ imageUrl: asString(itemContent.imageUrl),
335
+ audioUrl: asString(itemContent.audioUrl),
336
+ showHints: itemContent.showHints === true,
337
+ hints: asArray(itemContent.hints).map((hint) => String(hint ?? '').trim()).filter(Boolean),
338
+ isExample,
339
+ points: isExample ? 0 : asNumber(item.points, 1),
340
+ questionNumber: asNumber(item.questionNumber, index + 1),
341
+ };
342
+ });
343
+ return {
344
+ content: '',
345
+ answer: {
346
+ instruction: asString(meta.instruction, 'Complete the sentences.'),
347
+ audioUrl: asString(meta.audioUrl),
348
+ imageUrl: asString(meta.imageUrl),
349
+ showHints: meta.showHints === true,
350
+ hints: asArray(meta.hints).map((hint) => String(hint ?? '').trim()).filter(Boolean),
351
+ items,
352
+ explanation: asString(apiQuestion.explanation),
353
+ points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
354
+ },
355
+ };
356
+ };
357
+ const reverseMatchingWithLinesGroup = (apiQuestion) => {
358
+ const content = asRecord(apiQuestion.content);
359
+ const answer = readAnswer(apiQuestion);
360
+ const fallbackAnswers = asArray(answer.answer);
361
+ const meta = asRecord(content.meta);
362
+ const rightItems = asArray(content.rightItems).map((raw, index) => {
363
+ const item = asRecord(raw);
364
+ return {
365
+ id: asString(item.id, `r${index + 1}`),
366
+ text: asString(item.text),
367
+ };
368
+ });
369
+ const items = asArray(content.items).map((rawItem, index) => {
370
+ const item = asRecord(rawItem);
371
+ const itemContent = asRecord(item.content);
372
+ const itemAnswer = asRecord(item.correctAnswer);
373
+ const fallback = fallbackAnswers[index];
374
+ const isExample = item.isExample === true;
375
+ return {
376
+ imageUrl: asString(itemContent.imageUrl),
377
+ correctAnswer: asString(itemAnswer.answer, typeof fallback === 'string' ? fallback : ''),
378
+ isExample,
379
+ points: isExample ? 0 : asNumber(item.points, 1),
380
+ questionNumber: asNumber(item.questionNumber, index + 1),
381
+ };
382
+ });
383
+ return {
384
+ content: '',
385
+ answer: {
386
+ title: asString(meta.title),
387
+ instruction: asString(meta.instruction, 'Look and match.'),
388
+ rightItems,
389
+ items,
390
+ explanation: asString(apiQuestion.explanation),
391
+ points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
392
+ },
393
+ };
394
+ };
311
395
  const reverseCrossOutWordGroup = (apiQuestion) => {
312
396
  const content = asRecord(apiQuestion.content);
313
397
  const answer = readAnswer(apiQuestion);
@@ -460,6 +544,8 @@ const handlers = {
460
544
  ANSWER_THE_QUESTION_GROUP: reverseAnswerTheQuestionGroup,
461
545
  TRUE_FALSE_GROUP: reverseTrueFalseGroup,
462
546
  TRUE_FALSE_CORRECT_GROUP: reverseTrueFalseCorrectGroup,
547
+ FILL_IN_BLANK_GROUP: reverseFillInBlankGroup,
548
+ MATCHING_WITH_LINES_GROUP: reverseMatchingWithLinesGroup,
463
549
  CROSS_OUT_WORD_GROUP: reverseCrossOutWordGroup,
464
550
  WORD_ORDER_AND_MATCH_GROUP: reverseWordOrderAndMatchGroup,
465
551
  WORD_ORDER_AND_MATCH: reverseWordOrderAndMatchGroup,
@@ -14,6 +14,7 @@ import { transformChooseTheCorrectAnswer } from '../../../components/questions/t
14
14
  import { transformChooseTheCorrectAnswerGroup } from '../../../components/questions/types/choose-the-correct-answer-group/transform';
15
15
  import { transformCrossOutWordGroup } from '../../../components/questions/types/cross-out-word-group/transform';
16
16
  import { transformTrueFalseCorrectGroup } from '../../../components/questions/types/true-false-correct-group/transform';
17
+ import { transformFillInBlankGroup } from '../../../components/questions/types/fill-in-blank-group/transform';
17
18
  import { transformFillInBlank } from '../../../components/questions/types/fill-in-blank/transform';
18
19
  import { transformWriteCorrectVerbForm } from '../../../components/questions/types/write-correct-verb-form/transform';
19
20
  import { transformChooseCorrectAdjective } from '../../../components/questions/types/choose-correct-adjective/transform';
@@ -24,6 +25,7 @@ import { transformWordOrdering } from '../../../components/questions/types/word-
24
25
  import { transformWordFillParagraph } from '../../../components/questions/types/word-fill-paragraph/transform';
25
26
  import { transformMatchByWritingAnswer } from '../../../components/questions/types/match-by-writing-answer/transform';
26
27
  import { transformMatchWordToPicture } from '../../../components/questions/types/match-word-to-picture/transform';
28
+ import { transformMatchingWithLinesGroup } from '../../../components/questions/types/matching-with-lines-group/transform';
27
29
  import { transformWriteSentences } from '../../../components/questions/types/write-sentences/transform';
28
30
  import { transformListenAndChooseFromAnswerGroup } from '../../../components/questions/types/listen-and-choose-from-answer-group/transform';
29
31
  import { transformListenAndChooseObjectsInScene } from '../../../components/questions/types/listen-and-choose-objects-in-scene/transform';
@@ -65,6 +67,7 @@ const questionTransformHandlers = {
65
67
  CHOOSE_THE_CORRECT_ANSWER_GROUP: transformChooseTheCorrectAnswerGroup,
66
68
  CROSS_OUT_WORD_GROUP: transformCrossOutWordGroup,
67
69
  TRUE_FALSE_CORRECT_GROUP: transformTrueFalseCorrectGroup,
70
+ FILL_IN_BLANK_GROUP: transformFillInBlankGroup,
68
71
  FILL_IN_BLANK: transformFillInBlank,
69
72
  WRITE_CORRECT_VERB_FORM: transformWriteCorrectVerbForm,
70
73
  CHOOSE_CORRECT_ADJECTIVE: transformChooseCorrectAdjective,
@@ -75,6 +78,7 @@ const questionTransformHandlers = {
75
78
  WORD_FILL_PARAGRAPH: transformWordFillParagraph,
76
79
  MATCH_BY_WRITING_ANSWER: transformMatchByWritingAnswer,
77
80
  MATCH_WORD_TO_PICTURE: transformMatchWordToPicture,
81
+ MATCHING_WITH_LINES_GROUP: transformMatchingWithLinesGroup,
78
82
  WRITE_SENTENCES: transformWriteSentences,
79
83
  LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP: transformListenAndChooseFromAnswerGroup,
80
84
  LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE: transformListenAndChooseObjectsInScene,
@@ -29,7 +29,7 @@ export declare enum StudentSelectionType {
29
29
  }
30
30
  export type ResultStatus = 'PENDING' | 'PARTIAL_GRADED' | 'GRADED';
31
31
  export type StudentStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'SUBMITTED';
32
- export type QuestionType = 'FILL_IN_BLANK' | 'TRUE_FALSE' | 'MATCHING' | 'ORDERING' | 'LISTENING' | 'COLORING' | 'ESSAY' | 'LISTEN_AND_FILL_IN_THE_BLANK_WITH_IMAGE' | 'LISTEN_AND_TICK_ANSWER' | 'LISTEN_AND_FILL_NAME_NUMBER' | 'LISTENING_FILL_BLANK' | 'LISTENING_COLOR' | 'TICK_TRUE_OR_FALSE' | 'TICK_YES_OR_NO' | 'READING_TICK_CROSS' | 'ARRANGE_LETTERS_INTO_WORDS' | 'READ_PASSAGE_AND_COMPLETE_STORY' | 'CLOZE_TEST_WITH_TICK_BOX' | 'READ_AND_CHOOSE_APPROPRIATE_WORD' | 'WRITE_SHORT_PARAGRAPH' | 'LABEL_THE_PICTURE' | 'READING_LETTER_ARRANGE' | 'READING_PASSAGE_FILL' | 'FILL_BLANK' | 'FILL_MISSING_WORDS_IN_GRID' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'LABEL_THE_PICTURE' | 'READ_AND_COLOR_OBJECTS' | 'IMAGE_OBJECT_MATCHING' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'SPONTANEOUS_QA_GROUP' | 'WORD_ORDER_AND_MATCH_GROUP' | 'WRITE_A_SHORT_LETTER' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_CORRECT_ADJECTIVE' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX';
32
+ export type QuestionType = 'FILL_IN_BLANK' | 'TRUE_FALSE' | 'MATCHING' | 'ORDERING' | 'LISTENING' | 'COLORING' | 'ESSAY' | 'LISTEN_AND_FILL_IN_THE_BLANK_WITH_IMAGE' | 'LISTEN_AND_TICK_ANSWER' | 'LISTEN_AND_FILL_NAME_NUMBER' | 'LISTENING_FILL_BLANK' | 'LISTENING_COLOR' | 'TICK_TRUE_OR_FALSE' | 'TICK_YES_OR_NO' | 'READING_TICK_CROSS' | 'ARRANGE_LETTERS_INTO_WORDS' | 'READ_PASSAGE_AND_COMPLETE_STORY' | 'CLOZE_TEST_WITH_TICK_BOX' | 'READ_AND_CHOOSE_APPROPRIATE_WORD' | 'WRITE_SHORT_PARAGRAPH' | 'LABEL_THE_PICTURE' | 'READING_LETTER_ARRANGE' | 'READING_PASSAGE_FILL' | 'FILL_BLANK' | 'FILL_MISSING_WORDS_IN_GRID' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'LABEL_THE_PICTURE' | 'READ_AND_COLOR_OBJECTS' | 'IMAGE_OBJECT_MATCHING' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'SPONTANEOUS_QA_GROUP' | 'WORD_ORDER_AND_MATCH_GROUP' | 'WRITE_A_SHORT_LETTER' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_CORRECT_ADJECTIVE' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX';
33
33
  export type Level = 'Pre-A1' | 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2';
34
34
  export type Difficulty = 'EASY' | 'MEDIUM' | 'HARD';
35
35
  export type Skill = 'LISTENING' | 'READING' | 'WRITING' | 'SPEAKING';
@@ -0,0 +1,45 @@
1
+ export declare const FILL_IN_BLANK_ITEM_TYPE: "FILL_IN_BLANK";
2
+ export interface FillInBlankGroupItemAnswer {
3
+ answers: string[][];
4
+ caseSensitive?: boolean;
5
+ }
6
+ export interface FillInBlankGroupItemContent {
7
+ question: string;
8
+ imageUrl?: string;
9
+ audioUrl?: string;
10
+ showHints?: boolean;
11
+ hints?: string[];
12
+ }
13
+ export interface FillInBlankGroupItem {
14
+ questionType?: typeof FILL_IN_BLANK_ITEM_TYPE;
15
+ isExample?: boolean;
16
+ content: FillInBlankGroupItemContent;
17
+ correctAnswer?: FillInBlankGroupItemAnswer;
18
+ points?: number;
19
+ questionNumber?: number;
20
+ }
21
+ export interface FillInBlankGroupContent {
22
+ meta?: {
23
+ instruction?: string;
24
+ audioUrl?: string;
25
+ imageUrl?: string;
26
+ showHints?: boolean;
27
+ hints?: string[];
28
+ };
29
+ items: FillInBlankGroupItem[];
30
+ }
31
+ export interface FillInBlankGroupCorrectAnswer {
32
+ answer: FillInBlankGroupItemAnswer[];
33
+ }
34
+ export interface FillInBlankGroupAPIPayload {
35
+ id?: string;
36
+ questionType: 'FILL_IN_BLANK_GROUP';
37
+ content: FillInBlankGroupContent;
38
+ correctAnswer: FillInBlankGroupCorrectAnswer;
39
+ points: number;
40
+ explanation?: string;
41
+ questionNumber: number;
42
+ partId?: string;
43
+ partNo?: number;
44
+ }
45
+ export type FillInBlankGroupLocalStorage = FillInBlankGroupAPIPayload;
@@ -0,0 +1 @@
1
+ export const FILL_IN_BLANK_ITEM_TYPE = 'FILL_IN_BLANK';
@@ -33,4 +33,6 @@ export * from './word-order-and-match-group';
33
33
  export * from './crossword-puzzle';
34
34
  export * from './find-words-in-matrix';
35
35
  export * from './match-word-to-picture';
36
+ export * from './matching-with-lines-group';
36
37
  export * from './true-false-correct-group';
38
+ export * from './fill-in-blank-group';
@@ -52,5 +52,9 @@ export * from './crossword-puzzle';
52
52
  export * from './find-words-in-matrix';
53
53
  // MATCH_WORD_TO_PICTURE
54
54
  export * from './match-word-to-picture';
55
+ // MATCHING_WITH_LINES_GROUP
56
+ export * from './matching-with-lines-group';
55
57
  // TRUE_FALSE_CORRECT_GROUP
56
58
  export * from './true-false-correct-group';
59
+ // FILL_IN_BLANK_GROUP
60
+ export * from './fill-in-blank-group';
@@ -0,0 +1,39 @@
1
+ export interface MatchingWithLinesRightItem {
2
+ id: string;
3
+ text: string;
4
+ }
5
+ export interface MatchingWithLinesGroupItemContent {
6
+ imageUrl: string;
7
+ }
8
+ export interface MatchingWithLinesGroupItem {
9
+ isExample?: boolean;
10
+ content: MatchingWithLinesGroupItemContent;
11
+ correctAnswer?: {
12
+ answer: string;
13
+ };
14
+ points?: number;
15
+ questionNumber?: number;
16
+ }
17
+ export interface MatchingWithLinesGroupContent {
18
+ meta?: {
19
+ instruction?: string;
20
+ title?: string;
21
+ };
22
+ rightItems: MatchingWithLinesRightItem[];
23
+ items: MatchingWithLinesGroupItem[];
24
+ }
25
+ export interface MatchingWithLinesGroupCorrectAnswer {
26
+ answer: string[];
27
+ }
28
+ export interface MatchingWithLinesGroupAPIPayload {
29
+ id?: string;
30
+ questionType: 'MATCHING_WITH_LINES_GROUP';
31
+ content: MatchingWithLinesGroupContent;
32
+ correctAnswer: MatchingWithLinesGroupCorrectAnswer;
33
+ points: number;
34
+ explanation?: string;
35
+ questionNumber: number;
36
+ partId?: string;
37
+ partNo?: number;
38
+ }
39
+ export type MatchingWithLinesGroupLocalStorage = MatchingWithLinesGroupAPIPayload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinyweb_dev/oe-exam-sdk",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Reusable OceanEdu question components.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",