@tinyweb_dev/oe-exam-sdk 0.2.5 → 0.2.7

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 (91) 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/MoversListenAndWriteRenderer.d.ts +2 -2
  6. package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.js +58 -14
  7. package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.d.ts +13 -0
  8. package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +31 -0
  9. package/dist/components/exams/take/components/question-renderers/index.d.ts +2 -0
  10. package/dist/components/exams/take/components/question-renderers/index.js +2 -0
  11. package/dist/components/exams/take/types.d.ts +12 -5
  12. package/dist/components/exams/take/utils/answer-transformers.js +17 -1
  13. package/dist/components/exams/take/utils/question-transformers.d.ts +45 -1
  14. package/dist/components/exams/take/utils/question-transformers.js +109 -53
  15. package/dist/components/questions/_shared/config/question-types.config.js +12 -0
  16. package/dist/components/questions/_shared/types/fill-in-blank-group.type.d.ts +50 -0
  17. package/dist/components/questions/_shared/types/fill-in-blank-group.type.js +2 -0
  18. package/dist/components/questions/_shared/types/fill-in-blank.type.d.ts +16 -1
  19. package/dist/components/questions/_shared/types/index.d.ts +2 -0
  20. package/dist/components/questions/_shared/types/index.js +2 -0
  21. package/dist/components/questions/_shared/types/matching-with-lines-group.type.d.ts +40 -0
  22. package/dist/components/questions/_shared/types/matching-with-lines-group.type.js +1 -0
  23. package/dist/components/questions/components/QuestionSearchDropdown.js +4 -0
  24. package/dist/components/questions/components/QuestionTypeSelector.js +2 -0
  25. package/dist/components/questions/creator/question-type-registry.js +4 -0
  26. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.d.ts +1 -1
  27. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +37 -60
  28. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +122 -149
  29. package/dist/components/questions/types/fill-in-blank/answer-utils.d.ts +34 -0
  30. package/dist/components/questions/types/fill-in-blank/answer-utils.js +58 -0
  31. package/dist/components/questions/types/fill-in-blank/register.js +19 -6
  32. package/dist/components/questions/types/fill-in-blank/transform.js +23 -26
  33. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.d.ts +2 -0
  34. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +64 -0
  35. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.d.ts +2 -0
  36. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +158 -0
  37. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.d.ts +7 -0
  38. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +30 -0
  39. package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +20 -0
  40. package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +99 -0
  41. package/dist/components/questions/types/fill-in-blank-group/index.d.ts +5 -0
  42. package/dist/components/questions/types/fill-in-blank-group/index.js +5 -0
  43. package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.d.ts +4 -0
  44. package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.js +102 -0
  45. package/dist/components/questions/types/fill-in-blank-group/register.d.ts +3 -0
  46. package/dist/components/questions/types/fill-in-blank-group/register.js +37 -0
  47. package/dist/components/questions/types/fill-in-blank-group/transform.d.ts +2 -0
  48. package/dist/components/questions/types/fill-in-blank-group/transform.js +68 -0
  49. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.d.ts +20 -0
  50. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +109 -0
  51. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.d.ts +2 -0
  52. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +30 -0
  53. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.d.ts +2 -0
  54. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +180 -0
  55. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +6 -0
  56. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +26 -0
  57. package/dist/components/questions/types/matching-with-lines-group/index.d.ts +4 -0
  58. package/dist/components/questions/types/matching-with-lines-group/index.js +4 -0
  59. package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.d.ts +8 -0
  60. package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +104 -0
  61. package/dist/components/questions/types/matching-with-lines-group/register.d.ts +3 -0
  62. package/dist/components/questions/types/matching-with-lines-group/register.js +37 -0
  63. package/dist/components/questions/types/matching-with-lines-group/transform.d.ts +2 -0
  64. package/dist/components/questions/types/matching-with-lines-group/transform.js +44 -0
  65. package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +10 -0
  66. package/dist/components/questions/viewer/FillInBlankGroupViewer.js +14 -0
  67. package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.d.ts +7 -0
  68. package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.js +48 -0
  69. package/dist/components/questions/viewer/QuestionViewer.js +6 -0
  70. package/dist/components/questions/viewer/index.d.ts +2 -0
  71. package/dist/components/questions/viewer/index.js +2 -0
  72. package/dist/components/results/renderers/ReviewListenAndWriteRenderer.d.ts +2 -3
  73. package/dist/components/results/renderers/ReviewListenAndWriteRenderer.js +17 -8
  74. package/dist/components/results/renderers/review-question-body-dedicated.js +2 -2
  75. package/dist/components/results/renderers/review-question-body-movers.js +37 -2
  76. package/dist/components/results/review-data.js +37 -0
  77. package/dist/index.d.ts +1 -1
  78. package/dist/index.js +1 -1
  79. package/dist/shared/constants/question-skills.js +4 -0
  80. package/dist/shared/lib/utils/fill-in-blank.d.ts +46 -0
  81. package/dist/shared/lib/utils/fill-in-blank.js +96 -0
  82. package/dist/shared/lib/utils/question-reverse-transform.js +150 -3
  83. package/dist/shared/lib/utils/question-transform.js +4 -0
  84. package/dist/shared/types/common.types.d.ts +1 -1
  85. package/dist/shared/types/questions/fill-in-blank-group.d.ts +45 -0
  86. package/dist/shared/types/questions/fill-in-blank-group.js +1 -0
  87. package/dist/shared/types/questions/index.d.ts +2 -0
  88. package/dist/shared/types/questions/index.js +4 -0
  89. package/dist/shared/types/questions/matching-with-lines-group.d.ts +39 -0
  90. package/dist/shared/types/questions/matching-with-lines-group.js +1 -0
  91. package/package.json +1 -1
