@patch-adams/core 1.5.2 → 1.5.4
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/dist/cli.cjs +166 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +166 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +166 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +166 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -4152,8 +4152,9 @@ function generateLrsBridgeCode(options) {
|
|
|
4152
4152
|
});
|
|
4153
4153
|
}
|
|
4154
4154
|
|
|
4155
|
-
// Track submitted Knowledge Check
|
|
4155
|
+
// Track submitted Knowledge Check questions to avoid duplicates
|
|
4156
4156
|
var submittedKnowledgeChecks = {};
|
|
4157
|
+
var kcQuestionCounter = 0;
|
|
4157
4158
|
|
|
4158
4159
|
/**
|
|
4159
4160
|
* Set up interceptors specifically for Rise Knowledge Check blocks
|
|
@@ -4163,7 +4164,11 @@ function generateLrsBridgeCode(options) {
|
|
|
4163
4164
|
if (!TRACK_QUIZZES) return;
|
|
4164
4165
|
|
|
4165
4166
|
// Intercept submit button clicks on Knowledge Check blocks
|
|
4167
|
+
// NOTE: If the SCORM tracker is active, it handles quiz tracking via
|
|
4168
|
+
// cmi.interactions \u2014 skip DOM scraping to avoid duplicate statements
|
|
4166
4169
|
document.addEventListener('click', function(e) {
|
|
4170
|
+
if (scormTrackerActive) return; // SCORM tracker handles this
|
|
4171
|
+
|
|
4167
4172
|
var submitBtn = e.target.closest('.quiz-card__button');
|
|
4168
4173
|
if (!submitBtn) return;
|
|
4169
4174
|
|
|
@@ -4185,16 +4190,26 @@ function generateLrsBridgeCode(options) {
|
|
|
4185
4190
|
* Extract and send xAPI statement for a Knowledge Check submission
|
|
4186
4191
|
*/
|
|
4187
4192
|
function extractKnowledgeCheckResult(kcBlock) {
|
|
4188
|
-
// Get block ID for
|
|
4193
|
+
// Get block ID for context
|
|
4189
4194
|
var blockContainer = kcBlock.closest('[data-block-id]');
|
|
4190
4195
|
var blockId = blockContainer ? blockContainer.getAttribute('data-block-id') : null;
|
|
4191
4196
|
|
|
4197
|
+
// Get question text first \u2014 needed for per-question dedup key
|
|
4198
|
+
var questionText = '';
|
|
4199
|
+
var questionTextEl = kcBlock.querySelector('.quiz-card__title .fr-view, .quiz-card__title');
|
|
4200
|
+
if (questionTextEl) {
|
|
4201
|
+
questionText = questionTextEl.textContent.trim();
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4192
4204
|
// Get question ID from the title element
|
|
4193
4205
|
var questionTitleEl = kcBlock.querySelector('.quiz-card__title');
|
|
4194
|
-
var questionId = questionTitleEl ? questionTitleEl.id :
|
|
4206
|
+
var questionId = questionTitleEl ? questionTitleEl.id : null;
|
|
4207
|
+
|
|
4208
|
+
// Build a question-specific dedup key using question text hash
|
|
4209
|
+
// This ensures each question in a multi-question quiz block gets its own key
|
|
4210
|
+
var questionHash = questionText ? questionText.substring(0, 100) : (questionId || blockId || generateUUID());
|
|
4211
|
+
var submissionKey = 'kc-' + questionHash;
|
|
4195
4212
|
|
|
4196
|
-
// Check if we already processed this submission (avoid duplicates)
|
|
4197
|
-
var submissionKey = blockId || questionId;
|
|
4198
4213
|
var feedbackLabel = kcBlock.querySelector('.quiz-card__feedback-label');
|
|
4199
4214
|
if (!feedbackLabel) {
|
|
4200
4215
|
log('Knowledge Check: No feedback visible yet');
|
|
@@ -4210,11 +4225,9 @@ function generateLrsBridgeCode(options) {
|
|
|
4210
4225
|
}
|
|
4211
4226
|
submittedKnowledgeChecks[submissionId] = true;
|
|
4212
4227
|
|
|
4213
|
-
//
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
if (questionTextEl) {
|
|
4217
|
-
questionText = questionTextEl.textContent.trim();
|
|
4228
|
+
// Use question-specific ID for the statement (not the shared block ID)
|
|
4229
|
+
if (!questionId) {
|
|
4230
|
+
questionId = blockId ? 'q-' + blockId + '-' + questionHash.substring(0, 20) : 'q-' + generateUUID();
|
|
4218
4231
|
}
|
|
4219
4232
|
|
|
4220
4233
|
// Determine question type from aria-label
|
|
@@ -4243,11 +4256,14 @@ function generateLrsBridgeCode(options) {
|
|
|
4243
4256
|
correct: isCorrect
|
|
4244
4257
|
});
|
|
4245
4258
|
|
|
4259
|
+
// Increment question counter for this session
|
|
4260
|
+
kcQuestionCounter++;
|
|
4261
|
+
|
|
4246
4262
|
// Send question answered statement using existing LRS method
|
|
4247
4263
|
LRS.questionAnswered({
|
|
4248
4264
|
questionId: questionId,
|
|
4249
4265
|
questionGuid: blockId || generateUUID(),
|
|
4250
|
-
questionNumber:
|
|
4266
|
+
questionNumber: kcQuestionCounter,
|
|
4251
4267
|
questionText: questionText.substring(0, 500),
|
|
4252
4268
|
questionType: questionType,
|
|
4253
4269
|
answer: answerText.substring(0, 500),
|
|
@@ -4308,6 +4324,142 @@ function generateLrsBridgeCode(options) {
|
|
|
4308
4324
|
return answerText;
|
|
4309
4325
|
}
|
|
4310
4326
|
|
|
4327
|
+
// ========================================================================
|
|
4328
|
+
// SCORM INTERACTION TRACKER
|
|
4329
|
+
// Intercepts cmi.interactions.N.* SetValue calls to capture quiz answers
|
|
4330
|
+
// directly from the SCORM data model \u2014 works regardless of Rise UI format
|
|
4331
|
+
// (Knowledge Check blocks, quiz lessons, etc.)
|
|
4332
|
+
// ========================================================================
|
|
4333
|
+
var scormInteractions = {}; // Pending interactions keyed by index N
|
|
4334
|
+
var scormInteractionsSent = {}; // Track which interactions were already sent
|
|
4335
|
+
var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue \u2014 KC handler defers
|
|
4336
|
+
|
|
4337
|
+
function setupScormInteractionTracker() {
|
|
4338
|
+
if (!TRACK_QUIZZES) return;
|
|
4339
|
+
if (!LRS.scormApi) {
|
|
4340
|
+
log('SCORM Interaction Tracker: No SCORM API found, skipping');
|
|
4341
|
+
return;
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4344
|
+
var api = LRS.scormApi;
|
|
4345
|
+
var apiType = LRS.scormApiType;
|
|
4346
|
+
|
|
4347
|
+
// Determine which SetValue function to wrap
|
|
4348
|
+
var setValueFn = apiType === '2004' ? 'SetValue' : 'LMSSetValue';
|
|
4349
|
+
var originalSetValue = api[setValueFn];
|
|
4350
|
+
|
|
4351
|
+
if (typeof originalSetValue !== 'function') {
|
|
4352
|
+
log('SCORM Interaction Tracker: No ' + setValueFn + ' function found');
|
|
4353
|
+
return;
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
// SCORM 1.2 uses cmi.interactions.N.*, SCORM 2004 uses cmi.interactions.N.*
|
|
4357
|
+
var interactionPattern = /^cmi\\.interactions\\.(\\d+)\\.(.+)$/;
|
|
4358
|
+
|
|
4359
|
+
api[setValueFn] = function(key, value) {
|
|
4360
|
+
// Always call the original first
|
|
4361
|
+
var result = originalSetValue.apply(api, arguments);
|
|
4362
|
+
|
|
4363
|
+
// Check if this is an interaction data element
|
|
4364
|
+
if (typeof key === 'string') {
|
|
4365
|
+
var match = key.match(interactionPattern);
|
|
4366
|
+
if (match) {
|
|
4367
|
+
var idx = match[1];
|
|
4368
|
+
var field = match[2];
|
|
4369
|
+
|
|
4370
|
+
// Initialize interaction tracking for this index
|
|
4371
|
+
if (!scormInteractions[idx]) {
|
|
4372
|
+
scormInteractions[idx] = {};
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
// Store the field value
|
|
4376
|
+
scormInteractions[idx][field] = value;
|
|
4377
|
+
|
|
4378
|
+
log('SCORM Interaction [' + idx + '].' + field + ' = ' + (value ? value.substring(0, 80) : value));
|
|
4379
|
+
|
|
4380
|
+
// When 'result' is set, the interaction is complete \u2014 fire xAPI statement
|
|
4381
|
+
if (field === 'result' && !scormInteractionsSent[idx]) {
|
|
4382
|
+
scormInteractionsSent[idx] = true;
|
|
4383
|
+
var interaction = scormInteractions[idx];
|
|
4384
|
+
|
|
4385
|
+
// Map SCORM interaction type to readable type
|
|
4386
|
+
var typeMap = {
|
|
4387
|
+
'choice': 'multiple-choice',
|
|
4388
|
+
'true-false': 'true-false',
|
|
4389
|
+
'fill-in': 'fill-in-blank',
|
|
4390
|
+
'matching': 'matching',
|
|
4391
|
+
'performance': 'performance',
|
|
4392
|
+
'sequencing': 'sequencing',
|
|
4393
|
+
'likert': 'likert',
|
|
4394
|
+
'numeric': 'numeric'
|
|
4395
|
+
};
|
|
4396
|
+
|
|
4397
|
+
var scormType = interaction.type || 'unknown';
|
|
4398
|
+
var questionType = typeMap[scormType] || scormType;
|
|
4399
|
+
|
|
4400
|
+
// Determine correctness from SCORM result value
|
|
4401
|
+
// SCORM 1.2: 'correct', 'wrong', 'unanticipated', 'neutral'
|
|
4402
|
+
// SCORM 2004: 'correct', 'incorrect', 'unanticipated', 'neutral'
|
|
4403
|
+
var isCorrect = value === 'correct';
|
|
4404
|
+
|
|
4405
|
+
// Get student response \u2014 for 'choice' type, this might be indices like '1,3'
|
|
4406
|
+
var studentResponse = interaction.student_response || interaction['student_response'] || '';
|
|
4407
|
+
|
|
4408
|
+
// Get correct response pattern
|
|
4409
|
+
var correctResponse = '';
|
|
4410
|
+
// SCORM 1.2: correct_responses.0.pattern
|
|
4411
|
+
// Check stored fields for correct_responses
|
|
4412
|
+
Object.keys(interaction).forEach(function(k) {
|
|
4413
|
+
if (k.indexOf('correct_responses') > -1 && k.indexOf('pattern') > -1) {
|
|
4414
|
+
correctResponse = interaction[k];
|
|
4415
|
+
}
|
|
4416
|
+
});
|
|
4417
|
+
|
|
4418
|
+
// Get interaction ID (Rise sets this to a unique question identifier)
|
|
4419
|
+
var interactionId = interaction.id || ('interaction-' + idx);
|
|
4420
|
+
|
|
4421
|
+
// Get lesson context
|
|
4422
|
+
var lessonInfo = getCachedLessonInfo();
|
|
4423
|
+
|
|
4424
|
+
// Increment question counter
|
|
4425
|
+
kcQuestionCounter++;
|
|
4426
|
+
|
|
4427
|
+
log('SCORM Interaction complete [' + idx + ']:', {
|
|
4428
|
+
id: interactionId,
|
|
4429
|
+
type: questionType,
|
|
4430
|
+
response: studentResponse,
|
|
4431
|
+
correctResponse: correctResponse,
|
|
4432
|
+
result: value,
|
|
4433
|
+
correct: isCorrect,
|
|
4434
|
+
questionNumber: kcQuestionCounter
|
|
4435
|
+
});
|
|
4436
|
+
|
|
4437
|
+
// Send xAPI answered statement
|
|
4438
|
+
LRS.questionAnswered({
|
|
4439
|
+
questionId: interactionId,
|
|
4440
|
+
questionGuid: interactionId,
|
|
4441
|
+
questionNumber: kcQuestionCounter,
|
|
4442
|
+
questionText: 'Question ' + kcQuestionCounter + ' (' + interactionId + ')',
|
|
4443
|
+
questionType: questionType,
|
|
4444
|
+
answer: studentResponse,
|
|
4445
|
+
correctAnswer: correctResponse,
|
|
4446
|
+
correct: isCorrect,
|
|
4447
|
+
result: isCorrect ? 'correct' : 'incorrect',
|
|
4448
|
+
assessmentName: lessonInfo.name || 'Quiz',
|
|
4449
|
+
lessonName: lessonInfo.name,
|
|
4450
|
+
sectionName: lessonInfo.sectionName
|
|
4451
|
+
});
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
return result;
|
|
4457
|
+
};
|
|
4458
|
+
|
|
4459
|
+
scormTrackerActive = true;
|
|
4460
|
+
log('SCORM Interaction Tracker: Wrapped ' + setValueFn + ' (apiType=' + apiType + ') \u2014 KC DOM handler will defer to SCORM tracker');
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4311
4463
|
function setupInteractionInterceptors() {
|
|
4312
4464
|
if (!TRACK_INTERACTIONS) return;
|
|
4313
4465
|
|
|
@@ -4526,6 +4678,9 @@ function generateLrsBridgeCode(options) {
|
|
|
4526
4678
|
LRS.scormApiFound = true;
|
|
4527
4679
|
LRS.scormApiType = result.type;
|
|
4528
4680
|
|
|
4681
|
+
// Wrap SCORM SetValue to intercept cmi.interactions for quiz tracking
|
|
4682
|
+
setupScormInteractionTracker();
|
|
4683
|
+
|
|
4529
4684
|
// Read learner_id for diagnostics
|
|
4530
4685
|
var scormLearnerId = 'n/a', scormLearnerName = 'n/a';
|
|
4531
4686
|
try {
|