@pie-element/mc-populated-blank 0.2.13 → 0.3.0-next.10
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.
- package/README.md +8 -5
- package/author.js +2 -0
- package/configure.js +2 -0
- package/dist/author/defaults.d.ts +74 -0
- package/dist/author/index.d.ts +0 -1
- package/dist/author/index.js +1732 -1553
- package/dist/author/index.js.map +1 -1
- package/dist/browser/author/index.js +41 -0
- package/dist/browser/author/index.js.map +1 -0
- package/dist/browser/client-6I83eQqA.js +2396 -0
- package/dist/browser/client-6I83eQqA.js.map +1 -0
- package/dist/browser/controller/index.js +209 -0
- package/dist/browser/controller/index.js.map +1 -0
- package/dist/browser/delivery/index.js +1131 -0
- package/dist/browser/delivery/index.js.map +1 -0
- package/dist/browser/i18n-CQZqUmjk.js +1542 -0
- package/dist/browser/i18n-CQZqUmjk.js.map +1 -0
- package/dist/browser/layoutLimits-Bp17YP2V.js +42 -0
- package/dist/browser/layoutLimits-Bp17YP2V.js.map +1 -0
- package/dist/browser/print/index.js +151 -0
- package/dist/browser/print/index.js.map +1 -0
- package/dist/controller/defaults.d.ts +8 -30
- package/dist/controller/index.d.ts +79 -63
- package/dist/controller/index.js +108 -85
- package/dist/controller/index.js.map +1 -1
- package/dist/delivery/computeAudioMode.d.ts +0 -1
- package/dist/delivery/computeChoiceCorrectness.d.ts +0 -1
- package/dist/delivery/computeDisplayState.d.ts +7 -8
- package/dist/delivery/computeLayoutProfile.d.ts +0 -1
- package/dist/delivery/computeLayoutStyle.d.ts +0 -2
- package/dist/delivery/i18n.d.ts +8 -0
- package/dist/delivery/index.d.ts +22 -1
- package/dist/delivery/index.js +5742 -3222
- package/dist/delivery/index.js.map +1 -1
- package/dist/delivery/variant-css-map.d.ts +15 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.iife.d.ts +0 -156
- package/dist/index.iife.js +139 -38
- package/dist/index.js +5742 -3222
- package/dist/index.js.map +1 -1
- package/dist/print/index.d.ts +4 -1
- package/dist/print/index.js +5418 -2879
- package/dist/print/index.js.map +1 -1
- package/dist/runtime-support.d.ts +12 -0
- package/dist/runtime-support.js +14 -0
- package/dist/runtime-support.js.map +1 -0
- package/dist/shared/layoutLimits.d.ts +1 -2
- package/dist/shared/types.d.ts +20 -19
- package/module/print.js +3883 -0
- package/module/print.js.map +1 -0
- package/package.json +42 -13
- package/print.js +2 -0
- package/dist/author/index.d.ts.map +0 -1
- package/dist/controller/defaults.d.ts.map +0 -1
- package/dist/controller/index.d.ts.map +0 -1
- package/dist/delivery/computeAudioMode.d.ts.map +0 -1
- package/dist/delivery/computeChoiceCorrectness.d.ts.map +0 -1
- package/dist/delivery/computeDisplayState.d.ts.map +0 -1
- package/dist/delivery/computeLayoutProfile.d.ts.map +0 -1
- package/dist/delivery/computeLayoutStyle.d.ts.map +0 -1
- package/dist/delivery/index.d.ts.map +0 -1
- package/dist/delivery/variant-css-map.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.iife.d.ts.map +0 -1
- package/dist/print/index.d.ts.map +0 -1
- package/dist/shared/layoutLimits.d.ts.map +0 -1
- package/dist/shared/types.d.ts.map +0 -1
|
@@ -2,10 +2,13 @@ import { DEFAULT_LAYOUT_LIMITS } from '../shared/layoutLimits';
|
|
|
2
2
|
export { DEFAULT_LAYOUT_LIMITS };
|
|
3
3
|
/** Token authors must include exactly once in `template` (HTML). */
|
|
4
4
|
export declare const BLANK_TOKEN = "{{blank}}";
|
|
5
|
+
/**
|
|
6
|
+
* The values `normalize` fills in for fields an item leaves out. They carry no
|
|
7
|
+
* content and no `id` or `element`: those come from the item config, and a
|
|
8
|
+
* default here would stand in for a field the author never set.
|
|
9
|
+
*/
|
|
5
10
|
declare const _default: {
|
|
6
11
|
model: {
|
|
7
|
-
id: string;
|
|
8
|
-
element: string;
|
|
9
12
|
prompt: string;
|
|
10
13
|
promptEnabled: boolean;
|
|
11
14
|
interactionMode: "populate_blank";
|
|
@@ -21,8 +24,8 @@ declare const _default: {
|
|
|
21
24
|
horizontalChoiceWidthVw: number;
|
|
22
25
|
horizontalChoiceTileMinHeightRem: number;
|
|
23
26
|
horizontalChoiceContentMinHeightRem: number;
|
|
24
|
-
selectedImageMaxHeightRem: number;
|
|
25
27
|
choiceImageMaxHeightRem: number;
|
|
28
|
+
choiceImageMaxWidthRem: number;
|
|
26
29
|
listenButtonSizePx: number;
|
|
27
30
|
stimulusMinColumnPx: number;
|
|
28
31
|
textMinColumnPx: number;
|
|
@@ -51,45 +54,20 @@ declare const _default: {
|
|
|
51
54
|
};
|
|
52
55
|
audioButtonSkin: null;
|
|
53
56
|
audioButtonSkinsByLocale: {};
|
|
54
|
-
uiText: {
|
|
55
|
-
answerChoices: string;
|
|
56
|
-
selectedAnswerInSentence: string;
|
|
57
|
-
showCorrectAnswer: string;
|
|
58
|
-
hideCorrectAnswer: string;
|
|
59
|
-
clickToEnableAutoplay: string;
|
|
60
|
-
audioResourceUnavailable: string;
|
|
61
|
-
transcriptLabel: string;
|
|
62
|
-
listenLabelEn: string;
|
|
63
|
-
listenLabelEs: string;
|
|
64
|
-
listenSilentAlt: string;
|
|
65
|
-
listenPlayingAlt: string;
|
|
66
|
-
listenSilentAltEs: string;
|
|
67
|
-
listenPlayingAltEs: string;
|
|
68
|
-
};
|
|
69
57
|
sentenceHtml: string;
|
|
70
58
|
template: string;
|
|
71
59
|
choiceMode: "text";
|
|
72
|
-
choices:
|
|
73
|
-
|
|
74
|
-
labelHtml: string;
|
|
75
|
-
}[];
|
|
60
|
+
choices: never[];
|
|
61
|
+
choiceGroupLabel: string;
|
|
76
62
|
correctChoiceId: string;
|
|
77
63
|
hasAudio: boolean;
|
|
78
64
|
autoplayAudioEnabled: boolean;
|
|
79
65
|
completeAudioEnabled: boolean;
|
|
80
66
|
audioUrl: string;
|
|
81
67
|
audioTranscript: string;
|
|
82
|
-
showVisibleTranscript: boolean;
|
|
83
68
|
locale: string;
|
|
84
69
|
teacherInstructions: string;
|
|
85
70
|
teacherInstructionsEnabled: boolean;
|
|
86
71
|
};
|
|
87
|
-
configuration: {
|
|
88
|
-
prompt: {
|
|
89
|
-
label: string;
|
|
90
|
-
settings: boolean;
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
72
|
};
|
|
94
73
|
export default _default;
|
|
95
|
-
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { McpbChoice, McpbQuestion, McpbSession, McpbEnv, McpbCorrectness } from '../shared/types';
|
|
2
2
|
export declare function countBlankTokens(template: string): number;
|
|
3
3
|
export declare const getCorrectness: (question: McpbQuestion, session: McpbSession) => McpbCorrectness;
|
|
4
|
-
|
|
4
|
+
/** One blank, one key: an answered session scores 1 only when it picked the key. */
|
|
5
|
+
export declare const getPartialScore: (question: McpbQuestion, session: McpbSession) => 1 | 0;
|
|
5
6
|
export declare const outcome: (question: McpbQuestion, session: McpbSession, env: McpbEnv) => Promise<unknown>;
|
|
7
|
+
/** The authoring starting point: a question that passes `validate()` as it stands. */
|
|
6
8
|
export declare const createDefaultModel: (model?: McpbQuestion) => {
|
|
7
9
|
layoutLimits: {
|
|
8
10
|
blankStandaloneWidthRem: number;
|
|
@@ -13,8 +15,8 @@ export declare const createDefaultModel: (model?: McpbQuestion) => {
|
|
|
13
15
|
horizontalChoiceWidthVw: number;
|
|
14
16
|
horizontalChoiceTileMinHeightRem: number;
|
|
15
17
|
horizontalChoiceContentMinHeightRem: number;
|
|
16
|
-
selectedImageMaxHeightRem: number;
|
|
17
18
|
choiceImageMaxHeightRem: number;
|
|
19
|
+
choiceImageMaxWidthRem: number;
|
|
18
20
|
listenButtonSizePx: number;
|
|
19
21
|
stimulusMinColumnPx: number;
|
|
20
22
|
textMinColumnPx: number;
|
|
@@ -41,40 +43,44 @@ export declare const createDefaultModel: (model?: McpbQuestion) => {
|
|
|
41
43
|
inlineTemplateMarginTopRem: number;
|
|
42
44
|
inlineChoicesMarginTopRem: number;
|
|
43
45
|
};
|
|
44
|
-
id
|
|
45
|
-
element
|
|
46
|
-
prompt
|
|
47
|
-
promptEnabled
|
|
48
|
-
interactionMode
|
|
49
|
-
layoutProfile
|
|
50
|
-
choiceLayout
|
|
51
|
-
layoutProfilePresets
|
|
52
|
-
audioButtonSkin
|
|
53
|
-
audioButtonSkinsByLocale
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
completeAudioEnabled: boolean;
|
|
66
|
-
audioUrl: string;
|
|
67
|
-
audioTranscript: string;
|
|
68
|
-
showVisibleTranscript: boolean;
|
|
46
|
+
id?: string;
|
|
47
|
+
element?: string;
|
|
48
|
+
prompt?: string;
|
|
49
|
+
promptEnabled?: boolean;
|
|
50
|
+
interactionMode?: import("../shared/types").McpbInteractionMode;
|
|
51
|
+
layoutProfile?: string;
|
|
52
|
+
choiceLayout?: string;
|
|
53
|
+
layoutProfilePresets?: Record<string, Partial<import("../shared/layoutLimits").LayoutLimits>>;
|
|
54
|
+
audioButtonSkin?: import("../shared/types").McpbAudioButtonSkin | null;
|
|
55
|
+
audioButtonSkinsByLocale?: Record<string, import("../shared/types").McpbAudioButtonSkin>;
|
|
56
|
+
sentenceHtml?: string;
|
|
57
|
+
template?: string;
|
|
58
|
+
choiceMode?: import("../shared/types").McpbChoiceMode;
|
|
59
|
+
choices?: McpbChoice[];
|
|
60
|
+
choiceGroupLabel?: string;
|
|
61
|
+
correctChoiceId?: string;
|
|
62
|
+
hasAudio?: boolean;
|
|
63
|
+
autoplayAudioEnabled?: boolean;
|
|
64
|
+
completeAudioEnabled?: boolean;
|
|
65
|
+
audioUrl?: string;
|
|
66
|
+
audioTranscript?: string;
|
|
69
67
|
useFeatureButtonAudio?: boolean;
|
|
70
|
-
locale
|
|
71
|
-
|
|
68
|
+
locale?: string;
|
|
69
|
+
language?: string;
|
|
72
70
|
lockChoiceOrder?: boolean;
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
shuffle?: boolean;
|
|
72
|
+
teacherInstructions?: string;
|
|
73
|
+
teacherInstructionsEnabled?: boolean;
|
|
74
|
+
printAnswerKey?: boolean;
|
|
75
75
|
customType?: string;
|
|
76
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* What `model()` and `outcome()` read: the item's own fields, with neutral
|
|
79
|
+
* values for the rest. Starter content here would reach a learner as the
|
|
80
|
+
* item's prompt or choices.
|
|
81
|
+
*/
|
|
77
82
|
export declare const normalize: (question?: McpbQuestion) => {
|
|
83
|
+
lockChoiceOrder: boolean;
|
|
78
84
|
layoutLimits: {
|
|
79
85
|
blankStandaloneWidthRem: number;
|
|
80
86
|
blankWideWidthRem: number;
|
|
@@ -84,8 +90,8 @@ export declare const normalize: (question?: McpbQuestion) => {
|
|
|
84
90
|
horizontalChoiceWidthVw: number;
|
|
85
91
|
horizontalChoiceTileMinHeightRem: number;
|
|
86
92
|
horizontalChoiceContentMinHeightRem: number;
|
|
87
|
-
selectedImageMaxHeightRem: number;
|
|
88
93
|
choiceImageMaxHeightRem: number;
|
|
94
|
+
choiceImageMaxWidthRem: number;
|
|
89
95
|
listenButtonSizePx: number;
|
|
90
96
|
stimulusMinColumnPx: number;
|
|
91
97
|
textMinColumnPx: number;
|
|
@@ -112,37 +118,34 @@ export declare const normalize: (question?: McpbQuestion) => {
|
|
|
112
118
|
inlineTemplateMarginTopRem: number;
|
|
113
119
|
inlineChoicesMarginTopRem: number;
|
|
114
120
|
};
|
|
115
|
-
id
|
|
116
|
-
element
|
|
117
|
-
prompt
|
|
118
|
-
promptEnabled
|
|
119
|
-
interactionMode
|
|
120
|
-
layoutProfile
|
|
121
|
-
choiceLayout
|
|
122
|
-
layoutProfilePresets
|
|
123
|
-
audioButtonSkin
|
|
124
|
-
audioButtonSkinsByLocale
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
completeAudioEnabled: boolean;
|
|
137
|
-
audioUrl: string;
|
|
138
|
-
audioTranscript: string;
|
|
139
|
-
showVisibleTranscript: boolean;
|
|
121
|
+
id?: string;
|
|
122
|
+
element?: string;
|
|
123
|
+
prompt?: string;
|
|
124
|
+
promptEnabled?: boolean;
|
|
125
|
+
interactionMode?: import("../shared/types").McpbInteractionMode;
|
|
126
|
+
layoutProfile?: string;
|
|
127
|
+
choiceLayout?: string;
|
|
128
|
+
layoutProfilePresets?: Record<string, Partial<import("../shared/layoutLimits").LayoutLimits>>;
|
|
129
|
+
audioButtonSkin?: import("../shared/types").McpbAudioButtonSkin | null;
|
|
130
|
+
audioButtonSkinsByLocale?: Record<string, import("../shared/types").McpbAudioButtonSkin>;
|
|
131
|
+
sentenceHtml?: string;
|
|
132
|
+
template?: string;
|
|
133
|
+
choiceMode?: import("../shared/types").McpbChoiceMode;
|
|
134
|
+
choices?: McpbChoice[];
|
|
135
|
+
choiceGroupLabel?: string;
|
|
136
|
+
correctChoiceId?: string;
|
|
137
|
+
hasAudio?: boolean;
|
|
138
|
+
autoplayAudioEnabled?: boolean;
|
|
139
|
+
completeAudioEnabled?: boolean;
|
|
140
|
+
audioUrl?: string;
|
|
141
|
+
audioTranscript?: string;
|
|
140
142
|
useFeatureButtonAudio?: boolean;
|
|
141
|
-
locale
|
|
143
|
+
locale?: string;
|
|
144
|
+
language?: string;
|
|
142
145
|
shuffle?: boolean;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
teacherInstructions?: string;
|
|
147
|
+
teacherInstructionsEnabled?: boolean;
|
|
148
|
+
printAnswerKey?: boolean;
|
|
146
149
|
customType?: string;
|
|
147
150
|
};
|
|
148
151
|
export declare const normalizeSession: (s: McpbSession) => McpbSession;
|
|
@@ -151,6 +154,19 @@ type UpdateSessionFn = (id: string, element: string, data: {
|
|
|
151
154
|
}) => Promise<void>;
|
|
152
155
|
export declare const model: (question: McpbQuestion, session: McpbSession | null, env: McpbEnv | null, updateSession?: UpdateSessionFn) => Promise<Record<string, unknown>>;
|
|
153
156
|
export declare const createCorrectResponseSession: (question: McpbQuestion, env: McpbEnv) => Promise<unknown>;
|
|
154
|
-
|
|
157
|
+
type ValidateConfig = {
|
|
158
|
+
minAnswerChoices?: number;
|
|
159
|
+
maxAnswerChoices?: number;
|
|
160
|
+
prompt?: {
|
|
161
|
+
required?: boolean;
|
|
162
|
+
};
|
|
163
|
+
teacherInstructions?: {
|
|
164
|
+
required?: boolean;
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Authoring errors in multiple-choice's shape: a message per field, and under
|
|
169
|
+
* `choices` a message per choice keyed by its id.
|
|
170
|
+
*/
|
|
171
|
+
export declare const validate: (question?: McpbQuestion, config?: ValidateConfig) => Record<string, string | Record<string, string>>;
|
|
155
172
|
export {};
|
|
156
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/controller/index.js
CHANGED
|
@@ -9,8 +9,8 @@ var DEFAULT_LAYOUT_LIMITS = {
|
|
|
9
9
|
horizontalChoiceWidthVw: 30,
|
|
10
10
|
horizontalChoiceTileMinHeightRem: 11,
|
|
11
11
|
horizontalChoiceContentMinHeightRem: 7.5,
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
choiceImageMaxHeightRem: 9.375,
|
|
13
|
+
choiceImageMaxWidthRem: 9.375,
|
|
14
14
|
listenButtonSizePx: 128,
|
|
15
15
|
stimulusMinColumnPx: 210,
|
|
16
16
|
textMinColumnPx: 260,
|
|
@@ -41,37 +41,44 @@ var DEFAULT_LAYOUT_LIMITS = {
|
|
|
41
41
|
//#region src/controller/defaults.ts
|
|
42
42
|
/** Token authors must include exactly once in `template` (HTML). */
|
|
43
43
|
var BLANK_TOKEN = "{{blank}}";
|
|
44
|
+
/**
|
|
45
|
+
* The values `normalize` fills in for fields an item leaves out. They carry no
|
|
46
|
+
* content and no `id` or `element`: those come from the item config, and a
|
|
47
|
+
* default here would stand in for a field the author never set.
|
|
48
|
+
*/
|
|
49
|
+
var defaults_default$1 = { model: {
|
|
50
|
+
prompt: "",
|
|
51
|
+
promptEnabled: true,
|
|
52
|
+
interactionMode: "populate_blank",
|
|
53
|
+
layoutProfile: "",
|
|
54
|
+
choiceLayout: "",
|
|
55
|
+
layoutProfilePresets: {},
|
|
56
|
+
layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },
|
|
57
|
+
audioButtonSkin: null,
|
|
58
|
+
audioButtonSkinsByLocale: {},
|
|
59
|
+
sentenceHtml: "",
|
|
60
|
+
template: "",
|
|
61
|
+
choiceMode: "text",
|
|
62
|
+
choices: [],
|
|
63
|
+
choiceGroupLabel: "",
|
|
64
|
+
correctChoiceId: "",
|
|
65
|
+
hasAudio: false,
|
|
66
|
+
autoplayAudioEnabled: false,
|
|
67
|
+
completeAudioEnabled: false,
|
|
68
|
+
audioUrl: "",
|
|
69
|
+
audioTranscript: "",
|
|
70
|
+
locale: "",
|
|
71
|
+
teacherInstructions: "",
|
|
72
|
+
teacherInstructionsEnabled: true
|
|
73
|
+
} };
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/author/defaults.ts
|
|
76
|
+
/** The model a new item starts from: a valid question an author edits in place. */
|
|
44
77
|
var defaults_default = {
|
|
45
78
|
model: {
|
|
46
|
-
|
|
47
|
-
element: "mc-populated-blank",
|
|
79
|
+
...defaults_default$1.model,
|
|
48
80
|
prompt: "<p><strong>Demo prompt</strong></p>",
|
|
49
|
-
promptEnabled: true,
|
|
50
|
-
interactionMode: "populate_blank",
|
|
51
|
-
layoutProfile: "",
|
|
52
|
-
choiceLayout: "",
|
|
53
|
-
layoutProfilePresets: {},
|
|
54
|
-
layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },
|
|
55
|
-
audioButtonSkin: null,
|
|
56
|
-
audioButtonSkinsByLocale: {},
|
|
57
|
-
uiText: {
|
|
58
|
-
answerChoices: "Answer choices",
|
|
59
|
-
selectedAnswerInSentence: "blank",
|
|
60
|
-
showCorrectAnswer: "Show correct answer",
|
|
61
|
-
hideCorrectAnswer: "Hide correct answer",
|
|
62
|
-
clickToEnableAutoplay: "Click to enable audio autoplay",
|
|
63
|
-
audioResourceUnavailable: "Audio is enabled but no playable audio URL is configured.",
|
|
64
|
-
transcriptLabel: "Transcript",
|
|
65
|
-
listenLabelEn: "Listen",
|
|
66
|
-
listenLabelEs: "Escuchar",
|
|
67
|
-
listenSilentAlt: "Repeat instructions",
|
|
68
|
-
listenPlayingAlt: "Instructions are playing",
|
|
69
|
-
listenSilentAltEs: "Escuchar. Repetir las instrucciones.",
|
|
70
|
-
listenPlayingAltEs: "Escuchar. Estas son las instrucciones."
|
|
71
|
-
},
|
|
72
|
-
sentenceHtml: "",
|
|
73
81
|
template: `<p>The answer is ${BLANK_TOKEN}.</p>`,
|
|
74
|
-
choiceMode: "text",
|
|
75
82
|
choices: [
|
|
76
83
|
{
|
|
77
84
|
id: "c1",
|
|
@@ -86,16 +93,7 @@ var defaults_default = {
|
|
|
86
93
|
labelHtml: "<p>Option C</p>"
|
|
87
94
|
}
|
|
88
95
|
],
|
|
89
|
-
correctChoiceId: "c2"
|
|
90
|
-
hasAudio: false,
|
|
91
|
-
autoplayAudioEnabled: false,
|
|
92
|
-
completeAudioEnabled: false,
|
|
93
|
-
audioUrl: "",
|
|
94
|
-
audioTranscript: "",
|
|
95
|
-
showVisibleTranscript: false,
|
|
96
|
-
locale: "",
|
|
97
|
-
teacherInstructions: "",
|
|
98
|
-
teacherInstructionsEnabled: false
|
|
96
|
+
correctChoiceId: "c2"
|
|
99
97
|
},
|
|
100
98
|
configuration: { prompt: {
|
|
101
99
|
label: "Prompt",
|
|
@@ -115,10 +113,8 @@ var getCorrectness = (question, session) => {
|
|
|
115
113
|
if (session.choiceId === correct) return "correct";
|
|
116
114
|
return "incorrect";
|
|
117
115
|
};
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return 1;
|
|
121
|
-
};
|
|
116
|
+
/** One blank, one key: an answered session scores 1 only when it picked the key. */
|
|
117
|
+
var getPartialScore = (question, session) => getCorrectness(question, session) === "correct" ? 1 : 0;
|
|
122
118
|
var outcome = (question, session, env) => new Promise((resolve) => {
|
|
123
119
|
if (!session || isEmptyObject(session)) {
|
|
124
120
|
resolve({
|
|
@@ -129,8 +125,8 @@ var outcome = (question, session, env) => new Promise((resolve) => {
|
|
|
129
125
|
return;
|
|
130
126
|
}
|
|
131
127
|
session = normalizeSession(session);
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
128
|
+
const normalizedQuestion = normalize(question);
|
|
129
|
+
if (getCorrectness(normalizedQuestion, session) === "unanswered") {
|
|
134
130
|
resolve({
|
|
135
131
|
score: 0,
|
|
136
132
|
empty: true,
|
|
@@ -138,29 +134,41 @@ var outcome = (question, session, env) => new Promise((resolve) => {
|
|
|
138
134
|
});
|
|
139
135
|
return;
|
|
140
136
|
}
|
|
141
|
-
const score =
|
|
137
|
+
const score = getPartialScore(normalizedQuestion, session);
|
|
142
138
|
resolve({
|
|
143
139
|
score,
|
|
144
140
|
empty: false,
|
|
145
141
|
traceLog: [
|
|
146
142
|
`Mode: ${env?.mode || "unknown"}.`,
|
|
147
143
|
`Student selected choice: ${session.choiceId}.`,
|
|
148
|
-
`Correct choice: ${
|
|
144
|
+
`Correct choice: ${normalizedQuestion.correctChoiceId || "none"}.`,
|
|
149
145
|
`Final score: ${score}.`
|
|
150
146
|
]
|
|
151
147
|
});
|
|
152
148
|
});
|
|
153
|
-
var
|
|
154
|
-
...
|
|
149
|
+
var withDefaults = (base, model = {}) => ({
|
|
150
|
+
...base,
|
|
155
151
|
...model,
|
|
156
152
|
layoutLimits: {
|
|
157
153
|
...DEFAULT_LAYOUT_LIMITS,
|
|
158
154
|
...model?.layoutLimits || {}
|
|
159
155
|
}
|
|
160
156
|
});
|
|
161
|
-
|
|
157
|
+
/** The authoring starting point: a question that passes `validate()` as it stands. */
|
|
158
|
+
var createDefaultModel = (model = {}) => withDefaults(defaults_default.model, model);
|
|
159
|
+
/**
|
|
160
|
+
* What `model()` and `outcome()` read: the item's own fields, with neutral
|
|
161
|
+
* values for the rest. Starter content here would reach a learner as the
|
|
162
|
+
* item's prompt or choices.
|
|
163
|
+
*/
|
|
164
|
+
var normalize = (question = {}) => {
|
|
165
|
+
const { shuffle, ...rest } = question || {};
|
|
166
|
+
return {
|
|
167
|
+
...withDefaults(defaults_default$1.model, rest),
|
|
168
|
+
lockChoiceOrder: typeof rest.lockChoiceOrder === "boolean" ? rest.lockChoiceOrder : shuffle !== true
|
|
169
|
+
};
|
|
170
|
+
};
|
|
162
171
|
var normalizeSession = (s) => ({ ...s });
|
|
163
|
-
var shouldShuffleChoices = (question) => !!question?.shuffle;
|
|
164
172
|
var shouldLockChoices = (question, env) => {
|
|
165
173
|
if (question?.lockChoiceOrder) return true;
|
|
166
174
|
if (env?.["@pie-element"]?.lockChoiceOrder) return true;
|
|
@@ -174,6 +182,16 @@ function shuffleArray(items) {
|
|
|
174
182
|
}
|
|
175
183
|
return out;
|
|
176
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* The fields delivery renders. An imported choice can carry more, such as a
|
|
187
|
+
* correctness flag or feedback, which must not reach a learner's browser.
|
|
188
|
+
*/
|
|
189
|
+
var toDeliveryChoice = ({ id, labelHtml, imageUrl, imageAlt }) => ({
|
|
190
|
+
id,
|
|
191
|
+
...labelHtml !== void 0 ? { labelHtml } : {},
|
|
192
|
+
...imageUrl !== void 0 ? { imageUrl } : {},
|
|
193
|
+
...imageAlt !== void 0 ? { imageAlt } : {}
|
|
194
|
+
});
|
|
177
195
|
var getStoredShuffle = (session) => Array.isArray(session?.data?.shuffledValues) ? session.data.shuffledValues : Array.isArray(session?.shuffledValues) ? session.shuffledValues : [];
|
|
178
196
|
var applyShuffledValues = (choices, shuffledValues, choiceKey) => {
|
|
179
197
|
const orderedChoices = shuffledValues.map((value) => choices.find((choice) => choice?.[choiceKey] === value)).filter((c) => !!c);
|
|
@@ -184,8 +202,7 @@ var applyShuffledValues = (choices, shuffledValues, choiceKey) => {
|
|
|
184
202
|
};
|
|
185
203
|
var getOrderedChoices = async (question, session, env, updateSession) => {
|
|
186
204
|
const choices = Array.isArray(question?.choices) ? [...question.choices] : [];
|
|
187
|
-
if (!choices.length ||
|
|
188
|
-
if (shouldLockChoices(question, env || {})) return choices;
|
|
205
|
+
if (!choices.length || shouldLockChoices(question, env || {})) return choices;
|
|
189
206
|
const shuffledValues = getStoredShuffle(session);
|
|
190
207
|
if (shuffledValues.length) return applyShuffledValues(choices, shuffledValues, "id");
|
|
191
208
|
const shuffledChoices = shuffleArray(choices);
|
|
@@ -199,7 +216,7 @@ var model = async (question, session, env, updateSession) => {
|
|
|
199
216
|
const safeSession = session || {};
|
|
200
217
|
const safeEnv = env || {};
|
|
201
218
|
const normalizedQuestion = normalize(question);
|
|
202
|
-
const choices = await getOrderedChoices(normalizedQuestion, safeSession, safeEnv, updateSession);
|
|
219
|
+
const choices = (await getOrderedChoices(normalizedQuestion, safeSession, safeEnv, updateSession)).filter((c) => !!c && typeof c === "object").map(toDeliveryChoice);
|
|
203
220
|
const out = {
|
|
204
221
|
prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,
|
|
205
222
|
interactionMode: normalizedQuestion.interactionMode || "populate_blank",
|
|
@@ -209,20 +226,20 @@ var model = async (question, session, env, updateSession) => {
|
|
|
209
226
|
layoutLimits: normalizedQuestion.layoutLimits || { ...DEFAULT_LAYOUT_LIMITS },
|
|
210
227
|
audioButtonSkin: normalizedQuestion.audioButtonSkin && typeof normalizedQuestion.audioButtonSkin === "object" ? normalizedQuestion.audioButtonSkin : null,
|
|
211
228
|
audioButtonSkinsByLocale: normalizedQuestion.audioButtonSkinsByLocale && typeof normalizedQuestion.audioButtonSkinsByLocale === "object" ? normalizedQuestion.audioButtonSkinsByLocale : {},
|
|
212
|
-
uiText: normalizedQuestion.uiText && typeof normalizedQuestion.uiText === "object" ? normalizedQuestion.uiText : {},
|
|
213
229
|
sentenceHtml: normalizedQuestion.sentenceHtml || null,
|
|
214
230
|
template: normalizedQuestion.template,
|
|
215
231
|
choiceMode: normalizedQuestion.choiceMode,
|
|
216
232
|
choices,
|
|
233
|
+
choiceGroupLabel: normalizedQuestion.choiceGroupLabel || "",
|
|
217
234
|
hasAudio: normalizedQuestion.hasAudio,
|
|
218
235
|
autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,
|
|
219
236
|
completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,
|
|
220
237
|
audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,
|
|
221
238
|
audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,
|
|
222
|
-
showVisibleTranscript: !!normalizedQuestion.showVisibleTranscript,
|
|
223
239
|
customType: normalizedQuestion.customType || "",
|
|
224
240
|
useFeatureButtonAudio: typeof normalizedQuestion.useFeatureButtonAudio === "boolean" ? normalizedQuestion.useFeatureButtonAudio : void 0,
|
|
225
241
|
locale: normalizedQuestion.locale || "",
|
|
242
|
+
language: normalizedQuestion.language || normalizedQuestion.locale || void 0,
|
|
226
243
|
disabled: safeEnv.mode !== "gather",
|
|
227
244
|
mode: safeEnv.mode
|
|
228
245
|
};
|
|
@@ -240,18 +257,22 @@ var createCorrectResponseSession = (question, env) => {
|
|
|
240
257
|
return new Promise((resolve) => {
|
|
241
258
|
if (env.mode !== "evaluate" && env.role === "instructor") resolve({
|
|
242
259
|
id: "1",
|
|
243
|
-
element: "mc-populated-blank",
|
|
244
260
|
choiceId: question?.correctChoiceId || ""
|
|
245
261
|
});
|
|
246
262
|
else resolve(null);
|
|
247
263
|
});
|
|
248
264
|
};
|
|
249
|
-
|
|
265
|
+
/** Markup with its tags removed, keeping media an author can answer from. */
|
|
266
|
+
var getContent = (html) => (html || "").replace(/(<(?!img|iframe|source)([^>]+)>)/gi, "").trim();
|
|
267
|
+
/**
|
|
268
|
+
* Authoring errors in multiple-choice's shape: a message per field, and under
|
|
269
|
+
* `choices` a message per choice keyed by its id.
|
|
270
|
+
*/
|
|
271
|
+
var validate = (question = {}, config = {}) => {
|
|
250
272
|
const errors = {};
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
273
|
+
const { minAnswerChoices = 2, maxAnswerChoices } = config;
|
|
274
|
+
if ((question.promptEnabled || config.prompt?.required) && !getContent(question.prompt)) errors.prompt = "This field is required.";
|
|
275
|
+
if (config.teacherInstructions?.required && !getContent(question.teacherInstructions)) errors.teacherInstructions = "This field is required.";
|
|
255
276
|
const interactionMode = question.interactionMode || "populate_blank";
|
|
256
277
|
const n = countBlankTokens(question.template || "");
|
|
257
278
|
if (interactionMode === "audio_mc_only") {
|
|
@@ -261,33 +282,35 @@ var validate = (question = {}, _config = {}) => {
|
|
|
261
282
|
else if (n > 1) errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;
|
|
262
283
|
} else errors.interactionMode = "Unknown interaction mode";
|
|
263
284
|
const choices = Array.isArray(question.choices) ? question.choices : [];
|
|
264
|
-
if (choices.length <
|
|
285
|
+
if (choices.length < minAnswerChoices) errors.answerChoices = `There should be at least ${minAnswerChoices} choices defined.`;
|
|
286
|
+
else if (maxAnswerChoices != null && choices.length > maxAnswerChoices) errors.answerChoices = `No more than ${maxAnswerChoices} choices should be defined.`;
|
|
265
287
|
const mode = question.choiceMode || "text";
|
|
266
|
-
|
|
267
|
-
|
|
288
|
+
const choicesErrors = {};
|
|
289
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
290
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
291
|
+
choices.forEach((c, i) => {
|
|
292
|
+
const key = c?.id || String(i);
|
|
268
293
|
if (!c?.id) {
|
|
269
|
-
|
|
270
|
-
|
|
294
|
+
choicesErrors[key] = "Choice needs an id.";
|
|
295
|
+
return;
|
|
271
296
|
}
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
errors.choices = `Choice ${i + 1} needs label text`;
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
if (!c.imageUrl?.trim()) {
|
|
280
|
-
errors.choices = `Choice ${i + 1} needs an image URL`;
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
|
-
if (!c.imageAlt?.trim()) {
|
|
284
|
-
errors.choices = `Choice ${i + 1} needs image alt text`;
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
297
|
+
if (seenIds.has(c.id)) {
|
|
298
|
+
choicesErrors[key] = "Choice id should be unique.";
|
|
299
|
+
return;
|
|
287
300
|
}
|
|
288
|
-
|
|
301
|
+
seenIds.add(c.id);
|
|
302
|
+
if (mode === "text") {
|
|
303
|
+
const label = getContent(c.labelHtml);
|
|
304
|
+
if (!label) choicesErrors[key] = "Content should not be empty.";
|
|
305
|
+
else if (seenLabels.has(label)) choicesErrors[key] = "Content should be unique.";
|
|
306
|
+
seenLabels.add(label);
|
|
307
|
+
} else if (!c.imageUrl?.trim()) choicesErrors[key] = "An image is required.";
|
|
308
|
+
else if (!c.imageAlt?.trim()) choicesErrors[key] = "Image alt text is required.";
|
|
309
|
+
});
|
|
310
|
+
if (Object.keys(choicesErrors).length) errors.choices = choicesErrors;
|
|
289
311
|
const correct = question.correctChoiceId;
|
|
290
|
-
if (!correct
|
|
312
|
+
if (!correct) errors.correctResponse = "No correct response defined.";
|
|
313
|
+
else if (!choices.some((c) => c?.id === correct)) errors.correctResponse = "The correct response must be one of the choices.";
|
|
291
314
|
if (question.hasAudio) {
|
|
292
315
|
if (!!!question.audioUrl?.trim()) errors.audioUrl = "A playable audio URL is required when audio is enabled";
|
|
293
316
|
}
|
|
@@ -301,8 +324,8 @@ var validate = (question = {}, _config = {}) => {
|
|
|
301
324
|
"horizontalChoiceWidthVw",
|
|
302
325
|
"horizontalChoiceTileMinHeightRem",
|
|
303
326
|
"horizontalChoiceContentMinHeightRem",
|
|
304
|
-
"selectedImageMaxHeightRem",
|
|
305
327
|
"choiceImageMaxHeightRem",
|
|
328
|
+
"choiceImageMaxWidthRem",
|
|
306
329
|
"listenButtonSizePx",
|
|
307
330
|
"stimulusMinColumnPx",
|
|
308
331
|
"textMinColumnPx",
|