@patch-adams/core 1.4.10 → 1.4.12

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.js CHANGED
@@ -3225,7 +3225,8 @@ function generateLrsBridgeCode(options) {
3225
3225
  sendStatement(statement);
3226
3226
  };
3227
3227
 
3228
- // Content/Page viewed - uses document as object for Bravais aggregation
3228
+ // Content/Page viewed - uses page-specific object for human-readable display
3229
+ // Object shows page/lesson title, parent context maintains document aggregation
3229
3230
  LRS.contentOpened = function(data) {
3230
3231
  if (!TRACK_NAVIGATION) return;
3231
3232
 
@@ -3245,31 +3246,31 @@ function generateLrsBridgeCode(options) {
3245
3246
  lessonId = lessonId.substring(2); // Remove '#/'
3246
3247
  }
3247
3248
 
3248
- // Add page info to context extensions (not as main object)
3249
+ // Get the page title - prefer lesson name from DOM
3250
+ var pageTitle = data.title || lessonInfo.name || 'Page';
3251
+
3252
+ // Build page activity object with human-readable name
3253
+ // This shows the page/lesson title in the Object column
3254
+ var pageObject = buildPageActivityObject({
3255
+ pageGuid: lessonId,
3256
+ name: pageTitle,
3257
+ resourceType: 'Lesson'
3258
+ });
3259
+
3260
+ // Add page info to context extensions
3249
3261
  var additionalContext = {
3250
3262
  extensions: {
3251
3263
  pageId: lessonId || 'home',
3252
- pageTitle: data.title || lessonInfo.name || 'Page',
3264
+ pageTitle: pageTitle,
3253
3265
  lessonName: lessonInfo.name,
3254
3266
  sectionName: lessonInfo.sectionName,
3255
3267
  pageUrl: data.url || window.location.href
3256
3268
  }
3257
3269
  };
3258
3270
 
3259
- // Use document as main object (required for Bravais aggregation)
3260
- // Use 'experienced' verb which is standard xAPI for viewing content
3261
- var statement = buildStatement(
3262
- 'experienced',
3263
- 'http://xyleme.com/bravais/activities/document',
3264
- {
3265
- event: 'page_viewed',
3266
- pageId: lessonId,
3267
- lessonName: lessonInfo.name,
3268
- sectionName: lessonInfo.sectionName
3269
- },
3270
- result,
3271
- additionalContext
3272
- );
3271
+ // Use 'experienced' verb for viewing content
3272
+ // Parent course activity added by buildXylemeContext ensures document aggregation
3273
+ var statement = buildStatementXyleme('experienced', pageObject, result, additionalContext);
3273
3274
  sendStatement(statement);
3274
3275
  };
3275
3276
 
