@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.cjs +394 -52
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +394 -52
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +394 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +394 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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 = '
|
|
1087
|
-
log('Found SCORM
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3295
|
-
q.
|
|
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(
|
|
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(
|
|
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:
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
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.
|
|
3787
|
+
log('Initializing LRS bridge v2.5.1...');
|
|
3446
3788
|
|
|
3447
3789
|
// Extract course info early
|
|
3448
3790
|
extractCourseInfo();
|