@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/index.cjs
CHANGED
|
@@ -3819,8 +3819,9 @@ function generateLrsBridgeCode(options) {
|
|
|
3819
3819
|
});
|
|
3820
3820
|
}
|
|
3821
3821
|
|
|
3822
|
-
// Track submitted Knowledge Check
|
|
3822
|
+
// Track submitted Knowledge Check questions to avoid duplicates
|
|
3823
3823
|
var submittedKnowledgeChecks = {};
|
|
3824
|
+
var kcQuestionCounter = 0;
|
|
3824
3825
|
|
|
3825
3826
|
/**
|
|
3826
3827
|
* Set up interceptors specifically for Rise Knowledge Check blocks
|
|
@@ -3830,7 +3831,11 @@ function generateLrsBridgeCode(options) {
|
|
|
3830
3831
|
if (!TRACK_QUIZZES) return;
|
|
3831
3832
|
|
|
3832
3833
|
// Intercept submit button clicks on Knowledge Check blocks
|
|
3834
|
+
// NOTE: If the SCORM tracker is active, it handles quiz tracking via
|
|
3835
|
+
// cmi.interactions \u2014 skip DOM scraping to avoid duplicate statements
|
|
3833
3836
|
document.addEventListener('click', function(e) {
|
|
3837
|
+
if (scormTrackerActive) return; // SCORM tracker handles this
|
|
3838
|
+
|
|
3834
3839
|
var submitBtn = e.target.closest('.quiz-card__button');
|
|
3835
3840
|
if (!submitBtn) return;
|
|
3836
3841
|
|
|
@@ -3852,16 +3857,26 @@ function generateLrsBridgeCode(options) {
|
|
|
3852
3857
|
* Extract and send xAPI statement for a Knowledge Check submission
|
|
3853
3858
|
*/
|
|
3854
3859
|
function extractKnowledgeCheckResult(kcBlock) {
|
|
3855
|
-
// Get block ID for
|
|
3860
|
+
// Get block ID for context
|
|
3856
3861
|
var blockContainer = kcBlock.closest('[data-block-id]');
|
|
3857
3862
|
var blockId = blockContainer ? blockContainer.getAttribute('data-block-id') : null;
|
|
3858
3863
|
|
|
3864
|
+
// Get question text first \u2014 needed for per-question dedup key
|
|
3865
|
+
var questionText = '';
|
|
3866
|
+
var questionTextEl = kcBlock.querySelector('.quiz-card__title .fr-view, .quiz-card__title');
|
|
3867
|
+
if (questionTextEl) {
|
|
3868
|
+
questionText = questionTextEl.textContent.trim();
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3859
3871
|
// Get question ID from the title element
|
|
3860
3872
|
var questionTitleEl = kcBlock.querySelector('.quiz-card__title');
|
|
3861
|
-
var questionId = questionTitleEl ? questionTitleEl.id :
|
|
3873
|
+
var questionId = questionTitleEl ? questionTitleEl.id : null;
|
|
3874
|
+
|
|
3875
|
+
// Build a question-specific dedup key using question text hash
|
|
3876
|
+
// This ensures each question in a multi-question quiz block gets its own key
|
|
3877
|
+
var questionHash = questionText ? questionText.substring(0, 100) : (questionId || blockId || generateUUID());
|
|
3878
|
+
var submissionKey = 'kc-' + questionHash;
|
|
3862
3879
|
|
|
3863
|
-
// Check if we already processed this submission (avoid duplicates)
|
|
3864
|
-
var submissionKey = blockId || questionId;
|
|
3865
3880
|
var feedbackLabel = kcBlock.querySelector('.quiz-card__feedback-label');
|
|
3866
3881
|
if (!feedbackLabel) {
|
|
3867
3882
|
log('Knowledge Check: No feedback visible yet');
|
|
@@ -3877,11 +3892,9 @@ function generateLrsBridgeCode(options) {
|
|
|
3877
3892
|
}
|
|
3878
3893
|
submittedKnowledgeChecks[submissionId] = true;
|
|
3879
3894
|
|
|
3880
|
-
//
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
if (questionTextEl) {
|
|
3884
|
-
questionText = questionTextEl.textContent.trim();
|
|
3895
|
+
// Use question-specific ID for the statement (not the shared block ID)
|
|
3896
|
+
if (!questionId) {
|
|
3897
|
+
questionId = blockId ? 'q-' + blockId + '-' + questionHash.substring(0, 20) : 'q-' + generateUUID();
|
|
3885
3898
|
}
|
|
3886
3899
|
|
|
3887
3900
|
// Determine question type from aria-label
|
|
@@ -3910,11 +3923,14 @@ function generateLrsBridgeCode(options) {
|
|
|
3910
3923
|
correct: isCorrect
|
|
3911
3924
|
});
|
|
3912
3925
|
|
|
3926
|
+
// Increment question counter for this session
|
|
3927
|
+
kcQuestionCounter++;
|
|
3928
|
+
|
|
3913
3929
|
// Send question answered statement using existing LRS method
|
|
3914
3930
|
LRS.questionAnswered({
|
|
3915
3931
|
questionId: questionId,
|
|
3916
3932
|
questionGuid: blockId || generateUUID(),
|
|
3917
|
-
questionNumber:
|
|
3933
|
+
questionNumber: kcQuestionCounter,
|
|
3918
3934
|
questionText: questionText.substring(0, 500),
|
|
3919
3935
|
questionType: questionType,
|
|
3920
3936
|
answer: answerText.substring(0, 500),
|
|
@@ -3975,6 +3991,142 @@ function generateLrsBridgeCode(options) {
|
|
|
3975
3991
|
return answerText;
|
|
3976
3992
|
}
|
|
3977
3993
|
|
|
3994
|
+
// ========================================================================
|
|
3995
|
+
// SCORM INTERACTION TRACKER
|
|
3996
|
+
// Intercepts cmi.interactions.N.* SetValue calls to capture quiz answers
|
|
3997
|
+
// directly from the SCORM data model \u2014 works regardless of Rise UI format
|
|
3998
|
+
// (Knowledge Check blocks, quiz lessons, etc.)
|
|
3999
|
+
// ========================================================================
|
|
4000
|
+
var scormInteractions = {}; // Pending interactions keyed by index N
|
|
4001
|
+
var scormInteractionsSent = {}; // Track which interactions were already sent
|
|
4002
|
+
var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue \u2014 KC handler defers
|
|
4003
|
+
|
|
4004
|
+
function setupScormInteractionTracker() {
|
|
4005
|
+
if (!TRACK_QUIZZES) return;
|
|
4006
|
+
if (!LRS.scormApi) {
|
|
4007
|
+
log('SCORM Interaction Tracker: No SCORM API found, skipping');
|
|
4008
|
+
return;
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
var api = LRS.scormApi;
|
|
4012
|
+
var apiType = LRS.scormApiType;
|
|
4013
|
+
|
|
4014
|
+
// Determine which SetValue function to wrap
|
|
4015
|
+
var setValueFn = apiType === '2004' ? 'SetValue' : 'LMSSetValue';
|
|
4016
|
+
var originalSetValue = api[setValueFn];
|
|
4017
|
+
|
|
4018
|
+
if (typeof originalSetValue !== 'function') {
|
|
4019
|
+
log('SCORM Interaction Tracker: No ' + setValueFn + ' function found');
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
// SCORM 1.2 uses cmi.interactions.N.*, SCORM 2004 uses cmi.interactions.N.*
|
|
4024
|
+
var interactionPattern = /^cmi\\.interactions\\.(\\d+)\\.(.+)$/;
|
|
4025
|
+
|
|
4026
|
+
api[setValueFn] = function(key, value) {
|
|
4027
|
+
// Always call the original first
|
|
4028
|
+
var result = originalSetValue.apply(api, arguments);
|
|
4029
|
+
|
|
4030
|
+
// Check if this is an interaction data element
|
|
4031
|
+
if (typeof key === 'string') {
|
|
4032
|
+
var match = key.match(interactionPattern);
|
|
4033
|
+
if (match) {
|
|
4034
|
+
var idx = match[1];
|
|
4035
|
+
var field = match[2];
|
|
4036
|
+
|
|
4037
|
+
// Initialize interaction tracking for this index
|
|
4038
|
+
if (!scormInteractions[idx]) {
|
|
4039
|
+
scormInteractions[idx] = {};
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
// Store the field value
|
|
4043
|
+
scormInteractions[idx][field] = value;
|
|
4044
|
+
|
|
4045
|
+
log('SCORM Interaction [' + idx + '].' + field + ' = ' + (value ? value.substring(0, 80) : value));
|
|
4046
|
+
|
|
4047
|
+
// When 'result' is set, the interaction is complete \u2014 fire xAPI statement
|
|
4048
|
+
if (field === 'result' && !scormInteractionsSent[idx]) {
|
|
4049
|
+
scormInteractionsSent[idx] = true;
|
|
4050
|
+
var interaction = scormInteractions[idx];
|
|
4051
|
+
|
|
4052
|
+
// Map SCORM interaction type to readable type
|
|
4053
|
+
var typeMap = {
|
|
4054
|
+
'choice': 'multiple-choice',
|
|
4055
|
+
'true-false': 'true-false',
|
|
4056
|
+
'fill-in': 'fill-in-blank',
|
|
4057
|
+
'matching': 'matching',
|
|
4058
|
+
'performance': 'performance',
|
|
4059
|
+
'sequencing': 'sequencing',
|
|
4060
|
+
'likert': 'likert',
|
|
4061
|
+
'numeric': 'numeric'
|
|
4062
|
+
};
|
|
4063
|
+
|
|
4064
|
+
var scormType = interaction.type || 'unknown';
|
|
4065
|
+
var questionType = typeMap[scormType] || scormType;
|
|
4066
|
+
|
|
4067
|
+
// Determine correctness from SCORM result value
|
|
4068
|
+
// SCORM 1.2: 'correct', 'wrong', 'unanticipated', 'neutral'
|
|
4069
|
+
// SCORM 2004: 'correct', 'incorrect', 'unanticipated', 'neutral'
|
|
4070
|
+
var isCorrect = value === 'correct';
|
|
4071
|
+
|
|
4072
|
+
// Get student response \u2014 for 'choice' type, this might be indices like '1,3'
|
|
4073
|
+
var studentResponse = interaction.student_response || interaction['student_response'] || '';
|
|
4074
|
+
|
|
4075
|
+
// Get correct response pattern
|
|
4076
|
+
var correctResponse = '';
|
|
4077
|
+
// SCORM 1.2: correct_responses.0.pattern
|
|
4078
|
+
// Check stored fields for correct_responses
|
|
4079
|
+
Object.keys(interaction).forEach(function(k) {
|
|
4080
|
+
if (k.indexOf('correct_responses') > -1 && k.indexOf('pattern') > -1) {
|
|
4081
|
+
correctResponse = interaction[k];
|
|
4082
|
+
}
|
|
4083
|
+
});
|
|
4084
|
+
|
|
4085
|
+
// Get interaction ID (Rise sets this to a unique question identifier)
|
|
4086
|
+
var interactionId = interaction.id || ('interaction-' + idx);
|
|
4087
|
+
|
|
4088
|
+
// Get lesson context
|
|
4089
|
+
var lessonInfo = getCachedLessonInfo();
|
|
4090
|
+
|
|
4091
|
+
// Increment question counter
|
|
4092
|
+
kcQuestionCounter++;
|
|
4093
|
+
|
|
4094
|
+
log('SCORM Interaction complete [' + idx + ']:', {
|
|
4095
|
+
id: interactionId,
|
|
4096
|
+
type: questionType,
|
|
4097
|
+
response: studentResponse,
|
|
4098
|
+
correctResponse: correctResponse,
|
|
4099
|
+
result: value,
|
|
4100
|
+
correct: isCorrect,
|
|
4101
|
+
questionNumber: kcQuestionCounter
|
|
4102
|
+
});
|
|
4103
|
+
|
|
4104
|
+
// Send xAPI answered statement
|
|
4105
|
+
LRS.questionAnswered({
|
|
4106
|
+
questionId: interactionId,
|
|
4107
|
+
questionGuid: interactionId,
|
|
4108
|
+
questionNumber: kcQuestionCounter,
|
|
4109
|
+
questionText: 'Question ' + kcQuestionCounter + ' (' + interactionId + ')',
|
|
4110
|
+
questionType: questionType,
|
|
4111
|
+
answer: studentResponse,
|
|
4112
|
+
correctAnswer: correctResponse,
|
|
4113
|
+
correct: isCorrect,
|
|
4114
|
+
result: isCorrect ? 'correct' : 'incorrect',
|
|
4115
|
+
assessmentName: lessonInfo.name || 'Quiz',
|
|
4116
|
+
lessonName: lessonInfo.name,
|
|
4117
|
+
sectionName: lessonInfo.sectionName
|
|
4118
|
+
});
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
return result;
|
|
4124
|
+
};
|
|
4125
|
+
|
|
4126
|
+
scormTrackerActive = true;
|
|
4127
|
+
log('SCORM Interaction Tracker: Wrapped ' + setValueFn + ' (apiType=' + apiType + ') \u2014 KC DOM handler will defer to SCORM tracker');
|
|
4128
|
+
}
|
|
4129
|
+
|
|
3978
4130
|
function setupInteractionInterceptors() {
|
|
3979
4131
|
if (!TRACK_INTERACTIONS) return;
|
|
3980
4132
|
|
|
@@ -4193,6 +4345,9 @@ function generateLrsBridgeCode(options) {
|
|
|
4193
4345
|
LRS.scormApiFound = true;
|
|
4194
4346
|
LRS.scormApiType = result.type;
|
|
4195
4347
|
|
|
4348
|
+
// Wrap SCORM SetValue to intercept cmi.interactions for quiz tracking
|
|
4349
|
+
setupScormInteractionTracker();
|
|
4350
|
+
|
|
4196
4351
|
// Read learner_id for diagnostics
|
|
4197
4352
|
var scormLearnerId = 'n/a', scormLearnerName = 'n/a';
|
|
4198
4353
|
try {
|