@patch-adams/core 1.4.3 → 1.4.6

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
@@ -1047,48 +1047,55 @@ function generateLrsBridgeCode(options) {
1047
1047
 
1048
1048
  function setupBridge() {
1049
1049
  try {
1050
- // 1. Try PlayerIntegration first (Xyleme native mode)
1050
+ // 1. Try PlayerIntegration first (Xyleme native mode) - for SCORM APIs
1051
1051
  var integrationResult = findPlayerIntegration(10);
1052
1052
  if (integrationResult) {
1053
1053
  LRS.integration = integrationResult.integration;
1054
1054
  LRS.integrationLevel = integrationResult.level;
1055
- LRS.mode = 'playerIntegration';
1056
- log('Using PlayerIntegration mode');
1055
+ log('Found PlayerIntegration at level', integrationResult.level);
1057
1056
 
1058
1057
  if (LRS.integration.Internal_SCORM_2004_API) {
1059
1058
  window.API_1484_11 = LRS.integration.Internal_SCORM_2004_API;
1060
1059
  LRS.scormApi = window.API_1484_11;
1060
+ LRS.scormApiFound = true;
1061
+ LRS.scormApiType = '2004';
1061
1062
  }
1062
1063
  if (LRS.integration.Internal_SCORM_12_API) {
1063
1064
  window.API = LRS.integration.Internal_SCORM_12_API;
1065
+ if (!LRS.scormApiFound) {
1066
+ LRS.scormApi = window.API;
1067
+ LRS.scormApiFound = true;
1068
+ LRS.scormApiType = '1.2';
1069
+ }
1064
1070
  }
1065
- return true;
1066
- }
1067
-
1068
- // 2. Search for SCORM APIs
1069
- log('No PlayerIntegration found, searching for SCORM APIs...');
1070
-
1071
- var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);
1072
- if (api2004) {
1073
- LRS.scormApi = api2004.api;
1074
- LRS.scormApiLevel = api2004.level;
1075
- LRS.scormApiWindow = api2004.window;
1076
- LRS.scormApiFound = true;
1077
- LRS.scormApiType = '2004';
1078
- log('Found SCORM 2004 API at level ' + api2004.level);
1071
+ // Don't return early - continue to detect LRS endpoint for direct xAPI
1079
1072
  } else {
1080
- var api12 = findAPIInFrameHierarchy('API', 10);
1081
- if (api12) {
1082
- LRS.scormApi = api12.api;
1083
- LRS.scormApiLevel = api12.level;
1084
- LRS.scormApiWindow = api12.window;
1073
+ // 2. Search for SCORM APIs directly
1074
+ log('No PlayerIntegration found, searching for SCORM APIs...');
1075
+
1076
+ var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);
1077
+ if (api2004) {
1078
+ LRS.scormApi = api2004.api;
1079
+ LRS.scormApiLevel = api2004.level;
1080
+ LRS.scormApiWindow = api2004.window;
1085
1081
  LRS.scormApiFound = true;
1086
- LRS.scormApiType = '1.2';
1087
- log('Found SCORM 1.2 API at level ' + api12.level);
1082
+ LRS.scormApiType = '2004';
1083
+ log('Found SCORM 2004 API at level ' + api2004.level);
1084
+ } else {
1085
+ var api12 = findAPIInFrameHierarchy('API', 10);
1086
+ if (api12) {
1087
+ LRS.scormApi = api12.api;
1088
+ LRS.scormApiLevel = api12.level;
1089
+ LRS.scormApiWindow = api12.window;
1090
+ LRS.scormApiFound = true;
1091
+ LRS.scormApiType = '1.2';
1092
+ log('Found SCORM 1.2 API at level ' + api12.level);
1093
+ }
1088
1094
  }
1089
1095
  }
1090
1096
 
1091
- // 3. Determine LRS endpoint
1097
+ // 3. ALWAYS try to determine LRS endpoint for direct xAPI statements
1098
+ // This is critical for detailed tracking (media, interactions, quizzes)
1092
1099
  // Priority: 1) Configured endpoint, 2) Auto-detected Bravais endpoint
1093
1100
  if (LRS_ENDPOINT_CONFIG && LRS_ENDPOINT_CONFIG.length > 0) {
1094
1101
  LRS_ENDPOINT = LRS_ENDPOINT_CONFIG;
@@ -1104,9 +1111,17 @@ function generateLrsBridgeCode(options) {
1104
1111
  LRS.lrsEndpoint = LRS_ENDPOINT;
1105
1112
 
1106
1113
  // 4. Determine mode based on what we found
1114
+ // Prefer directLRS when available for detailed tracking
1107
1115
  if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {
1108
1116
  LRS.mode = 'directLRS';
1109
1117
  log('Using direct LRS mode with endpoint:', LRS_ENDPOINT);
1118
+ if (LRS.integration) {
1119
+ log('PlayerIntegration also available for SCORM state');
1120
+ }
1121
+ return true;
1122
+ } else if (LRS.integration) {
1123
+ LRS.mode = 'playerIntegration';
1124
+ log('Using PlayerIntegration mode (no direct LRS endpoint)');
1110
1125
  return true;
1111
1126
  } else if (LRS.scormApiFound) {
1112
1127
  LRS.mode = 'scormOnly';
@@ -1921,7 +1936,7 @@ function generateLrsBridgeCode(options) {
1921
1936
  } catch (e) {}
1922
1937
 
1923
1938
  for (var j = 0; j < urlsToCheck.length; j++) {
1924
- var match = urlsToCheck[j].match(/(https?://core-[a-zA-Z0-9_-]+.bravais.com)/);
1939
+ var match = urlsToCheck[j].match(/(https?:\\/\\/core-[a-zA-Z0-9_-]+\\.bravais\\.com)/);
1925
1940
  if (match) {
1926
1941
  return match[1];
1927
1942
  }
@@ -2325,6 +2340,154 @@ function generateLrsBridgeCode(options) {
2325
2340
  return info;
2326
2341
  }
2327
2342
 
2343
+ // ========================================================================
2344
+ // 4b. LESSON/SECTION NAME EXTRACTION
2345
+ // ========================================================================
2346
+
2347
+ /**
2348
+ * Get the current lesson/section information from Rise DOM
2349
+ * Returns { id, name, lessonIndex, sectionName }
2350
+ */
2351
+ function getCurrentLessonInfo() {
2352
+ var lessonInfo = {
2353
+ id: window.location.hash || '#/',
2354
+ name: null,
2355
+ lessonIndex: null,
2356
+ sectionName: null
2357
+ };
2358
+
2359
+ try {
2360
+ // 1. Try to get from Rise's navigation sidebar
2361
+ // Rise shows active lesson with specific classes
2362
+ var activeLesson = document.querySelector(
2363
+ '.sidebar__link--current, ' +
2364
+ '.sidebar-lesson--active, ' +
2365
+ '.nav-sidebar__lesson--active, ' +
2366
+ '[class*="sidebar"] [class*="current"], ' +
2367
+ '[class*="sidebar"] [class*="active"], ' +
2368
+ '[aria-current="page"], ' +
2369
+ '[aria-current="true"]'
2370
+ );
2371
+
2372
+ if (activeLesson) {
2373
+ var lessonTitle = activeLesson.querySelector(
2374
+ '.sidebar__link-text, ' +
2375
+ '.lesson-title, ' +
2376
+ '.sidebar-lesson__title, ' +
2377
+ '[class*="title"], ' +
2378
+ '[class*="label"]'
2379
+ );
2380
+ if (lessonTitle) {
2381
+ lessonInfo.name = lessonTitle.textContent.trim();
2382
+ } else {
2383
+ lessonInfo.name = activeLesson.textContent.trim();
2384
+ }
2385
+
2386
+ // Try to get the section/module name (parent group)
2387
+ var section = activeLesson.closest(
2388
+ '.sidebar__section, ' +
2389
+ '.sidebar-section, ' +
2390
+ '[class*="section"], ' +
2391
+ '[class*="module"]'
2392
+ );
2393
+ if (section) {
2394
+ var sectionTitle = section.querySelector(
2395
+ '.sidebar__section-title, ' +
2396
+ '.section-title, ' +
2397
+ '[class*="section-title"], ' +
2398
+ '[class*="module-title"], ' +
2399
+ 'h2, h3'
2400
+ );
2401
+ if (sectionTitle) {
2402
+ lessonInfo.sectionName = sectionTitle.textContent.trim();
2403
+ }
2404
+ }
2405
+ }
2406
+
2407
+ // 2. Try Rise's header/breadcrumb
2408
+ if (!lessonInfo.name) {
2409
+ var headerTitle = document.querySelector(
2410
+ '.lesson-header__title, ' +
2411
+ '.lesson__title, ' +
2412
+ '.content-header__title, ' +
2413
+ '[class*="lesson"] [class*="header"] [class*="title"], ' +
2414
+ '.blocks-lesson-header__title'
2415
+ );
2416
+ if (headerTitle) {
2417
+ lessonInfo.name = headerTitle.textContent.trim();
2418
+ }
2419
+ }
2420
+
2421
+ // 3. Try breadcrumbs
2422
+ if (!lessonInfo.name || !lessonInfo.sectionName) {
2423
+ var breadcrumbs = document.querySelectorAll(
2424
+ '.breadcrumb__item, ' +
2425
+ '.breadcrumbs li, ' +
2426
+ '[class*="breadcrumb"] span, ' +
2427
+ '[class*="breadcrumb"] a'
2428
+ );
2429
+ if (breadcrumbs.length >= 2) {
2430
+ // Usually: Course > Section > Lesson
2431
+ if (!lessonInfo.sectionName && breadcrumbs.length >= 2) {
2432
+ lessonInfo.sectionName = breadcrumbs[breadcrumbs.length - 2].textContent.trim();
2433
+ }
2434
+ if (!lessonInfo.name) {
2435
+ lessonInfo.name = breadcrumbs[breadcrumbs.length - 1].textContent.trim();
2436
+ }
2437
+ }
2438
+ }
2439
+
2440
+ // 4. Try Rise's internal data
2441
+ if (window.__RISE_COURSE_DATA__ && window.__RISE_COURSE_DATA__.lessons) {
2442
+ var lessonId = lessonInfo.id.replace('#/lessons/', '').replace('#/', '');
2443
+ var lessons = window.__RISE_COURSE_DATA__.lessons;
2444
+ for (var i = 0; i < lessons.length; i++) {
2445
+ if (lessons[i].id === lessonId || lessons[i].slug === lessonId) {
2446
+ lessonInfo.name = lessons[i].title || lessonInfo.name;
2447
+ lessonInfo.lessonIndex = i;
2448
+ break;
2449
+ }
2450
+ }
2451
+ }
2452
+
2453
+ // 5. Clean up - remove any "Home" or generic names if we're on a real lesson
2454
+ if (lessonInfo.name) {
2455
+ lessonInfo.name = lessonInfo.name.replace(/^\\\\d+\\\\.\\\\s*/, ''); // Remove leading numbers "1. "
2456
+ if (lessonInfo.name.length > 200) {
2457
+ lessonInfo.name = lessonInfo.name.substring(0, 200) + '...';
2458
+ }
2459
+ }
2460
+
2461
+ if (lessonInfo.sectionName) {
2462
+ lessonInfo.sectionName = lessonInfo.sectionName.replace(/^\\\\d+\\\\.\\\\s*/, '');
2463
+ if (lessonInfo.sectionName.length > 200) {
2464
+ lessonInfo.sectionName = lessonInfo.sectionName.substring(0, 200) + '...';
2465
+ }
2466
+ }
2467
+
2468
+ } catch (e) {
2469
+ warn('Error extracting lesson info:', e);
2470
+ }
2471
+
2472
+ log('Current lesson info:', lessonInfo);
2473
+ return lessonInfo;
2474
+ }
2475
+
2476
+ /**
2477
+ * Cache the current lesson info (updates on navigation)
2478
+ */
2479
+ var cachedLessonInfo = null;
2480
+ var cachedLessonHash = null;
2481
+
2482
+ function getCachedLessonInfo() {
2483
+ var currentHash = window.location.hash;
2484
+ if (cachedLessonHash !== currentHash) {
2485
+ cachedLessonInfo = getCurrentLessonInfo();
2486
+ cachedLessonHash = currentHash;
2487
+ }
2488
+ return cachedLessonInfo || getCurrentLessonInfo();
2489
+ }
2490
+
2328
2491
  // ========================================================================
2329
2492
  // 5. XAPI STATEMENT BUILDING
2330
2493
  // ========================================================================
@@ -2669,14 +2832,20 @@ function generateLrsBridgeCode(options) {
2669
2832
  LRS.eventLog.push({
2670
2833
  statement: statement,
2671
2834
  timestamp: new Date().toISOString(),
2672
- mode: LRS.mode
2835
+ mode: LRS.mode,
2836
+ lrsEndpoint: LRS_ENDPOINT
2673
2837
  });
2674
2838
 
2675
- // Route based on mode
2676
- if (LRS.mode === 'playerIntegration' && LRS.integration) {
2677
- return sendViaPlayerIntegration(statement);
2678
- } else if (LRS.mode === 'directLRS' && LRS_ENDPOINT) {
2839
+ // ALWAYS prefer direct LRS when endpoint is available
2840
+ // This ensures all our detailed tracking (media, interactions, etc.) goes to the LRS
2841
+ // PlayerIntegration is used only for SCORM state, not for xAPI statements
2842
+ if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {
2843
+ log('Sending via directLRS:', LRS_ENDPOINT);
2679
2844
  return sendViaDirectLRS(statement);
2845
+ } else if (LRS.mode === 'playerIntegration' && LRS.integration) {
2846
+ // Fallback to PlayerIntegration only if no direct LRS endpoint
2847
+ log('No LRS endpoint, falling back to PlayerIntegration');
2848
+ return sendViaPlayerIntegration(statement);
2680
2849
  } else {
2681
2850
  // SCORM-only or offline mode - just log
2682
2851
  log('Statement logged (no LRS):', statement.verb.display['en-US']);
@@ -2815,13 +2984,16 @@ function generateLrsBridgeCode(options) {
2815
2984
  LRS.contentOpened = function(data) {
2816
2985
  if (!TRACK_NAVIGATION) return;
2817
2986
 
2987
+ // Get current lesson info from DOM (includes name)
2988
+ var lessonInfo = getCurrentLessonInfo();
2989
+
2818
2990
  var result = null;
2819
2991
  if (data.duration) {
2820
2992
  result = { duration: data.duration };
2821
2993
  }
2822
2994
 
2823
2995
  // Extract clean page/lesson ID from Rise hash paths
2824
- var lessonId = data.pageGuid || data.lessonId || '';
2996
+ var lessonId = data.pageGuid || data.lessonId || lessonInfo.id || '';
2825
2997
  if (lessonId.indexOf('#/lessons/') === 0) {
2826
2998
  lessonId = lessonId.substring(10); // Remove '#/lessons/'
2827
2999
  } else if (lessonId.indexOf('#/') === 0) {
@@ -2832,7 +3004,9 @@ function generateLrsBridgeCode(options) {
2832
3004
  var additionalContext = {
2833
3005
  extensions: {
2834
3006
  pageId: lessonId || 'home',
2835
- pageTitle: data.title || 'Page',
3007
+ pageTitle: data.title || lessonInfo.name || 'Page',
3008
+ lessonName: lessonInfo.name,
3009
+ sectionName: lessonInfo.sectionName,
2836
3010
  pageUrl: data.url || window.location.href
2837
3011
  }
2838
3012
  };
@@ -2842,7 +3016,12 @@ function generateLrsBridgeCode(options) {
2842
3016
  var statement = buildStatement(
2843
3017
  'experienced',
2844
3018
  'http://xyleme.com/bravais/activities/document',
2845
- { event: 'page_viewed', pageId: lessonId },
3019
+ {
3020
+ event: 'page_viewed',
3021
+ pageId: lessonId,
3022
+ lessonName: lessonInfo.name,
3023
+ sectionName: lessonInfo.sectionName
3024
+ },
2846
3025
  result,
2847
3026
  additionalContext
2848
3027
  );
@@ -2853,6 +3032,9 @@ function generateLrsBridgeCode(options) {
2853
3032
  LRS.mediaPlayed = function(data) {
2854
3033
  if (!TRACK_MEDIA) return;
2855
3034
 
3035
+ // Get current lesson context
3036
+ var lessonInfo = getCachedLessonInfo();
3037
+
2856
3038
  var mediaType = data.type === 'audio' ? ACTIVITY_TYPES.audio : ACTIVITY_TYPES.video;
2857
3039
  var verbKey = data.action === 'play' ? 'played' :
2858
3040
  data.action === 'pause' ? 'paused' :
@@ -2863,7 +3045,11 @@ function generateLrsBridgeCode(options) {
2863
3045
  mediaSrc: data.src || 'media-' + Date.now(),
2864
3046
  mediaName: data.name || (data.type || 'Media'),
2865
3047
  mediaType: data.type || 'video',
2866
- 'https://w3id.org/xapi/video/extensions/length': data.duration || 0
3048
+ 'https://w3id.org/xapi/video/extensions/length': data.duration || 0,
3049
+ // Include lesson/section context
3050
+ lessonId: lessonInfo.id,
3051
+ lessonName: lessonInfo.name,
3052
+ sectionName: lessonInfo.sectionName
2867
3053
  };
2868
3054
 
2869
3055
  var result = {
@@ -2886,12 +3072,21 @@ function generateLrsBridgeCode(options) {
2886
3072
  LRS.assessmentStarted = function(data) {
2887
3073
  if (!TRACK_QUIZZES) return;
2888
3074
 
3075
+ // Get lesson context
3076
+ var lessonInfo = getCachedLessonInfo();
3077
+
2889
3078
  // Build assessment activity object for Xyleme format
2890
3079
  var assessmentObject = buildAssessmentActivityObject({
2891
3080
  assessmentGuid: data.assessmentGuid || data.id,
2892
- title: data.title || 'Assessment'
3081
+ title: data.title || data.assessmentName || 'Assessment'
2893
3082
  });
2894
3083
 
3084
+ // Add lesson context to extensions
3085
+ assessmentObject.definition = assessmentObject.definition || {};
3086
+ assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};
3087
+ assessmentObject.definition.extensions.lessonName = lessonInfo.name;
3088
+ assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;
3089
+
2895
3090
  var statement = buildStatementXyleme('attempted', assessmentObject);
2896
3091
  sendStatement(statement);
2897
3092
  };
@@ -2900,13 +3095,31 @@ function generateLrsBridgeCode(options) {
2900
3095
  LRS.assessmentEnded = function(data) {
2901
3096
  if (!TRACK_QUIZZES) return;
2902
3097
 
3098
+ // Get lesson context (may be passed in from extractQuizResult)
3099
+ var lessonInfo = data.lessonName ? { name: data.lessonName, sectionName: data.sectionName } : getCachedLessonInfo();
3100
+
2903
3101
  // Build assessment activity object for Xyleme format
2904
3102
  var assessmentObject = buildAssessmentActivityObject({
2905
- assessmentGuid: data.assessmentGuid || data.id,
2906
- title: data.title || 'Assessment'
3103
+ assessmentGuid: data.assessmentGuid || data.assessmentId || data.id,
3104
+ title: data.title || data.assessmentName || 'Assessment'
2907
3105
  });
2908
3106
 
2909
- var result = { completion: true };
3107
+ // Add lesson context to extensions
3108
+ assessmentObject.definition = assessmentObject.definition || {};
3109
+ assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};
3110
+ assessmentObject.definition.extensions.lessonName = lessonInfo.name;
3111
+ assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;
3112
+ assessmentObject.definition.extensions.assessmentName = data.assessmentName || data.title;
3113
+
3114
+ var result = {
3115
+ completion: true,
3116
+ extensions: {
3117
+ 'https://patch-adams.io/xapi/extensions/assessmentName': data.assessmentName || data.title || null,
3118
+ 'https://patch-adams.io/xapi/extensions/lessonName': lessonInfo.name || null,
3119
+ 'https://patch-adams.io/xapi/extensions/sectionName': lessonInfo.sectionName || null,
3120
+ 'https://patch-adams.io/xapi/extensions/questionCount': data.questionCount || null
3121
+ }
3122
+ };
2910
3123
 
2911
3124
  if (typeof data.score === 'number') {
2912
3125
  result.score = {
@@ -2935,14 +3148,27 @@ function generateLrsBridgeCode(options) {
2935
3148
  LRS.questionAnswered = function(data) {
2936
3149
  if (!TRACK_QUIZZES) return;
2937
3150
 
3151
+ // Get lesson context if not provided
3152
+ var lessonInfo = data.lessonName ? data : getCachedLessonInfo();
3153
+
2938
3154
  // Build question activity object for Xyleme format
2939
3155
  var questionObject = buildQuestionActivityObject({
2940
3156
  questionGuid: data.questionGuid || data.questionId,
2941
3157
  text: data.questionText || 'Question'
2942
3158
  });
2943
3159
 
3160
+ // Build comprehensive result with human-readable data
2944
3161
  var result = {
2945
- response: data.answer || data.response || ''
3162
+ response: data.answer || data.response || '',
3163
+ extensions: {
3164
+ 'https://patch-adams.io/xapi/extensions/questionNumber': data.questionNumber || null,
3165
+ 'https://patch-adams.io/xapi/extensions/questionText': data.questionText || null,
3166
+ 'https://patch-adams.io/xapi/extensions/answerText': data.answer || data.response || null,
3167
+ 'https://patch-adams.io/xapi/extensions/correctAnswer': data.correctAnswer || null,
3168
+ 'https://patch-adams.io/xapi/extensions/assessmentName': data.assessmentName || null,
3169
+ 'https://patch-adams.io/xapi/extensions/lessonName': lessonInfo.lessonName || lessonInfo.name || null,
3170
+ 'https://patch-adams.io/xapi/extensions/sectionName': lessonInfo.sectionName || null
3171
+ }
2946
3172
  };
2947
3173
 
2948
3174
  if (data.result === 'correct' || data.correct === true) {
@@ -2974,6 +3200,9 @@ function generateLrsBridgeCode(options) {
2974
3200
  LRS.interacted = function(data) {
2975
3201
  if (!TRACK_INTERACTIONS) return;
2976
3202
 
3203
+ // Get current lesson context
3204
+ var lessonInfo = getCachedLessonInfo();
3205
+
2977
3206
  // Activity type is interaction, details include interaction ID and type
2978
3207
  var statement = buildStatement(
2979
3208
  'interacted',
@@ -2981,7 +3210,11 @@ function generateLrsBridgeCode(options) {
2981
3210
  {
2982
3211
  interactionId: data.id || 'interaction-' + Date.now(),
2983
3212
  interactionName: data.name || data.type || 'Interaction',
2984
- interactionType: data.interactionType || data.type || 'other'
3213
+ interactionType: data.interactionType || data.type || 'other',
3214
+ // Include lesson/section context
3215
+ lessonId: lessonInfo.id,
3216
+ lessonName: lessonInfo.name,
3217
+ sectionName: lessonInfo.sectionName
2985
3218
  }
2986
3219
  );
2987
3220
  sendStatement(statement);
@@ -3282,43 +3515,152 @@ function generateLrsBridgeCode(options) {
3282
3515
  'quiz-' + Date.now();
3283
3516
  }
3284
3517
 
3518
+ /**
3519
+ * Extract quiz/assessment name from the DOM
3520
+ */
3521
+ function extractAssessmentName(node) {
3522
+ // Try various selectors for assessment/quiz titles
3523
+ var titleEl = node.querySelector(
3524
+ '.quiz-title, .assessment-title, .knowledge-check-title, ' +
3525
+ '.blocks-quiz__title, .block-title, ' +
3526
+ '[class*="quiz"] [class*="title"], ' +
3527
+ '[class*="assessment"] [class*="title"], ' +
3528
+ 'h2, h3'
3529
+ );
3530
+ if (titleEl) {
3531
+ return titleEl.textContent.trim().substring(0, 200);
3532
+ }
3533
+
3534
+ // Try to get from parent block
3535
+ var parentBlock = node.closest('[data-block-type]');
3536
+ if (parentBlock) {
3537
+ var blockTitle = parentBlock.querySelector('.block-title, h2, h3');
3538
+ if (blockTitle) {
3539
+ return blockTitle.textContent.trim().substring(0, 200);
3540
+ }
3541
+ }
3542
+
3543
+ // Get current lesson name as context
3544
+ var lessonInfo = getCachedLessonInfo();
3545
+ if (lessonInfo.name) {
3546
+ return 'Quiz in ' + lessonInfo.name;
3547
+ }
3548
+
3549
+ return 'Knowledge Check';
3550
+ }
3551
+
3285
3552
  function extractQuizResult(node) {
3286
3553
  var scoreEl = node.querySelector('.score-value, .quiz-score, .score-percentage, [class*="score"]');
3287
3554
  var score = scoreEl ? parseFloat(scoreEl.textContent.replace(/[^0-9.]/g, '')) : null;
3288
3555
 
3289
- var questions = node.querySelectorAll('.question-result, .question-feedback, .question-item, [class*="question"]');
3556
+ // Get assessment name and lesson context
3557
+ var assessmentName = extractAssessmentName(node);
3558
+ var lessonInfo = getCachedLessonInfo();
3559
+
3560
+ // Find all question containers - Rise uses various structures
3561
+ var questions = node.querySelectorAll(
3562
+ '.question-result, .question-feedback, .question-item, ' +
3563
+ '.blocks-quiz__question, .quiz-question, ' +
3564
+ '[class*="question-"][class*="result"], ' +
3565
+ '[class*="question-"][class*="feedback"], ' +
3566
+ '[data-question-id]'
3567
+ );
3568
+
3569
+ log('Found', questions.length, 'question elements in quiz result');
3290
3570
 
3291
3571
  if (questions.length > 0) {
3292
3572
  questions.forEach(function(q, index) {
3573
+ // Determine if correct - check multiple indicators
3293
3574
  var isCorrect = q.classList.contains('correct') ||
3294
- q.querySelector('.correct, [class*="correct"]') !== null ||
3295
- q.getAttribute('data-correct') === 'true';
3575
+ q.classList.contains('is-correct') ||
3576
+ q.querySelector('.correct, .is-correct, [class*="correct"]') !== null ||
3577
+ q.getAttribute('data-correct') === 'true' ||
3578
+ q.getAttribute('data-result') === 'correct';
3296
3579
 
3580
+ // Get the question text - try multiple selectors
3297
3581
  var questionText = '';
3298
- var questionEl = q.querySelector('.question-text, .question-stem, .question-title, [class*="question-text"]');
3582
+ var questionEl = q.querySelector(
3583
+ '.question-text, .question-stem, .question-title, ' +
3584
+ '.blocks-quiz__question-text, .quiz-question__text, ' +
3585
+ '[class*="question-text"], [class*="question-stem"], ' +
3586
+ '[class*="prompt"], p:first-of-type'
3587
+ );
3299
3588
  if (questionEl) {
3300
3589
  questionText = questionEl.textContent.trim();
3301
3590
  }
3302
3591
 
3592
+ // Get the learner's selected answer text
3303
3593
  var answerText = '';
3304
- var answerEl = q.querySelector('.selected-answer, .learner-response, .answer-text, [class*="response"]');
3594
+ var answerEl = q.querySelector(
3595
+ '.selected-answer, .learner-response, .answer-text, ' +
3596
+ '.user-answer, .chosen-answer, .response-text, ' +
3597
+ '.blocks-quiz__response, .quiz-question__response, ' +
3598
+ '[class*="selected"], [class*="chosen"], [class*="response"], ' +
3599
+ '[class*="user-answer"]'
3600
+ );
3305
3601
  if (answerEl) {
3306
3602
  answerText = answerEl.textContent.trim();
3307
3603
  }
3308
3604
 
3605
+ // If no specific answer element, try to find selected radio/checkbox label
3606
+ if (!answerText) {
3607
+ var selectedInput = q.querySelector('input:checked, [aria-checked="true"]');
3608
+ if (selectedInput) {
3609
+ var label = q.querySelector('label[for="' + selectedInput.id + '"]');
3610
+ if (label) {
3611
+ answerText = label.textContent.trim();
3612
+ } else {
3613
+ var parentLabel = selectedInput.closest('label');
3614
+ if (parentLabel) {
3615
+ answerText = parentLabel.textContent.trim();
3616
+ }
3617
+ }
3618
+ }
3619
+ }
3620
+
3621
+ // Get question ID if available
3622
+ var questionId = q.getAttribute('data-question-id') ||
3623
+ q.getAttribute('data-block-id') ||
3624
+ q.getAttribute('id') ||
3625
+ 'q' + (index + 1);
3626
+
3627
+ // Get correct answer text if available (for reporting)
3628
+ var correctAnswerText = '';
3629
+ var correctEl = q.querySelector(
3630
+ '.correct-answer, .right-answer, ' +
3631
+ '[class*="correct-answer"], [class*="right-answer"]'
3632
+ );
3633
+ if (correctEl) {
3634
+ correctAnswerText = correctEl.textContent.trim();
3635
+ }
3636
+
3637
+ log('Question', index + 1, ':', {
3638
+ questionText: questionText.substring(0, 50) + '...',
3639
+ answerText: answerText.substring(0, 50) + '...',
3640
+ isCorrect: isCorrect
3641
+ });
3642
+
3309
3643
  LRS.questionAnswered({
3310
- questionId: 'q' + (index + 1),
3311
- questionText: questionText.substring(0, 200),
3312
- answer: answerText.substring(0, 200),
3313
- result: isCorrect ? 'correct' : 'incorrect'
3644
+ questionId: questionId,
3645
+ questionNumber: index + 1,
3646
+ questionText: questionText.substring(0, 500),
3647
+ answer: answerText.substring(0, 500),
3648
+ correctAnswer: correctAnswerText.substring(0, 500),
3649
+ result: isCorrect ? 'correct' : 'incorrect',
3650
+ assessmentName: assessmentName,
3651
+ lessonName: lessonInfo.name,
3652
+ sectionName: lessonInfo.sectionName
3314
3653
  });
3315
3654
  });
3316
3655
  }
3317
3656
 
3318
3657
  LRS.assessmentEnded({
3319
3658
  assessmentId: extractAssessmentId(node),
3659
+ assessmentName: assessmentName,
3320
3660
  score: score,
3321
- questionCount: questions.length
3661
+ questionCount: questions.length,
3662
+ lessonName: lessonInfo.name,
3663
+ sectionName: lessonInfo.sectionName
3322
3664
  });
3323
3665
  }
3324
3666
 
@@ -3442,7 +3784,7 @@ function generateLrsBridgeCode(options) {
3442
3784
  // ========================================================================
3443
3785
 
3444
3786
  function init() {
3445
- log('Initializing LRS bridge v2.3.0...');
3787
+ log('Initializing LRS bridge v2.5.1...');
3446
3788
 
3447
3789
  // Extract course info early
3448
3790
  extractCourseInfo();