@@ -0,0 +1,96 @@
1
+ /**
2
+ * FILL_IN_BLANK — single source of truth for the whole flow.
3
+ *
4
+ * Wire format (never change one side without the other):
5
+ * content.question "A dog {0} swim, but it {1} fly."
6
+ * correct_answer.answers string[][] — answers[i] = accepted equivalents for blank {i}
7
+ * student answer { answers: string[] } — one entry per blank, index-aligned
8
+ *
9
+ * Matching rule (mirrors the API `normalizeText`): trim, lowercase, collapse inner
10
+ * whitespace, keep diacritics. A blank is correct when the input equals ANY equivalent.
11
+ */
12
+ const PLACEHOLDER_PATTERN = /\{(\d+)\}/g;
13
+ /** Split a stem into the text / blank segments used by every renderer. */
14
+ export function parseFillBlankSegments(text) {
15
+ const segments = [];
16
+ const regex = new RegExp(PLACEHOLDER_PATTERN);
17
+ let lastIndex = 0;
18
+ let match;
19
+ while ((match = regex.exec(text)) !== null) {
20
+ if (match.index > lastIndex) {
21
+ segments.push({ type: 'text', value: text.slice(lastIndex, match.index) });
22
+ }
23
+ segments.push({ type: 'blank', index: Number.parseInt(match[1], 10) });
24
+ lastIndex = regex.lastIndex;
25
+ }
26
+ if (lastIndex < text.length) {
27
+ segments.push({ type: 'text', value: text.slice(lastIndex) });
28
+ }
29
+ return segments;
30
+ }
31
+ /**
32
+ * Renderer-facing parse: like `parseFillBlankSegments`, but guarantees at least
33
+ * one blank so a stem authored without `{0}` still gets an input box.
34
+ */
35
+ export function parseFillBlankStem(text) {
36
+ const segments = parseFillBlankSegments(text);
37
+ if (!segments.some((segment) => segment.type === 'blank')) {
38
+ segments.push({ type: 'blank', index: 0 });
39
+ }
40
+ return segments;
41
+ }
42
+ /** Number of input boxes a stem needs (highest placeholder index + 1). */
43
+ export function countBlanks(segments) {
44
+ const highest = segments.reduce((max, segment) => (segment.type === 'blank' ? Math.max(max, segment.index) : max), -1);
45
+ return highest + 1 || 1;
46
+ }
47
+ /** Placeholder indices present in a stem, ascending and de-duplicated. */
48
+ export function extractPlaceholderIndices(text) {
49
+ const indices = new Set();
50
+ for (const segment of parseFillBlankSegments(text || '')) {
51
+ if (segment.type === 'blank')
52
+ indices.add(segment.index);
53
+ }
54
+ return [...indices].sort((a, b) => a - b);
55
+ }
56
+ /** Normalize one blank's stored value into a list of accepted equivalents. */
57
+ export function toAcceptedAnswers(value) {
58
+ const list = Array.isArray(value) ? value : [value];
59
+ return list.map((item) => String(item ?? '').trim()).filter(Boolean);
60
+ }
61
+ /**
62
+ * Normalize `correct_answer.answers` into `string[][]` padded to `blankCount`.
63
+ * Accepts the canonical `string[][]` and the legacy flat `string[]`.
64
+ */
65
+ export function normalizeAcceptedAnswers(raw, blankCount) {
66
+ const source = Array.isArray(raw) ? raw : [raw];
67
+ return Array.from({ length: blankCount }, (_, index) => toAcceptedAnswers(source[index]));
68
+ }
69
+ /** Normalize a stored student answer into one string per blank. */
70
+ export function toBlankValues(raw, blankCount) {
71
+ const source = Array.isArray(raw) ? raw : [raw];
72
+ return Array.from({ length: blankCount }, (_, index) => String(source[index] ?? ''));
73
+ }
74
+ /** True when the student typed something into at least one blank. */
75
+ export function hasAnyBlankValue(raw) {
76
+ const source = Array.isArray(raw) ? raw : [raw];
77
+ return source.some((value) => String(value ?? '').trim().length > 0);
78
+ }
79
+ function normalizeForCompare(value) {
80
+ return value.trim().toLowerCase().replace(/\s+/g, ' ');
81
+ }
82
+ /** Compare a student input against the accepted equivalents of one blank. */
83
+ export function isBlankCorrect(value, accepted) {
84
+ const input = normalizeForCompare(value ?? '');
85
+ if (!input || !accepted?.length)
86
+ return false;
87
+ return accepted.some((answer) => normalizeForCompare(answer) === input);
88
+ }
89
+ /** Join equivalents for display, e.g. "is / 's". */
90
+ export function formatAcceptedAnswers(accepted) {
91
+ return (accepted ?? []).map((answer) => answer.trim()).filter(Boolean).join(' / ');
92
+ }
93
+ /** First equivalent — used to prefill example questions and review hints. */
94
+ export function getPrimaryAnswer(accepted) {
95
+ return (accepted ?? []).map((answer) => answer.trim()).find(Boolean) || '';
96
+ }
@@ -66,19 +66,80 @@ const reversePictureChoice = (apiQuestion) => {
66
66
  const reverseFillInBlank = (apiQuestion) => {
67
67
  const content = asRecord(apiQuestion.content);
68
68
  const answer = readAnswer(apiQuestion);
69
- const firstBlank = asRecord(asArray(content.blanks)[0]);
69
+ const toBlankAlts = (value) => {
70
+ if (Array.isArray(value)) {
71
+ const cleaned = value
72
+ .map((item) => (item == null ? '' : String(item).trim()))
73
+ .filter(Boolean);
74
+ return cleaned.length > 0 ? cleaned : [''];
75
+ }
76
+ if (value == null)
77
+ return [''];
78
+ const text = String(value).trim();
79
+ return text ? [text] : [''];
80
+ };
81
+ const toAnswersMatrix = (raw, blankCount) => {
82
+ let result = [];
83
+ if (Array.isArray(raw)) {
84
+ result = raw.map((item) => toBlankAlts(item));
85
+ }
86
+ if (typeof blankCount === 'number' && blankCount > 0) {
87
+ while (result.length < blankCount) {
88
+ result.push(['']);
89
+ }
90
+ }
91
+ return result;
92
+ };
93
+ if (content.question && Array.isArray(answer.answers)) {
94
+ return {
95
+ content: '',
96
+ answer: {
97
+ question: asString(content.question),
98
+ answers: toAnswersMatrix(answer.answers),
99
+ caseSensitive: Boolean(answer.caseSensitive ?? false),
100
+ imageUrl: asString(content.imageUrl),
101
+ audioUrl: asString(content.audioUrl),
102
+ showHints: Boolean(content.showHints ?? false),
103
+ hints: asArray(content.hints),
104
+ explanation: asString(apiQuestion.explanation),
105
+ points: readPoints(apiQuestion),
106
+ },
107
+ };
108
+ }
109
+ const blanks = asArray(content.blanks);
110
+ const firstBlank = asRecord(blanks[0]);
111
+ const answersData = answer.answers ?? {};
112
+ let answersMatrix = [];
113
+ if (Array.isArray(answersData)) {
114
+ answersMatrix = toAnswersMatrix(answersData, blanks.length || undefined);
115
+ }
116
+ else if (typeof answersData === 'object' && answersData !== null) {
117
+ answersMatrix = blanks.map((blank) => toBlankAlts(asRecord(answersData)[asString(asRecord(blank).id)]));
118
+ }
70
119
  return {
71
120
  content: '',
72
121
  answer: {
73
122
  question: asString(content.question ?? firstBlank.text),
74
- answers: Array.isArray(answer.answers) ? answer.answers : [],
123
+ answers: answersMatrix,
75
124
  caseSensitive: Boolean(answer.caseSensitive ?? false),
76
125
  imageUrl: asString(content.imageUrl),
126
+ audioUrl: asString(content.audioUrl),
77
127
  showHints: Boolean(content.showHints ?? false),
78
128
  hints: asArray(content.hints),
79
129
  explanation: asString(apiQuestion.explanation),
80
130
  points: readPoints(apiQuestion),
81
- blanks: asArray(content.blanks),
131
+ blanks: blanks.map((rawBlank, index) => {
132
+ const blank = asRecord(rawBlank);
133
+ const alts = typeof answersData === 'object' && !Array.isArray(answersData)
134
+ ? toBlankAlts(asRecord(answersData)[asString(blank.id)])
135
+ : answersMatrix[index] || [''];
136
+ return {
137
+ id: blank.id,
138
+ text: blank.text,
139
+ correctAnswer: alts[0] || '',
140
+ correctAnswers: alts,
141
+ };
142
+ }),
82
143
  },
83
144
  };
84
145
  };
@@ -308,6 +369,90 @@ const reverseTrueFalseCorrectGroup = (apiQuestion) => {
308
369
  },
309
370
  };
