@patch-adams/core 1.4.3 → 1.4.5

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
@@ -1056,48 +1056,55 @@ function generateLrsBridgeCode(options) {
1056
1056
 
1057
1057
  function setupBridge() {
1058
1058
  try {
1059
- // 1. Try PlayerIntegration first (Xyleme native mode)
1059
+ // 1. Try PlayerIntegration first (Xyleme native mode) - for SCORM APIs
1060
1060
  var integrationResult = findPlayerIntegration(10);
1061
1061
  if (integrationResult) {
1062
1062
  LRS.integration = integrationResult.integration;
1063
1063
  LRS.integrationLevel = integrationResult.level;
1064
- LRS.mode = 'playerIntegration';
1065
- log('Using PlayerIntegration mode');
1064
+ log('Found PlayerIntegration at level', integrationResult.level);
1066
1065
 
1067
1066
  if (LRS.integration.Internal_SCORM_2004_API) {
1068
1067
  window.API_1484_11 = LRS.integration.Internal_SCORM_2004_API;
1069
1068
  LRS.scormApi = window.API_1484_11;
1069
+ LRS.scormApiFound = true;
1070
+ LRS.scormApiType = '2004';
1070
1071
  }
1071
1072
  if (LRS.integration.Internal_SCORM_12_API) {
1072
1073
  window.API = LRS.integration.Internal_SCORM_12_API;
1074
+ if (!LRS.scormApiFound) {
1075
+ LRS.scormApi = window.API;
1076
+ LRS.scormApiFound = true;
1077
+ LRS.scormApiType = '1.2';
1078
+ }
1073
1079
  }
1074
- return true;
1075
- }
1076
-
1077
- // 2. Search for SCORM APIs
1078
- log('No PlayerIntegration found, searching for SCORM APIs...');
1079
-
1080
- var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);
1081
- if (api2004) {
1082
- LRS.scormApi = api2004.api;
1083
- LRS.scormApiLevel = api2004.level;
1084
- LRS.scormApiWindow = api2004.window;
1085
- LRS.scormApiFound = true;
1086
- LRS.scormApiType = '2004';
1087
- log('Found SCORM 2004 API at level ' + api2004.level);
1080
+ // Don't return early - continue to detect LRS endpoint for direct xAPI
1088
1081
  } else {
1089
- var api12 = findAPIInFrameHierarchy('API', 10);
1090
- if (api12) {
1091
- LRS.scormApi = api12.api;
1092
- LRS.scormApiLevel = api12.level;
1093
- LRS.scormApiWindow = api12.window;
1082
+ // 2. Search for SCORM APIs directly
1083
+ log('No PlayerIntegration found, searching for SCORM APIs...');
1084
+
1085
+ var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);
1086
+ if (api2004) {
1087
+ LRS.scormApi = api2004.api;
1088
+ LRS.scormApiLevel = api2004.level;
1089
+ LRS.scormApiWindow = api2004.window;
1094
1090
  LRS.scormApiFound = true;
1095
- LRS.scormApiType = '1.2';
1096
- log('Found SCORM 1.2 API at level ' + api12.level);
1091
+ LRS.scormApiType = '2004';
1092
+ log('Found SCORM 2004 API at level ' + api2004.level);
1093
+ } else {
1094
+ var api12 = findAPIInFrameHierarchy('API', 10);
1095
+ if (api12) {
1096
+ LRS.scormApi = api12.api;
1097
+ LRS.scormApiLevel = api12.level;
1098
+ LRS.scormApiWindow = api12.window;
1099
+ LRS.scormApiFound = true;
1100
+ LRS.scormApiType = '1.2';
1101
+ log('Found SCORM 1.2 API at level ' + api12.level);
1102
+ }
1097
1103
  }
1098
1104
  }
1099
1105
 
1100
- // 3. Determine LRS endpoint
1106
+ // 3. ALWAYS try to determine LRS endpoint for direct xAPI statements
1107
+ // This is critical for detailed tracking (media, interactions, quizzes)
1101
1108
  // Priority: 1) Configured endpoint, 2) Auto-detected Bravais endpoint
1102
1109
  if (LRS_ENDPOINT_CONFIG && LRS_ENDPOINT_CONFIG.length > 0) {
1103
1110
  LRS_ENDPOINT = LRS_ENDPOINT_CONFIG;
@@ -1113,9 +1120,17 @@ function generateLrsBridgeCode(options) {
1113
1120
  LRS.lrsEndpoint = LRS_ENDPOINT;
1114
1121
 
1115
1122
  // 4. Determine mode based on what we found
1123
+ // Prefer directLRS when available for detailed tracking
1116
1124
  if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {
1117
1125
  LRS.mode = 'directLRS';
1118
1126
  log('Using direct LRS mode with endpoint:', LRS_ENDPOINT);
1127
+ if (LRS.integration) {
1128
+ log('PlayerIntegration also available for SCORM state');
1129
+ }
1130
+ return true;
1131
+ } else if (LRS.integration) {
1132
+ LRS.mode = 'playerIntegration';
1133
+ log('Using PlayerIntegration mode (no direct LRS endpoint)');
1119
1134
  return true;
1120
1135
  } else if (LRS.scormApiFound) {
1121
1136
  LRS.mode = 'scormOnly';
@@ -2334,6 +2349,154 @@ function generateLrsBridgeCode(options) {
2334
2349
  return info;
2335
2350
  }
2336
2351
 
2352
+ // ========================================================================
2353
+ // 4b. LESSON/SECTION NAME EXTRACTION
2354
+ // ========================================================================
2355
+
2356
+ /**
2357
+ * Get the current lesson/section information from Rise DOM
2358
+ * Returns { id, name, lessonIndex, sectionName }
2359
+ */
2360
+ function getCurrentLessonInfo() {
2361
+ var lessonInfo = {
2362
+ id: window.location.hash || '#/',
2363
+ name: null,
2364
+ lessonIndex: null,
2365
+ sectionName: null
2366
+ };
2367
+
2368
+ try {
2369
+ // 1. Try to get from Rise's navigation sidebar
2370
+ // Rise shows active lesson with specific classes
2371
+ var activeLesson = document.querySelector(
2372
+ '.sidebar__link--current, ' +
2373
+ '.sidebar-lesson--active, ' +
2374
+ '.nav-sidebar__lesson--active, ' +
2375
+ '[class*="sidebar"] [class*="current"], ' +
2376
+ '[class*="sidebar"] [class*="active"], ' +
2377
+ '[aria-current="page"], ' +
2378
+ '[aria-current="true"]'
2379
+ );
2380
+
2381
+ if (activeLesson) {
2382
+ var lessonTitle = activeLesson.querySelector(
2383
+ '.sidebar__link-text, ' +
2384
+ '.lesson-title, ' +
2385
+ '.sidebar-lesson__title, ' +
2386
+ '[class*="title"], ' +
2387
+ '[class*="label"]'
2388
+ );
2389
+ if (lessonTitle) {
2390
+ lessonInfo.name = lessonTitle.textContent.trim();
2391
+ } else {
2392
+ lessonInfo.name = activeLesson.textContent.trim();
2393
+ }
2394
+
2395
+ // Try to get the section/module name (parent group)
2396
+ var section = activeLesson.closest(
2397
+ '.sidebar__section, ' +
2398
+ '.sidebar-section, ' +
2399
+ '[class*="section"], ' +
2400
+ '[class*="module"]'
2401
+ );
2402
+ if (section) {
2403
+ var sectionTitle = section.querySelector(
2404
+ '.sidebar__section-title, ' +
2405
+ '.section-title, ' +
2406
+ '[class*="section-title"], ' +
2407
+ '[class*="module-title"], ' +
2408
+ 'h2, h3'
2409
+ );
2410
+ if (sectionTitle) {
2411
+ lessonInfo.sectionName = sectionTitle.textContent.trim();
2412
+ }
2413
+ }
2414
+ }
2415
+
2416
+ // 2. Try Rise's header/breadcrumb
2417
+ if (!lessonInfo.name) {
2418
+ var headerTitle = document.querySelector(
2419
+ '.lesson-header__title, ' +
2420
+ '.lesson__title, ' +
2421
+ '.content-header__title, ' +
2422
+ '[class*="lesson"] [class*="header"] [class*="title"], ' +
2423
+ '.blocks-lesson-header__title'
2424
+ );
2425
+ if (headerTitle) {
2426
+ lessonInfo.name = headerTitle.textContent.trim();
2427
+ }
2428
+ }
2429
+
2430
+ // 3. Try breadcrumbs
2431
+ if (!lessonInfo.name || !lessonInfo.sectionName) {
2432
+ var breadcrumbs = document.querySelectorAll(
2433
+ '.breadcrumb__item, ' +
2434
+ '.breadcrumbs li, ' +
2435
+ '[class*="breadcrumb"] span, ' +
2436
+ '[class*="breadcrumb"] a'
2437
+ );
2438
+ if (breadcrumbs.length >= 2) {
2439
+ // Usually: Course > Section > Lesson
2440
+ if (!lessonInfo.sectionName && breadcrumbs.length >= 2) {
2441
+ lessonInfo.sectionName = breadcrumbs[breadcrumbs.length - 2].textContent.trim();
2442
+ }
2443
+ if (!lessonInfo.name) {
2444
+ lessonInfo.name = breadcrumbs[breadcrumbs.length - 1].textContent.trim();
2445
+ }
2446
+ }
2447
+ }
2448
+
2449
+ // 4. Try Rise's internal data
2450
+ if (window.__RISE_COURSE_DATA__ && window.__RISE_COURSE_DATA__.lessons) {
2451
+ var lessonId = lessonInfo.id.replace('#/lessons/', '').replace('#/', '');
2452
+ var lessons = window.__RISE_COURSE_DATA__.lessons;
2453
+ for (var i = 0; i < lessons.length; i++) {
2454
+ if (lessons[i].id === lessonId || lessons[i].slug === lessonId) {
2455
+ lessonInfo.name = lessons[i].title || lessonInfo.name;
2456
+ lessonInfo.lessonIndex = i;
2457
+ break;
2458
+ }
2459
+ }
2460
+ }
2461
+
2462
+ // 5. Clean up - remove any "Home" or generic names if we're on a real lesson
2463
+ if (lessonInfo.name) {
2464
+ lessonInfo.name = lessonInfo.name.replace(/^\\d+\\.\\s*/, ''); // Remove leading numbers "1. "
2465
+ if (lessonInfo.name.length > 200) {
2466
+ lessonInfo.name = lessonInfo.name.substring(0, 200) + '...';
2467
+ }
2468
+ }
2469
+
2470
+ if (lessonInfo.sectionName) {
2471
+ lessonInfo.sectionName = lessonInfo.sectionName.replace(/^\\d+\\.\\s*/, '');
2472
+ if (lessonInfo.sectionName.length > 200) {
2473
+ lessonInfo.sectionName = lessonInfo.sectionName.substring(0, 200) + '...';
2474
+ }
2475
+ }
2476
+
2477
+ } catch (e) {
2478
+ warn('Error extracting lesson info:', e);
2479
+ }
2480
+
2481
+ log('Current lesson info:', lessonInfo);
2482
+ return lessonInfo;
2483
+ }
2484
+
2485
+ /**
2486
+ * Cache the current lesson info (updates on navigation)
2487
+ */
2488
+ var cachedLessonInfo = null;
2489
+ var cachedLessonHash = null;
2490
+
2491
+ function getCachedLessonInfo() {
2492
+ var currentHash = window.location.hash;
2493
+ if (cachedLessonHash !== currentHash) {
2494
+ cachedLessonInfo = getCurrentLessonInfo();
2495
+ cachedLessonHash = currentHash;
2496
+ }
2497
+ return cachedLessonInfo || getCurrentLessonInfo();
2498
+ }
2499
+
2337
2500
  // ========================================================================
2338
2501
  // 5. XAPI STATEMENT BUILDING
2339
2502
  // ========================================================================
@@ -2678,14 +2841,20 @@ function generateLrsBridgeCode(options) {
2678
2841
  LRS.eventLog.push({
2679
2842
  statement: statement,
2680
2843
  timestamp: new Date().toISOString(),
2681
- mode: LRS.mode
2844
+ mode: LRS.mode,
2845
+ lrsEndpoint: LRS_ENDPOINT
2682
2846
  });
2683
2847
 
2684
- // Route based on mode
2685
- if (LRS.mode === 'playerIntegration' && LRS.integration) {
2686
- return sendViaPlayerIntegration(statement);
2687
- } else if (LRS.mode === 'directLRS' && LRS_ENDPOINT) {
2848
+ // ALWAYS prefer direct LRS when endpoint is available
2849
+ // This ensures all our detailed tracking (media, interactions, etc.) goes to the LRS
2850
+ // PlayerIntegration is used only for SCORM state, not for xAPI statements
2851
+ if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {
2852
+ log('Sending via directLRS:', LRS_ENDPOINT);
2688
2853
  return sendViaDirectLRS(statement);
2854
+ } else if (LRS.mode === 'playerIntegration' && LRS.integration) {
2855
+ // Fallback to PlayerIntegration only if no direct LRS endpoint
2856
+ log('No LRS endpoint, falling back to PlayerIntegration');
2857
+ return sendViaPlayerIntegration(statement);
2689
2858
  } else {
2690
2859
  // SCORM-only or offline mode - just log
2691
2860
  log('Statement logged (no LRS):', statement.verb.display['en-US']);
@@ -2824,13 +2993,16 @@ function generateLrsBridgeCode(options) {
2824
2993
  LRS.contentOpened = function(data) {
2825
2994
  if (!TRACK_NAVIGATION) return;
2826
2995
 
2996
+ // Get current lesson info from DOM (includes name)
2997
+ var lessonInfo = getCurrentLessonInfo();
2998
+
2827
2999
  var result = null;
2828
3000
  if (data.duration) {
2829
3001
  result = { duration: data.duration };
2830
3002
  }
2831
3003
 
2832
3004
  // Extract clean page/lesson ID from Rise hash paths
2833
- var lessonId = data.pageGuid || data.lessonId || '';
3005
+ var lessonId = data.pageGuid || data.lessonId || lessonInfo.id || '';
2834
3006
  if (lessonId.indexOf('#/lessons/') === 0) {
2835
3007
  lessonId = lessonId.substring(10); // Remove '#/lessons/'
2836
3008
  } else if (lessonId.indexOf('#/') === 0) {
@@ -2841,7 +3013,9 @@ function generateLrsBridgeCode(options) {
2841
3013
  var additionalContext = {
2842
3014
  extensions: {
2843
3015
  pageId: lessonId || 'home',
2844
- pageTitle: data.title || 'Page',
3016
+ pageTitle: data.title || lessonInfo.name || 'Page',
3017
+ lessonName: lessonInfo.name,
3018
+ sectionName: lessonInfo.sectionName,
2845
3019
  pageUrl: data.url || window.location.href
2846
3020
  }
2847
3021
  };
@@ -2851,7 +3025,12 @@ function generateLrsBridgeCode(options) {
2851
3025
  var statement = buildStatement(
2852
3026
  'experienced',
2853
3027
  'http://xyleme.com/bravais/activities/document',
2854
- { event: 'page_viewed', pageId: lessonId },
3028
+ {
3029
+ event: 'page_viewed',
3030
+ pageId: lessonId,
3031
+ lessonName: lessonInfo.name,
3032
+ sectionName: lessonInfo.sectionName
3033
+ },
2855
3034
  result,
2856
3035
  additionalContext
2857
3036
  );
@@ -2862,6 +3041,9 @@ function generateLrsBridgeCode(options) {
2862
3041
  LRS.mediaPlayed = function(data) {
2863
3042
  if (!TRACK_MEDIA) return;
2864
3043
 
3044
+ // Get current lesson context
3045
+ var lessonInfo = getCachedLessonInfo();
3046
+
2865
3047
  var mediaType = data.type === 'audio' ? ACTIVITY_TYPES.audio : ACTIVITY_TYPES.video;
2866
3048
  var verbKey = data.action === 'play' ? 'played' :
2867
3049
  data.action === 'pause' ? 'paused' :
@@ -2872,7 +3054,11 @@ function generateLrsBridgeCode(options) {
2872
3054
  mediaSrc: data.src || 'media-' + Date.now(),
2873
3055
  mediaName: data.name || (data.type || 'Media'),
2874
3056
  mediaType: data.type || 'video',
2875
- 'https://w3id.org/xapi/video/extensions/length': data.duration || 0
3057
+ 'https://w3id.org/xapi/video/extensions/length': data.duration || 0,
3058
+ // Include lesson/section context
3059
+ lessonId: lessonInfo.id,
3060
+ lessonName: lessonInfo.name,
3061
+ sectionName: lessonInfo.sectionName
2876
3062
  };
2877
3063
 
2878
3064
  var result = {
@@ -2895,12 +3081,21 @@ function generateLrsBridgeCode(options) {
2895
3081
  LRS.assessmentStarted = function(data) {
2896
3082
  if (!TRACK_QUIZZES) return;
2897
3083
 
3084
+ // Get lesson context
3085
+ var lessonInfo = getCachedLessonInfo();
3086
+
2898
3087
  // Build assessment activity object for Xyleme format
2899
3088
  var assessmentObject = buildAssessmentActivityObject({
2900
3089
  assessmentGuid: data.assessmentGuid || data.id,
2901
- title: data.title || 'Assessment'
3090
+ title: data.title || data.assessmentName || 'Assessment'
2902
3091
  });
2903
3092
 
3093
+ // Add lesson context to extensions
3094
+ assessmentObject.definition = assessmentObject.definition || {};
3095
+ assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};
3096
+ assessmentObject.definition.extensions.lessonName = lessonInfo.name;
3097
+ assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;
3098
+
2904
3099
  var statement = buildStatementXyleme('attempted', assessmentObject);
2905
3100
  sendStatement(statement);
2906
3101
  };
@@ -2909,13 +3104,31 @@ function generateLrsBridgeCode(options) {
2909
3104
  LRS.assessmentEnded = function(data) {
2910
3105
  if (!TRACK_QUIZZES) return;
2911
3106
 
3107
+ // Get lesson context (may be passed in from extractQuizResult)
3108
+ var lessonInfo = data.lessonName ? { name: data.lessonName, sectionName: data.sectionName } : getCachedLessonInfo();
3109
+
2912
3110
  // Build assessment activity object for Xyleme format
2913
3111
  var assessmentObject = buildAssessmentActivityObject({
2914
- assessmentGuid: data.assessmentGuid || data.id,
2915
- title: data.title || 'Assessment'
3112
+ assessmentGuid: data.assessmentGuid || data.assessmentId || data.id,
3113
+ title: data.title || data.assessmentName || 'Assessment'
2916
3114
  });
2917
3115
 
2918
- var result = { completion: true };
3116
+ // Add lesson context to extensions
3117
+ assessmentObject.definition = assessmentObject.definition || {};
3118
+ assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};
3119
+ assessmentObject.definition.extensions.lessonName = lessonInfo.name;
3120
+ assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;
3121
+ assessmentObject.definition.extensions.assessmentName = data.assessmentName || data.title;
3122
+
3123
+ var result = {
3124
+ completion: true,
3125
+ extensions: {
3126
+ 'https://patch-adams.io/xapi/extensions/assessmentName': data.assessmentName || data.title || null,
3127
+ 'https://patch-adams.io/xapi/extensions/lessonName': lessonInfo.name || null,
3128
+ 'https://patch-adams.io/xapi/extensions/sectionName': lessonInfo.sectionName || null,
3129
+ 'https://patch-adams.io/xapi/extensions/questionCount': data.questionCount || null
3130
+ }
3131
+ };
2919
3132
 
2920
3133
  if (typeof data.score === 'number') {
2921
3134
  result.score = {
@@ -2944,14 +3157,27 @@ function generateLrsBridgeCode(options) {
2944
3157
  LRS.questionAnswered = function(data) {
2945
3158
  if (!TRACK_QUIZZES) return;
2946
3159
 
3160
+ // Get lesson context if not provided
3161
+ var lessonInfo = data.lessonName ? data : getCachedLessonInfo();
3162
+
2947
3163
  // Build question activity object for Xyleme format
2948
3164
  var questionObject = buildQuestionActivityObject({
2949
3165
  questionGuid: data.questionGuid || data.questionId,
2950
3166
  text: data.questionText || 'Question'
2951
3167
  });
2952
3168
 
3169
+ // Build comprehensive result with human-readable data
2953
3170
  var result = {
2954
- response: data.answer || data.response || ''
3171
+ response: data.answer || data.response || '',
3172
+ extensions: {
3173
+ 'https://patch-adams.io/xapi/extensions/questionNumber': data.questionNumber || null,
3174
+ 'https://patch-adams.io/xapi/extensions/questionText': data.questionText || null,
3175
+ 'https://patch-adams.io/xapi/extensions/answerText': data.answer || data.response || null,
3176
+ 'https://patch-adams.io/xapi/extensions/correctAnswer': data.correctAnswer || null,
3177
+ 'https://patch-adams.io/xapi/extensions/assessmentName': data.assessmentName || null,
3178
+ 'https://patch-adams.io/xapi/extensions/lessonName': lessonInfo.lessonName || lessonInfo.name || null,
3179
+ 'https://patch-adams.io/xapi/extensions/sectionName': lessonInfo.sectionName || null
3180
+ }
2955
3181
  };
2956
3182
 
2957
3183
  if (data.result === 'correct' || data.correct === true) {
@@ -2983,6 +3209,9 @@ function generateLrsBridgeCode(options) {
2983
3209
  LRS.interacted = function(data) {
2984
3210
  if (!TRACK_INTERACTIONS) return;
2985
3211
 
3212
+ // Get current lesson context
3213
+ var lessonInfo = getCachedLessonInfo();
3214
+
2986
3215
  // Activity type is interaction, details include interaction ID and type
2987
3216
  var statement = buildStatement(
2988
3217
  'interacted',
@@ -2990,7 +3219,11 @@ function generateLrsBridgeCode(options) {
2990
3219
  {
2991
3220
  interactionId: data.id || 'interaction-' + Date.now(),
2992
3221
  interactionName: data.name || data.type || 'Interaction',
2993
- interactionType: data.interactionType || data.type || 'other'
3222
+ interactionType: data.interactionType || data.type || 'other',
3223
+ // Include lesson/section context
3224
+ lessonId: lessonInfo.id,
3225
+ lessonName: lessonInfo.name,
3226
+ sectionName: lessonInfo.sectionName
2994
3227
  }
2995
3228
  );
2996
3229
  sendStatement(statement);
@@ -3291,43 +3524,152 @@ function generateLrsBridgeCode(options) {
3291
3524
  'quiz-' + Date.now();
3292
3525
  }
3293
3526
 
3527
+ /**
3528
+ * Extract quiz/assessment name from the DOM
3529
+ */
3530
+ function extractAssessmentName(node) {
3531
+ // Try various selectors for assessment/quiz titles
3532
+ var titleEl = node.querySelector(
3533
+ '.quiz-title, .assessment-title, .knowledge-check-title, ' +
3534
+ '.blocks-quiz__title, .block-title, ' +
3535
+ '[class*="quiz"] [class*="title"], ' +
3536
+ '[class*="assessment"] [class*="title"], ' +
3537
+ 'h2, h3'
3538
+ );
3539
+ if (titleEl) {
3540
+ return titleEl.textContent.trim().substring(0, 200);
3541
+ }
3542
+
3543
+ // Try to get from parent block
3544
+ var parentBlock = node.closest('[data-block-type]');
3545
+ if (parentBlock) {
3546
+ var blockTitle = parentBlock.querySelector('.block-title, h2, h3');
3547
+ if (blockTitle) {
3548
+ return blockTitle.textContent.trim().substring(0, 200);
3549
+ }
3550
+ }
3551
+
3552
+ // Get current lesson name as context
3553
+ var lessonInfo = getCachedLessonInfo();
3554
+ if (lessonInfo.name) {
3555
+ return 'Quiz in ' + lessonInfo.name;
3556
+ }
3557
+
3558
+ return 'Knowledge Check';
3559
+ }
3560
+
3294
3561
  function extractQuizResult(node) {
3295
3562
  var scoreEl = node.querySelector('.score-value, .quiz-score, .score-percentage, [class*="score"]');
3296
3563
  var score = scoreEl ? parseFloat(scoreEl.textContent.replace(/[^0-9.]/g, '')) : null;
3297
3564
 
3298
- var questions = node.querySelectorAll('.question-result, .question-feedback, .question-item, [class*="question"]');
3565
+ // Get assessment name and lesson context
3566
+ var assessmentName = extractAssessmentName(node);
3567
+ var lessonInfo = getCachedLessonInfo();
3568
+
3569
+ // Find all question containers - Rise uses various structures
3570
+ var questions = node.querySelectorAll(
3571
+ '.question-result, .question-feedback, .question-item, ' +
3572
+ '.blocks-quiz__question, .quiz-question, ' +
3573
+ '[class*="question-"][class*="result"], ' +
3574
+ '[class*="question-"][class*="feedback"], ' +
3575
+ '[data-question-id]'
3576
+ );
3577
+
3578
+ log('Found', questions.length, 'question elements in quiz result');
3299
3579
 
3300
3580
  if (questions.length > 0) {
3301
3581
  questions.forEach(function(q, index) {
3582
+ // Determine if correct - check multiple indicators
3302
3583
  var isCorrect = q.classList.contains('correct') ||
3303
- q.querySelector('.correct, [class*="correct"]') !== null ||
3304
- q.getAttribute('data-correct') === 'true';
3584
+ q.classList.contains('is-correct') ||
3585
+ q.querySelector('.correct, .is-correct, [class*="correct"]') !== null ||
3586
+ q.getAttribute('data-correct') === 'true' ||
3587
+ q.getAttribute('data-result') === 'correct';
3305
3588
 
3589
+ // Get the question text - try multiple selectors
3306
3590
  var questionText = '';
3307
- var questionEl = q.querySelector('.question-text, .question-stem, .question-title, [class*="question-text"]');
3591
+ var questionEl = q.querySelector(
3592
+ '.question-text, .question-stem, .question-title, ' +
3593
+ '.blocks-quiz__question-text, .quiz-question__text, ' +
3594
+ '[class*="question-text"], [class*="question-stem"], ' +
3595
+ '[class*="prompt"], p:first-of-type'
3596
+ );
3308
3597
  if (questionEl) {
3309
3598
  questionText = questionEl.textContent.trim();
3310
3599
  }
3311
3600
 
3601
+ // Get the learner's selected answer text
3312
3602
  var answerText = '';
3313
- var answerEl = q.querySelector('.selected-answer, .learner-response, .answer-text, [class*="response"]');
3603
+ var answerEl = q.querySelector(
3604
+ '.selected-answer, .learner-response, .answer-text, ' +
3605
+ '.user-answer, .chosen-answer, .response-text, ' +
3606
+ '.blocks-quiz__response, .quiz-question__response, ' +
3607
+ '[class*="selected"], [class*="chosen"], [class*="response"], ' +
3608
+ '[class*="user-answer"]'
3609
+ );
3314
3610
  if (answerEl) {
3315
3611
  answerText = answerEl.textContent.trim();
3316
3612
  }
3317
3613
 
3614
+ // If no specific answer element, try to find selected radio/checkbox label
3615
+ if (!answerText) {
3616
+ var selectedInput = q.querySelector('input:checked, [aria-checked="true"]');
3617
+ if (selectedInput) {
3618
+ var label = q.querySelector('label[for="' + selectedInput.id + '"]');
3619
+ if (label) {
3620
+ answerText = label.textContent.trim();
3621
+ } else {
3622
+ var parentLabel = selectedInput.closest('label');
3623
+ if (parentLabel) {
3624
+ answerText = parentLabel.textContent.trim();
3625
+ }
3626
+ }
3627
+ }
3628
+ }
3629
+
3630
+ // Get question ID if available
3631
+ var questionId = q.getAttribute('data-question-id') ||
3632
+ q.getAttribute('data-block-id') ||
3633
+ q.getAttribute('id') ||
3634
+ 'q' + (index + 1);
3635
+
3636
+ // Get correct answer text if available (for reporting)
3637
+ var correctAnswerText = '';
3638
+ var correctEl = q.querySelector(
3639
+ '.correct-answer, .right-answer, ' +
3640
+ '[class*="correct-answer"], [class*="right-answer"]'
3641
+ );
3642
+ if (correctEl) {
3643
+ correctAnswerText = correctEl.textContent.trim();
3644
+ }
3645
+
3646
+ log('Question', index + 1, ':', {
3647
+ questionText: questionText.substring(0, 50) + '...',
3648
+ answerText: answerText.substring(0, 50) + '...',
3649
+ isCorrect: isCorrect
3650
+ });
3651
+
3318
3652
  LRS.questionAnswered({
3319
- questionId: 'q' + (index + 1),
3320
- questionText: questionText.substring(0, 200),
3321
- answer: answerText.substring(0, 200),
3322
- result: isCorrect ? 'correct' : 'incorrect'
3653
+ questionId: questionId,
3654
+ questionNumber: index + 1,
3655
+ questionText: questionText.substring(0, 500),
3656
+ answer: answerText.substring(0, 500),
3657
+ correctAnswer: correctAnswerText.substring(0, 500),
3658
+ result: isCorrect ? 'correct' : 'incorrect',
3659
+ assessmentName: assessmentName,
3660
+ lessonName: lessonInfo.name,
3661
+ sectionName: lessonInfo.sectionName
3323
3662
  });
3324
3663
  });
3325
3664
  }
3326
3665
 
3327
3666
  LRS.assessmentEnded({
3328
3667
  assessmentId: extractAssessmentId(node),
3668
+ assessmentName: assessmentName,
3329
3669
  score: score,
3330
- questionCount: questions.length
3670
+ questionCount: questions.length,
3671
+ lessonName: lessonInfo.name,
3672
+ sectionName: lessonInfo.sectionName
3331
3673
  });
3332
3674
  }
3333
3675
 
@@ -3451,7 +3793,7 @@ function generateLrsBridgeCode(options) {
3451
3793
  // ========================================================================
3452
3794
 
3453
3795
  function init() {
3454
- log('Initializing LRS bridge v2.3.0...');
3796
+ log('Initializing LRS bridge v2.5.0...');
3455
3797
 
3456
3798
  // Extract course info early
3457
3799
  extractCourseInfo();