@@ -3721,6 +3722,9 @@ function generateLrsBridgeCode(options) {
3721
3722
  function setupQuizInterceptors() {
3722
3723
  if (!TRACK_QUIZZES) return;
3723
3724
 
3725
+ // Set up Knowledge Check specific interceptors for Rise blocks
3726
+ setupKnowledgeCheckInterceptors();
3727
+
3724
3728
  var quizObserver = new MutationObserver(function(mutations) {
3725
3729
  mutations.forEach(function(mutation) {
3726
3730
  mutation.addedNodes.forEach(function(node) {
@@ -3925,6 +3929,162 @@ function generateLrsBridgeCode(options) {
3925
3929
  });
3926
3930
  }
3927
3931
 
3932
+ // Track submitted Knowledge Check blocks to avoid duplicates
3933
+ var submittedKnowledgeChecks = {};
3934
+
3935
+ /**
3936
+ * Set up interceptors specifically for Rise Knowledge Check blocks
3937
+ * These blocks have a different DOM structure than standard quiz results
3938
+ */
3939
+ function setupKnowledgeCheckInterceptors() {
3940
+ if (!TRACK_QUIZZES) return;
3941
+
3942
+ // Intercept submit button clicks on Knowledge Check blocks
3943
+ document.addEventListener('click', function(e) {
3944
+ var submitBtn = e.target.closest('.quiz-card__button');
3945
+ if (!submitBtn) return;
3946
+
3947
+ // Find the Knowledge Check block
3948
+ var kcBlock = submitBtn.closest('[data-test-id="block-kc-card"]') ||
3949
+ submitBtn.closest('.block-knowledge');
3950
+ if (!kcBlock) return;
3951
+
3952
+ // Wait for feedback to appear after submission
3953
+ setTimeout(function() {
3954
+ extractKnowledgeCheckResult(kcBlock);
3955
+ }, 500);
3956
+ }, true);
3957
+
3958
+ log('Knowledge Check interceptors set up');
3959
+ }
3960
+
3961
+ /**
3962
+ * Extract and send xAPI statement for a Knowledge Check submission
3963
+ */
3964
+ function extractKnowledgeCheckResult(kcBlock) {
3965
+ // Get block ID for deduplication
3966
+ var blockContainer = kcBlock.closest('[data-block-id]');
3967
+ var blockId = blockContainer ? blockContainer.getAttribute('data-block-id') : null;
3968
+
3969
+ // Get question ID from the title element
3970
+ var questionTitleEl = kcBlock.querySelector('.quiz-card__title');
3971
+ var questionId = questionTitleEl ? questionTitleEl.id : (blockId ? 'q-' + blockId : 'q-' + generateUUID());
3972
+
3973
+ // Check if we already processed this submission (avoid duplicates)
3974
+ var submissionKey = blockId || questionId;
3975
+ var feedbackLabel = kcBlock.querySelector('.quiz-card__feedback-label');
3976
+ if (!feedbackLabel) {
3977
+ log('Knowledge Check: No feedback visible yet');
3978
+ return;
3979
+ }
3980
+
3981
+ var feedbackText = feedbackLabel.textContent.trim().toLowerCase();
3982
+ var submissionId = submissionKey + '-' + feedbackText;
3983
+
3984
+ if (submittedKnowledgeChecks[submissionId]) {
3985
+ log('Knowledge Check: Already processed this submission');
3986
+ return;
3987
+ }
3988
+ submittedKnowledgeChecks[submissionId] = true;
3989
+
3990
+ // Get question text
3991
+ var questionText = '';
3992
+ var questionTextEl = kcBlock.querySelector('.quiz-card__title .fr-view, .quiz-card__title');
3993
+ if (questionTextEl) {
3994
+ questionText = questionTextEl.textContent.trim();
3995
+ }
3996
+
3997
+ // Determine question type from aria-label
3998
+ var wrapper = kcBlock.querySelector('[data-test-id="block-knowledge-wrapper"]');
3999
+ var ariaLabel = wrapper ? wrapper.getAttribute('aria-label') : '';
4000
+ var questionType = 'unknown';
4001
+ if (ariaLabel.indexOf('Multiple choice') > -1) questionType = 'multiple-choice';
4002
+ else if (ariaLabel.indexOf('Multiple response') > -1) questionType = 'multiple-response';
4003
+ else if (ariaLabel.indexOf('Fill in the blank') > -1) questionType = 'fill-in-blank';
4004
+ else if (ariaLabel.indexOf('Matching') > -1) questionType = 'matching';
4005
+
4006
+ // Get selected answer(s) based on question type
4007
+ var answerText = extractKnowledgeCheckAnswer(kcBlock, questionType);
4008
+
4009
+ // Get correct/incorrect from feedback
4010
+ var isCorrect = feedbackText === 'correct';
4011
+
4012
+ // Get lesson context
4013
+ var lessonInfo = getCachedLessonInfo();
4014
+
4015
+ log('Knowledge Check submitted:', {
4016
+ questionId: questionId,
4017
+ questionText: questionText.substring(0, 50) + '...',
4018
+ questionType: questionType,
4019
+ answer: answerText.substring(0, 50) + '...',
4020
+ correct: isCorrect
4021
+ });
4022
+
4023
+ // Send question answered statement using existing LRS method
4024
+ LRS.questionAnswered({
4025
+ questionId: questionId,
4026
+ questionGuid: blockId || generateUUID(),
4027
+ questionNumber: 1,
4028
+ questionText: questionText.substring(0, 500),
4029
+ questionType: questionType,
4030
+ answer: answerText.substring(0, 500),
4031
+ correct: isCorrect,
4032
+ result: isCorrect ? 'correct' : 'incorrect',
4033
+ assessmentName: 'Knowledge Check',
4034
+ lessonName: lessonInfo.name,
4035
+ sectionName: lessonInfo.sectionName
4036
+ });
4037
+ }
4038
+
4039
+ /**
4040
+ * Extract the selected answer text from a Knowledge Check block
4041
+ * based on the question type
4042
+ */
4043
+ function extractKnowledgeCheckAnswer(kcBlock, questionType) {
4044
+ var answerText = '';
4045
+
4046
+ if (questionType === 'multiple-choice') {
4047
+ // Find checked radio button
4048
+ var checkedInput = kcBlock.querySelector('.quiz-multiple-choice-option__input:checked');
4049
+ if (checkedInput) {
4050
+ var label = checkedInput.closest('.quiz-multiple-choice-option');
4051
+ var textEl = label ? label.querySelector('.quiz-multiple-choice-option__label .fr-view, .quiz-multiple-choice-option__label') : null;
4052
+ answerText = textEl ? textEl.textContent.trim() : '';
4053
+ }
4054
+ }
4055
+ else if (questionType === 'multiple-response') {
4056
+ // Find all checked checkboxes
4057
+ var checkedInputs = kcBlock.querySelectorAll('.quiz-multiple-response-option__input:checked');
4058
+ var answers = [];
4059
+ checkedInputs.forEach(function(input) {
4060
+ var label = input.closest('.quiz-multiple-response-option');
4061
+ var textEl = label ? label.querySelector('.quiz-multiple-response-option__text .fr-view, .quiz-multiple-response-option__text') : null;
4062
+ if (textEl) answers.push(textEl.textContent.trim());
4063
+ });
4064
+ answerText = answers.join('; ');
4065
+ }
4066
+ else if (questionType === 'fill-in-blank') {
4067
+ // Get text input value
4068
+ var textInput = kcBlock.querySelector('.quiz-fill__input');
4069
+ answerText = textInput ? textInput.value.trim() : '';
4070
+ }
4071
+ else if (questionType === 'matching') {
4072
+ // Extract matching pairs from the drop zones
4073
+ var dropZones = kcBlock.querySelectorAll('.matching-drop-zone');
4074
+ var pairs = [];
4075
+ dropZones.forEach(function(zone) {
4076
+ var prompt = zone.querySelector('.matching-prompt-content');
4077
+ var response = zone.querySelector('.matching-interaction-piece-content');
4078
+ if (prompt && response) {
4079
+ pairs.push(prompt.textContent.trim() + ' \u2192 ' + response.textContent.trim());
4080
+ }
4081
+ });
4082
+ answerText = pairs.length > 0 ? pairs.join('; ') : 'Matching submitted';
4083
+ }
4084
+
4085
+ return answerText;
4086
+ }
4087
+
3928
4088
  function setupInteractionInterceptors() {
3929
4089
  if (!TRACK_INTERACTIONS) return;
3930
4090