@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 +393 -51
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +393 -51
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +393 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +393 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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 = '
|
|
1096
|
-
log('Found SCORM
|
|
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.
|
|
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
|
-
//
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3304
|
-
q.
|
|
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(
|
|
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(
|
|
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:
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
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.
|
|
3796
|
+
log('Initializing LRS bridge v2.5.0...');
|
|
3455
3797
|
|
|
3456
3798
|
// Extract course info early
|
|
3457
3799
|
extractCourseInfo();
|