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