310
371
  };
372
+ const reverseFillInBlankGroup = (apiQuestion) => {
373
+ const content = asRecord(apiQuestion.content);
374
+ const answer = readAnswer(apiQuestion);
375
+ const fallbackAnswers = asArray(answer.answer);
376
+ const meta = asRecord(content.meta);
377
+ const items = asArray(content.items).map((rawItem, index) => {
378
+ const item = asRecord(rawItem);
379
+ const itemContent = asRecord(item.content);
380
+ const itemAnswer = asRecord(item.correctAnswer);
381
+ const fallback = asRecord(fallbackAnswers[index]);
382
+ const isExample = item.isExample === true;
383
+ const question = asString(itemContent.question, '{0}');
384
+ const rawAnswers = itemAnswer.answers ?? fallback.answers;
385
+ const answers = asArray(rawAnswers).map((blank) => {
386
+ const alts = Array.isArray(blank)
387
+ ? blank.map((alt) => String(alt ?? '').trim()).filter(Boolean)
388
+ : [String(blank ?? '').trim()].filter(Boolean);
389
+ return alts.length > 0 ? alts : [''];
390
+ });
391
+ return {
392
+ question,
393
+ answers: answers.length > 0 ? answers : [['']],
394
+ caseSensitive: itemAnswer.caseSensitive === true || fallback.caseSensitive === true,
395
+ imageUrl: asString(itemContent.imageUrl),
396
+ audioUrl: asString(itemContent.audioUrl),
397
+ showHints: itemContent.showHints === true,
398
+ hints: asArray(itemContent.hints).map((hint) => String(hint ?? '').trim()).filter(Boolean),
399
+ isExample,
400
+ points: isExample ? 0 : asNumber(item.points, 1),
401
+ questionNumber: asNumber(item.questionNumber, index + 1),
402
+ };
403
+ });
404
+ return {
405
+ content: '',
406
+ answer: {
407
+ instruction: asString(meta.instruction, 'Complete the sentences.'),
408
+ audioUrl: asString(meta.audioUrl),
409
+ imageUrl: asString(meta.imageUrl),
410
+ showHints: meta.showHints === true,
411
+ hints: asArray(meta.hints).map((hint) => String(hint ?? '').trim()).filter(Boolean),
412
+ items,
413
+ explanation: asString(apiQuestion.explanation),
414
+ points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
415
+ },
416
+ };
417
+ };
418
+ const reverseMatchingWithLinesGroup = (apiQuestion) => {
419
+ const content = asRecord(apiQuestion.content);
420
+ const answer = readAnswer(apiQuestion);
421
+ const fallbackAnswers = asArray(answer.answer);
422
+ const meta = asRecord(content.meta);
423
+ const rightItems = asArray(content.rightItems).map((raw, index) => {
424
+ const item = asRecord(raw);
425
+ return {
426
+ id: asString(item.id, `r${index + 1}`),
427
+ text: asString(item.text),
428
+ };
429
+ });
430
+ const items = asArray(content.items).map((rawItem, index) => {
431
+ const item = asRecord(rawItem);
432
+ const itemContent = asRecord(item.content);
433
+ const itemAnswer = asRecord(item.correctAnswer);
434
+ const fallback = fallbackAnswers[index];
435
+ const isExample = item.isExample === true;
436
+ return {
437
+ imageUrl: asString(itemContent.imageUrl),
438
+ correctAnswer: asString(itemAnswer.answer, typeof fallback === 'string' ? fallback : ''),
439
+ isExample,
440
+ points: isExample ? 0 : asNumber(item.points, 1),
441
+ questionNumber: asNumber(item.questionNumber, index + 1),
442
+ };
443
+ });
444
+ return {
445
+ content: '',
446
+ answer: {
447
+ title: asString(meta.title),
448
+ instruction: asString(meta.instruction, 'Look and match.'),
449
+ rightItems,
450
+ items,
451
+ explanation: asString(apiQuestion.explanation),
452
+ points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
453
+ },
454
+ };
455
+ };
311
456
  const reverseCrossOutWordGroup = (apiQuestion) => {
312
457
  const content = asRecord(apiQuestion.content);
313
458
  const answer = readAnswer(apiQuestion);
@@ -460,6 +605,8 @@ const handlers = {
460
605
  ANSWER_THE_QUESTION_GROUP: reverseAnswerTheQuestionGroup,
461
606
  TRUE_FALSE_GROUP: reverseTrueFalseGroup,
462
607
  TRUE_FALSE_CORRECT_GROUP: reverseTrueFalseCorrectGroup,
608
+ FILL_IN_BLANK_GROUP: reverseFillInBlankGroup,
609
+ MATCHING_WITH_LINES_GROUP: reverseMatchingWithLinesGroup,
463
610
  CROSS_OUT_WORD_GROUP: reverseCrossOutWordGroup,
464
611
  WORD_ORDER_AND_MATCH_GROUP: reverseWordOrderAndMatchGroup,
465
612
  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.7",
4
4
  "description": "Reusable OceanEdu question components.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",