@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/index.js CHANGED
@@ -2890,7 +2890,8 @@ function generateLrsBridgeCode(options) {
2890
2890
  sendStatement(statement);
2891
2891
  };
2892
2892
 
2893
- // Content/Page viewed - uses document as object for Bravais aggregation
2893
+ // Content/Page viewed - uses page-specific object for human-readable display
2894
+ // Object shows page/lesson title, parent context maintains document aggregation
2894
2895
  LRS.contentOpened = function(data) {
2895
2896
  if (!TRACK_NAVIGATION) return;
2896
2897
 
@@ -2910,31 +2911,31 @@ function generateLrsBridgeCode(options) {
2910
2911
  lessonId = lessonId.substring(2); // Remove '#/'
2911
2912
  }
2912
2913
 
2913
- // Add page info to context extensions (not as main object)
2914
+ // Get the page title - prefer lesson name from DOM
2915
+ var pageTitle = data.title || lessonInfo.name || 'Page';
2916
+
2917
+ // Build page activity object with human-readable name
2918
+ // This shows the page/lesson title in the Object column
2919
+ var pageObject = buildPageActivityObject({
2920
+ pageGuid: lessonId,
2921
+ name: pageTitle,
2922
+ resourceType: 'Lesson'
2923
+ });
2924
+
2925
+ // Add page info to context extensions
2914
2926
  var additionalContext = {
2915
2927
  extensions: {
2916
2928
  pageId: lessonId || 'home',
2917
- pageTitle: data.title || lessonInfo.name || 'Page',
2929
+ pageTitle: pageTitle,
2918
2930
  lessonName: lessonInfo.name,
2919
2931
  sectionName: lessonInfo.sectionName,
2920
2932
  pageUrl: data.url || window.location.href
2921
2933
  }
2922
2934
  };
2923
2935
 
2924
- // Use document as main object (required for Bravais aggregation)
2925
- // Use 'experienced' verb which is standard xAPI for viewing content
2926
- var statement = buildStatement(
2927
- 'experienced',
2928
- 'http://xyleme.com/bravais/activities/document',
2929
- {
2930
- event: 'page_viewed',
2931
- pageId: lessonId,
2932
- lessonName: lessonInfo.name,
2933
- sectionName: lessonInfo.sectionName
2934
- },
2935
- result,
2936
- additionalContext
2937
- );
2936
+ // Use 'experienced' verb for viewing content
2937
+ // Parent course activity added by buildXylemeContext ensures document aggregation
2938
+ var statement = buildStatementXyleme('experienced', pageObject, result, additionalContext);
2938
2939
  sendStatement(statement);
2939
2940
  };
2940
2941
 
@@ -3386,6 +3387,9 @@ function generateLrsBridgeCode(options) {
3386
3387
  function setupQuizInterceptors() {
3387
3388
  if (!TRACK_QUIZZES) return;
3388
3389
 
3390
+ // Set up Knowledge Check specific interceptors for Rise blocks
3391
+ setupKnowledgeCheckInterceptors();
3392
+
3389
3393
  var quizObserver = new MutationObserver(function(mutations) {
3390
3394
  mutations.forEach(function(mutation) {
3391
3395
  mutation.addedNodes.forEach(function(node) {
@@ -3590,6 +3594,162 @@ function generateLrsBridgeCode(options) {
3590
3594
  });
3591
3595
  }
3592
3596
 
3597
+ // Track submitted Knowledge Check blocks to avoid duplicates
3598
+ var submittedKnowledgeChecks = {};
3599
+
3600
+ /**
3601
+ * Set up interceptors specifically for Rise Knowledge Check blocks
3602
+ * These blocks have a different DOM structure than standard quiz results
3603
+ */
3604
+ function setupKnowledgeCheckInterceptors() {
3605
+ if (!TRACK_QUIZZES) return;
3606
+
3607
+ // Intercept submit button clicks on Knowledge Check blocks
3608
+ document.addEventListener('click', function(e) {
3609
+ var submitBtn = e.target.closest('.quiz-card__button');
3610
+ if (!submitBtn) return;
3611
+
3612
+ // Find the Knowledge Check block
3613
+ var kcBlock = submitBtn.closest('[data-test-id="block-kc-card"]') ||
3614
+ submitBtn.closest('.block-knowledge');
3615
+ if (!kcBlock) return;
3616
+
3617
+ // Wait for feedback to appear after submission
3618
+ setTimeout(function() {
3619
+ extractKnowledgeCheckResult(kcBlock);
3620
+ }, 500);
3621
+ }, true);
3622
+
3623
+ log('Knowledge Check interceptors set up');
3624
+ }
3625
+
3626
+ /**
3627
+ * Extract and send xAPI statement for a Knowledge Check submission
3628
+ */
3629
+ function extractKnowledgeCheckResult(kcBlock) {
3630
+ // Get block ID for deduplication
3631
+ var blockContainer = kcBlock.closest('[data-block-id]');
3632
+ var blockId = blockContainer ? blockContainer.getAttribute('data-block-id') : null;
3633
+
3634
+ // Get question ID from the title element
3635
+ var questionTitleEl = kcBlock.querySelector('.quiz-card__title');
3636
+ var questionId = questionTitleEl ? questionTitleEl.id : (blockId ? 'q-' + blockId : 'q-' + generateUUID());
3637
+
3638
+ // Check if we already processed this submission (avoid duplicates)
3639
+ var submissionKey = blockId || questionId;
3640
+ var feedbackLabel = kcBlock.querySelector('.quiz-card__feedback-label');
3641
+ if (!feedbackLabel) {
3642
+ log('Knowledge Check: No feedback visible yet');
3643
+ return;
3644
+ }
3645
+
3646
+ var feedbackText = feedbackLabel.textContent.trim().toLowerCase();
3647
+ var submissionId = submissionKey + '-' + feedbackText;
3648
+
3649
+ if (submittedKnowledgeChecks[submissionId]) {
3650
+ log('Knowledge Check: Already processed this submission');
3651
+ return;
3652
+ }
3653
+ submittedKnowledgeChecks[submissionId] = true;
3654
+
3655
+ // Get question text
3656
+ var questionText = '';
3657
+ var questionTextEl = kcBlock.querySelector('.quiz-card__title .fr-view, .quiz-card__title');
3658
+ if (questionTextEl) {
3659
+ questionText = questionTextEl.textContent.trim();
3660
+ }
3661
+
3662
+ // Determine question type from aria-label
3663
+ var wrapper = kcBlock.querySelector('[data-test-id="block-knowledge-wrapper"]');
3664
+ var ariaLabel = wrapper ? wrapper.getAttribute('aria-label') : '';
3665
+ var questionType = 'unknown';
3666
+ if (ariaLabel.indexOf('Multiple choice') > -1) questionType = 'multiple-choice';
3667
+ else if (ariaLabel.indexOf('Multiple response') > -1) questionType = 'multiple-response';
3668
+ else if (ariaLabel.indexOf('Fill in the blank') > -1) questionType = 'fill-in-blank';
3669
+ else if (ariaLabel.indexOf('Matching') > -1) questionType = 'matching';
3670
+
3671
+ // Get selected answer(s) based on question type
3672
+ var answerText = extractKnowledgeCheckAnswer(kcBlock, questionType);
3673
+
3674
+ // Get correct/incorrect from feedback
3675
+ var isCorrect = feedbackText === 'correct';
3676
+
3677
+ // Get lesson context
3678
+ var lessonInfo = getCachedLessonInfo();
3679
+
3680
+ log('Knowledge Check submitted:', {
3681
+ questionId: questionId,
3682
+ questionText: questionText.substring(0, 50) + '...',
3683
+ questionType: questionType,
3684
+ answer: answerText.substring(0, 50) + '...',
3685
+ correct: isCorrect
3686
+ });
3687
+
3688
+ // Send question answered statement using existing LRS method
3689
+ LRS.questionAnswered({
3690
+ questionId: questionId,
3691
+ questionGuid: blockId || generateUUID(),
3692
+ questionNumber: 1,
3693
+ questionText: questionText.substring(0, 500),
3694
+ questionType: questionType,
3695
+ answer: answerText.substring(0, 500),
3696
+ correct: isCorrect,
3697
+ result: isCorrect ? 'correct' : 'incorrect',
3698
+ assessmentName: 'Knowledge Check',
3699
+ lessonName: lessonInfo.name,
3700
+ sectionName: lessonInfo.sectionName
3701
+ });
3702
+ }
3703
+
3704
+ /**
3705
+ * Extract the selected answer text from a Knowledge Check block
3706
+ * based on the question type
3707
+ */
3708
+ function extractKnowledgeCheckAnswer(kcBlock, questionType) {
3709
+ var answerText = '';
3710
+
3711
+ if (questionType === 'multiple-choice') {
3712
+ // Find checked radio button
3713
+ var checkedInput = kcBlock.querySelector('.quiz-multiple-choice-option__input:checked');
3714
+ if (checkedInput) {
3715
+ var label = checkedInput.closest('.quiz-multiple-choice-option');
3716
+ var textEl = label ? label.querySelector('.quiz-multiple-choice-option__label .fr-view, .quiz-multiple-choice-option__label') : null;
3717
+ answerText = textEl ? textEl.textContent.trim() : '';
3718
+ }
3719
+ }
3720
+ else if (questionType === 'multiple-response') {
3721
+ // Find all checked checkboxes
3722
+ var checkedInputs = kcBlock.querySelectorAll('.quiz-multiple-response-option__input:checked');
3723
+ var answers = [];
3724
+ checkedInputs.forEach(function(input) {
3725
+ var label = input.closest('.quiz-multiple-response-option');
3726
+ var textEl = label ? label.querySelector('.quiz-multiple-response-option__text .fr-view, .quiz-multiple-response-option__text') : null;
3727
+ if (textEl) answers.push(textEl.textContent.trim());
3728
+ });
3729
+ answerText = answers.join('; ');
3730
+ }
3731
+ else if (questionType === 'fill-in-blank') {
3732
+ // Get text input value
3733
+ var textInput = kcBlock.querySelector('.quiz-fill__input');
3734
+ answerText = textInput ? textInput.value.trim() : '';
3735
+ }
3736
+ else if (questionType === 'matching') {
3737
+ // Extract matching pairs from the drop zones
3738
+ var dropZones = kcBlock.querySelectorAll('.matching-drop-zone');
3739
+ var pairs = [];
3740
+ dropZones.forEach(function(zone) {
3741
+ var prompt = zone.querySelector('.matching-prompt-content');
3742
+ var response = zone.querySelector('.matching-interaction-piece-content');
3743
+ if (prompt && response) {
3744
+ pairs.push(prompt.textContent.trim() + ' \u2192 ' + response.textContent.trim());
3745
+ }
3746
+ });
3747
+ answerText = pairs.length > 0 ? pairs.join('; ') : 'Matching submitted';
3748
+ }
3749
+
3750
+ return answerText;
3751
+ }
3752
+
3593
3753
  function setupInteractionInterceptors() {
3594
3754
  if (!TRACK_INTERACTIONS) return;
3595
3755