coursecode 0.1.0
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/LICENSE +21 -0
- package/README.md +322 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/cli.js +331 -0
- package/framework/assets/logo-coursecode-black.svg +14 -0
- package/framework/assets/logo-coursecode-white.svg +14 -0
- package/framework/assets/logo-coursecode.svg +14 -0
- package/framework/css/01-base.css +160 -0
- package/framework/css/02-layout.css +499 -0
- package/framework/css/accessibility.css +834 -0
- package/framework/css/components/accordions.css +710 -0
- package/framework/css/components/assessments.css +520 -0
- package/framework/css/components/audio-player.css +570 -0
- package/framework/css/components/badges.css +80 -0
- package/framework/css/components/breadcrumbs.css +87 -0
- package/framework/css/components/buttons.css +707 -0
- package/framework/css/components/callouts.css +1280 -0
- package/framework/css/components/cards.css +475 -0
- package/framework/css/components/carousel.css +193 -0
- package/framework/css/components/checkbox-group.css +123 -0
- package/framework/css/components/checklist.css +203 -0
- package/framework/css/components/collapse.css +96 -0
- package/framework/css/components/comparison.css +33 -0
- package/framework/css/components/content-image.css +36 -0
- package/framework/css/components/document-gallery.css +425 -0
- package/framework/css/components/dropdown.css +115 -0
- package/framework/css/components/embed-frame.css +142 -0
- package/framework/css/components/engagement.css +412 -0
- package/framework/css/components/features.css +35 -0
- package/framework/css/components/flip-cards.css +253 -0
- package/framework/css/components/footer.css +353 -0
- package/framework/css/components/forms.css +294 -0
- package/framework/css/components/hero.css +216 -0
- package/framework/css/components/images.css +528 -0
- package/framework/css/components/interactive-timeline.css +274 -0
- package/framework/css/components/intro-cards.css +30 -0
- package/framework/css/components/lightbox.css +666 -0
- package/framework/css/components/loading.css +65 -0
- package/framework/css/components/modals.css +235 -0
- package/framework/css/components/notifications.css +107 -0
- package/framework/css/components/quote.css +150 -0
- package/framework/css/components/sidebar.css +684 -0
- package/framework/css/components/slide-header.css +52 -0
- package/framework/css/components/spinner.css +62 -0
- package/framework/css/components/stats.css +44 -0
- package/framework/css/components/steps.css +232 -0
- package/framework/css/components/tables.css +90 -0
- package/framework/css/components/tabs.css +347 -0
- package/framework/css/components/timeline.css +154 -0
- package/framework/css/components/toggle.css +95 -0
- package/framework/css/components/tooltip.css +226 -0
- package/framework/css/components/video-player.css +438 -0
- package/framework/css/design-tokens.css +707 -0
- package/framework/css/framework.css +86 -0
- package/framework/css/interactions/accessibility.css +75 -0
- package/framework/css/interactions/base.css +92 -0
- package/framework/css/interactions/drag-drop.css +295 -0
- package/framework/css/interactions/fill-in-the-blank.css +236 -0
- package/framework/css/interactions/hotspots.css +69 -0
- package/framework/css/interactions/index.css +45 -0
- package/framework/css/interactions/interactive-image.css +359 -0
- package/framework/css/interactions/likert.css +126 -0
- package/framework/css/interactions/matching.css +354 -0
- package/framework/css/interactions/numeric-input.css +78 -0
- package/framework/css/interactions/sequencing.css +378 -0
- package/framework/css/interactions/true-false.css +177 -0
- package/framework/css/layouts/article.css +258 -0
- package/framework/css/layouts/base.css +30 -0
- package/framework/css/layouts/canvas.css +38 -0
- package/framework/css/layouts/focused.css +236 -0
- package/framework/css/layouts/index.css +29 -0
- package/framework/css/layouts/presentation.css +191 -0
- package/framework/css/layouts/traditional.css +52 -0
- package/framework/css/responsive.css +439 -0
- package/framework/css/utilities/accessibility-utils.css +59 -0
- package/framework/css/utilities/animations.css +419 -0
- package/framework/css/utilities/borders.css +72 -0
- package/framework/css/utilities/colors.css +76 -0
- package/framework/css/utilities/container.css +46 -0
- package/framework/css/utilities/decorative.css +442 -0
- package/framework/css/utilities/display.css +257 -0
- package/framework/css/utilities/flexbox.css +80 -0
- package/framework/css/utilities/grid.css +69 -0
- package/framework/css/utilities/icons.css +534 -0
- package/framework/css/utilities/lists.css +190 -0
- package/framework/css/utilities/spacing.css +167 -0
- package/framework/css/utilities/tables.css +81 -0
- package/framework/css/utilities/typography.css +159 -0
- package/framework/css/utilities/visibility.css +117 -0
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
- package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
- package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
- package/framework/docs/DATA_MODEL.md +409 -0
- package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
- package/framework/docs/USER_GUIDE.md +583 -0
- package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
- package/framework/docs/examples/cloudflare-data-worker.js +102 -0
- package/framework/docs/examples/cloudflare-error-worker.js +228 -0
- package/framework/index.html +175 -0
- package/framework/js/app/AppActions.js +410 -0
- package/framework/js/app/AppState.js +225 -0
- package/framework/js/app/AppUI.js +616 -0
- package/framework/js/assessment/AssessmentActions.js +615 -0
- package/framework/js/assessment/AssessmentFactory.js +471 -0
- package/framework/js/assessment/AssessmentState.js +322 -0
- package/framework/js/assessment/AssessmentUI.js +451 -0
- package/framework/js/automation/api-engagement.js +196 -0
- package/framework/js/automation/api-interactions.js +167 -0
- package/framework/js/automation/api.js +242 -0
- package/framework/js/automation/index.js +41 -0
- package/framework/js/components/interactions/drag-drop.js +884 -0
- package/framework/js/components/interactions/fill-in.js +535 -0
- package/framework/js/components/interactions/hotspot.js +702 -0
- package/framework/js/components/interactions/interaction-base.js +511 -0
- package/framework/js/components/interactions/likert.js +301 -0
- package/framework/js/components/interactions/matching.js +699 -0
- package/framework/js/components/interactions/multiple-choice.js +377 -0
- package/framework/js/components/interactions/numeric.js +271 -0
- package/framework/js/components/interactions/sequencing.js +423 -0
- package/framework/js/components/interactions/true-false.js +241 -0
- package/framework/js/components/ui-components/accordion.js +442 -0
- package/framework/js/components/ui-components/alert.js +88 -0
- package/framework/js/components/ui-components/audio-player.js +1193 -0
- package/framework/js/components/ui-components/callout.js +121 -0
- package/framework/js/components/ui-components/carousel.js +145 -0
- package/framework/js/components/ui-components/checkbox-group.js +87 -0
- package/framework/js/components/ui-components/checklist.js +40 -0
- package/framework/js/components/ui-components/collapse.js +114 -0
- package/framework/js/components/ui-components/comparison.js +30 -0
- package/framework/js/components/ui-components/conditional-display.js +150 -0
- package/framework/js/components/ui-components/content-image.js +41 -0
- package/framework/js/components/ui-components/dropdown.js +262 -0
- package/framework/js/components/ui-components/embed-frame.js +274 -0
- package/framework/js/components/ui-components/features.js +33 -0
- package/framework/js/components/ui-components/flip-card.js +230 -0
- package/framework/js/components/ui-components/form-validator.js +76 -0
- package/framework/js/components/ui-components/hero.js +49 -0
- package/framework/js/components/ui-components/index.js +12 -0
- package/framework/js/components/ui-components/interactive-image.js +235 -0
- package/framework/js/components/ui-components/interactive-timeline.js +285 -0
- package/framework/js/components/ui-components/intro-cards.js +35 -0
- package/framework/js/components/ui-components/lightbox.js +652 -0
- package/framework/js/components/ui-components/modal.js +386 -0
- package/framework/js/components/ui-components/notifications.js +145 -0
- package/framework/js/components/ui-components/progress.js +88 -0
- package/framework/js/components/ui-components/quote.js +41 -0
- package/framework/js/components/ui-components/stats.js +33 -0
- package/framework/js/components/ui-components/steps.js +41 -0
- package/framework/js/components/ui-components/tabs.js +255 -0
- package/framework/js/components/ui-components/timeline.js +42 -0
- package/framework/js/components/ui-components/toggle-group.js +73 -0
- package/framework/js/components/ui-components/tooltip.js +458 -0
- package/framework/js/components/ui-components/value-display.js +133 -0
- package/framework/js/components/ui-components/video-player.js +686 -0
- package/framework/js/core/component-catalog.js +121 -0
- package/framework/js/core/event-bus.js +178 -0
- package/framework/js/core/interaction-catalog.js +149 -0
- package/framework/js/dev/runtime-linter.js +1725 -0
- package/framework/js/drivers/cmi5-driver.js +768 -0
- package/framework/js/drivers/driver-factory.js +77 -0
- package/framework/js/drivers/driver-interface.js +110 -0
- package/framework/js/drivers/http-driver-base.js +241 -0
- package/framework/js/drivers/lti-driver.js +508 -0
- package/framework/js/drivers/proxy-driver.js +444 -0
- package/framework/js/drivers/scorm-12-driver.js +560 -0
- package/framework/js/drivers/scorm-2004-driver.js +775 -0
- package/framework/js/drivers/scorm-driver-base.js +112 -0
- package/framework/js/engagement/engagement-manager.js +404 -0
- package/framework/js/engagement/engagement-progress.js +191 -0
- package/framework/js/engagement/engagement-trackers.js +215 -0
- package/framework/js/engagement/requirement-strategies.js +268 -0
- package/framework/js/main.js +727 -0
- package/framework/js/managers/accessibility-manager.js +499 -0
- package/framework/js/managers/assessment-manager.js +230 -0
- package/framework/js/managers/audio-manager.js +944 -0
- package/framework/js/managers/comment-manager.js +88 -0
- package/framework/js/managers/flag-manager.js +86 -0
- package/framework/js/managers/interaction-manager.js +254 -0
- package/framework/js/managers/interaction-registry.js +96 -0
- package/framework/js/managers/objective-manager.js +423 -0
- package/framework/js/managers/score-manager.js +441 -0
- package/framework/js/managers/video-manager.js +536 -0
- package/framework/js/navigation/Breadcrumbs.js +234 -0
- package/framework/js/navigation/NavigationActions.js +1132 -0
- package/framework/js/navigation/NavigationState.js +276 -0
- package/framework/js/navigation/NavigationUI.js +574 -0
- package/framework/js/navigation/document-gallery.js +357 -0
- package/framework/js/navigation/navigation-helpers.js +175 -0
- package/framework/js/navigation/navigation-validators.js +174 -0
- package/framework/js/state/index.js +8 -0
- package/framework/js/state/lms-connection.js +482 -0
- package/framework/js/state/lms-error-utils.js +58 -0
- package/framework/js/state/state-commits.js +200 -0
- package/framework/js/state/state-domains.js +86 -0
- package/framework/js/state/state-manager.js +502 -0
- package/framework/js/state/state-validation.js +311 -0
- package/framework/js/state/transaction-log.js +41 -0
- package/framework/js/state/xapi-statement-service.js +325 -0
- package/framework/js/utilities/access-control.js +99 -0
- package/framework/js/utilities/breakpoint-manager.js +315 -0
- package/framework/js/utilities/canvas-slide.js +35 -0
- package/framework/js/utilities/conditional-display.js +388 -0
- package/framework/js/utilities/course-channel.js +214 -0
- package/framework/js/utilities/course-helpers.js +420 -0
- package/framework/js/utilities/data-reporter.js +273 -0
- package/framework/js/utilities/error-reporter.js +313 -0
- package/framework/js/utilities/hotspot-helper.js +341 -0
- package/framework/js/utilities/icons.js +348 -0
- package/framework/js/utilities/logger.js +92 -0
- package/framework/js/utilities/markdown-renderer.js +45 -0
- package/framework/js/utilities/scroll-tracker.js +68 -0
- package/framework/js/utilities/ui-initializer.js +146 -0
- package/framework/js/utilities/utilities.js +293 -0
- package/framework/js/utilities/view-manager.js +227 -0
- package/framework/js/validation/html-validators.js +422 -0
- package/framework/js/validation/scorm-validators.js +438 -0
- package/framework/js/vendor/pipwerks.js +931 -0
- package/framework/scripts/generate-narration.js +629 -0
- package/framework/scripts/tts-providers/azure-provider.js +178 -0
- package/framework/scripts/tts-providers/base-provider.js +81 -0
- package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
- package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
- package/framework/scripts/tts-providers/google-provider.js +272 -0
- package/framework/scripts/tts-providers/index.js +158 -0
- package/framework/scripts/tts-providers/openai-provider.js +143 -0
- package/framework/version.json +63 -0
- package/lib/authoring-api.js +919 -0
- package/lib/build-linter.js +450 -0
- package/lib/build-packaging.js +186 -0
- package/lib/build.js +88 -0
- package/lib/cloud.js +691 -0
- package/lib/convert.js +341 -0
- package/lib/course-parser.js +936 -0
- package/lib/course-writer.js +258 -0
- package/lib/create.js +248 -0
- package/lib/css-index.js +237 -0
- package/lib/dev.js +51 -0
- package/lib/export-content.js +1246 -0
- package/lib/headless-browser.js +413 -0
- package/lib/import.js +377 -0
- package/lib/index.js +80 -0
- package/lib/info.js +79 -0
- package/lib/interaction-formatters.js +568 -0
- package/lib/manifest/cmi5-manifest.js +63 -0
- package/lib/manifest/lti-tool-config.js +53 -0
- package/lib/manifest/manifest-factory.js +99 -0
- package/lib/manifest/scorm-12-manifest.js +61 -0
- package/lib/manifest/scorm-2004-manifest.js +94 -0
- package/lib/manifest/scorm-proxy-manifest.js +104 -0
- package/lib/manifest-parser.js +96 -0
- package/lib/mcp-prompts.js +753 -0
- package/lib/mcp-server.js +316 -0
- package/lib/narration.js +53 -0
- package/lib/pdf-structure.js +142 -0
- package/lib/preview-export.js +231 -0
- package/lib/preview-routes-api.js +662 -0
- package/lib/preview-routes-editing.js +159 -0
- package/lib/preview-routes-lms.js +230 -0
- package/lib/preview-server.js +564 -0
- package/lib/project-utils.js +269 -0
- package/lib/proxy-templates/proxy.html +68 -0
- package/lib/proxy-templates/scorm-bridge.js +112 -0
- package/lib/scaffold.js +193 -0
- package/lib/schema-extractor.js +361 -0
- package/lib/slide-source-editor.js +586 -0
- package/lib/stub-player/app-viewer.js +195 -0
- package/lib/stub-player/app.js +370 -0
- package/lib/stub-player/catalog-panel.js +312 -0
- package/lib/stub-player/config-panel.js +1303 -0
- package/lib/stub-player/content-generator.js +586 -0
- package/lib/stub-player/content-viewer.js +173 -0
- package/lib/stub-player/debug-panel.js +420 -0
- package/lib/stub-player/edit-mode.js +922 -0
- package/lib/stub-player/edit-utils.js +400 -0
- package/lib/stub-player/header-bar.js +354 -0
- package/lib/stub-player/interaction-editor.js +210 -0
- package/lib/stub-player/interactions-panel.js +565 -0
- package/lib/stub-player/lms-api.js +1094 -0
- package/lib/stub-player/login-screen.js +74 -0
- package/lib/stub-player/outline-mode.js +689 -0
- package/lib/stub-player/styles/_assessments-panel.css +245 -0
- package/lib/stub-player/styles/_base.css +89 -0
- package/lib/stub-player/styles/_catalog-icons.css +96 -0
- package/lib/stub-player/styles/_catalog-panel.css +291 -0
- package/lib/stub-player/styles/_config-panel.css +636 -0
- package/lib/stub-player/styles/_content-viewer.css +834 -0
- package/lib/stub-player/styles/_debug-panel.css +576 -0
- package/lib/stub-player/styles/_edit-mode.css +128 -0
- package/lib/stub-player/styles/_header-bar.css +343 -0
- package/lib/stub-player/styles/_interaction-editor.css +140 -0
- package/lib/stub-player/styles/_interactions-panel.css +1038 -0
- package/lib/stub-player/styles/_login-screen.css +102 -0
- package/lib/stub-player/styles/_outline-mode.css +752 -0
- package/lib/stub-player/styles.css +15 -0
- package/lib/stub-player.js +160 -0
- package/lib/test-data-reporting.js +176 -0
- package/lib/test-error-reporting.js +146 -0
- package/lib/token.js +86 -0
- package/lib/upgrade.js +257 -0
- package/lib/validation-rules.js +517 -0
- package/lib/vite-plugin-content-discovery.js +296 -0
- package/package.json +108 -0
- package/schemas/XMLSchema.dtd +402 -0
- package/schemas/adlcp_v1p3.xsd +111 -0
- package/schemas/adlnav_v1p3.xsd +61 -0
- package/schemas/adlseq_v1p3.xsd +93 -0
- package/schemas/common/anyElement.xsd +27 -0
- package/schemas/common/dataTypes.xsd +138 -0
- package/schemas/common/elementNames.xsd +767 -0
- package/schemas/common/elementTypes.xsd +786 -0
- package/schemas/common/rootElement.xsd +31 -0
- package/schemas/common/vocabTypes.xsd +345 -0
- package/schemas/common/vocabValues.xsd +257 -0
- package/schemas/datatypes.dtd +203 -0
- package/schemas/ims_xml.xsd +35 -0
- package/schemas/imscp_v1p1.xsd +368 -0
- package/schemas/imsss_v1p0.xsd +67 -0
- package/schemas/imsss_v1p0auxresource.xsd +19 -0
- package/schemas/imsss_v1p0control.xsd +20 -0
- package/schemas/imsss_v1p0delivery.xsd +17 -0
- package/schemas/imsss_v1p0limit.xsd +47 -0
- package/schemas/imsss_v1p0objective.xsd +67 -0
- package/schemas/imsss_v1p0random.xsd +16 -0
- package/schemas/imsss_v1p0rollup.xsd +46 -0
- package/schemas/imsss_v1p0seqrule.xsd +108 -0
- package/schemas/imsss_v1p0util.xsd +94 -0
- package/schemas/license.txt +17 -0
- package/schemas/lom.xsd +102 -0
- package/schemas/lomCustom.xsd +62 -0
- package/schemas/lomLoose.xsd +62 -0
- package/schemas/lomStrict.xsd +62 -0
- package/schemas/xml.xsd +81 -0
- package/template/.env.example +92 -0
- package/template/course/assets/audio/example-intro.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
- package/template/course/assets/docs/example_md_1.md +39 -0
- package/template/course/assets/docs/example_md_2.md +41 -0
- package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
- package/template/course/assets/docs/example_pdf_2.pdf +0 -0
- package/template/course/assets/images/course-architecture.svg +36 -0
- package/template/course/assets/images/logo.svg +14 -0
- package/template/course/assets/widgets/counter-demo.html +190 -0
- package/template/course/assets/widgets/gravity-painter.html +384 -0
- package/template/course/course-config.js +539 -0
- package/template/course/icons.js +19 -0
- package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
- package/template/course/slides/example-course-structure.js +138 -0
- package/template/course/slides/example-final-exam.js +144 -0
- package/template/course/slides/example-finishing.js +127 -0
- package/template/course/slides/example-interactions-showcase.js +615 -0
- package/template/course/slides/example-preview-tour.js +129 -0
- package/template/course/slides/example-remedial.js +143 -0
- package/template/course/slides/example-summary.js +103 -0
- package/template/course/slides/example-ui-showcase.js +1805 -0
- package/template/course/slides/example-welcome.js +123 -0
- package/template/course/slides/example-workflow.js +140 -0
- package/template/course/theme.css +165 -0
- package/template/eslint.config.js +47 -0
- package/template/package.json +28 -0
- package/template/vite.config.js +339 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file scorm-validators.js
|
|
3
|
+
* @description SCORM 2004 4th Edition data format constants and validation utilities.
|
|
4
|
+
* Ensures data conforms to SCORM 2004 specifications.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SCORM 2004 4th Edition valid interaction types.
|
|
9
|
+
* @constant {string[]}
|
|
10
|
+
*/
|
|
11
|
+
export const SCORM_INTERACTION_TYPES = [
|
|
12
|
+
'true-false',
|
|
13
|
+
'choice',
|
|
14
|
+
'fill-in',
|
|
15
|
+
'long-fill-in',
|
|
16
|
+
'matching',
|
|
17
|
+
'performance',
|
|
18
|
+
'sequencing',
|
|
19
|
+
'likert',
|
|
20
|
+
'numeric',
|
|
21
|
+
'other'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* SCORM 2004 4th Edition valid interaction result values.
|
|
26
|
+
* @constant {string[]}
|
|
27
|
+
*/
|
|
28
|
+
export const SCORM_INTERACTION_RESULTS = [
|
|
29
|
+
'correct',
|
|
30
|
+
'incorrect',
|
|
31
|
+
'unanticipated',
|
|
32
|
+
'neutral'
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// SCORM 2004 Learner Response Formatting
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Formats a learner response for SCORM 2004 cmi.interactions.n.learner_response.
|
|
41
|
+
* SCORM 2004 requires specific string formats for each interaction type.
|
|
42
|
+
*
|
|
43
|
+
* Format requirements by type:
|
|
44
|
+
* - true-false: "true" or "false" (literal strings)
|
|
45
|
+
* - choice: "a[,]b[,]c" (short identifiers separated by [,])
|
|
46
|
+
* - matching: "source1[.]target1[,]source2[.]target2" (pairs with [.] separator)
|
|
47
|
+
* - sequencing: "item1[,]item2[,]item3" (ordered items separated by [,])
|
|
48
|
+
* - fill-in: plain text string
|
|
49
|
+
* - numeric: numeric string
|
|
50
|
+
* - likert: single character or short identifier
|
|
51
|
+
* - other: any string representation
|
|
52
|
+
*
|
|
53
|
+
* @param {string} interactionType - The SCORM interaction type
|
|
54
|
+
* @param {*} response - The internal response format (varies by type)
|
|
55
|
+
* @returns {string} SCORM 2004 compliant learner_response string
|
|
56
|
+
* @example
|
|
57
|
+
* formatLearnerResponseForScorm('true-false', true) // "true"
|
|
58
|
+
* formatLearnerResponseForScorm('matching', {pair1: 'match1', pair2: 'match2'}) // "pair1[.]match1[,]pair2[.]match2"
|
|
59
|
+
* formatLearnerResponseForScorm('sequencing', ['a', 'b', 'c']) // "a[,]b[,]c"
|
|
60
|
+
* formatLearnerResponseForScorm('choice', ['a', 'b']) // "a[,]b"
|
|
61
|
+
*/
|
|
62
|
+
export function formatLearnerResponseForScorm(interactionType, response) {
|
|
63
|
+
// Handle null/undefined
|
|
64
|
+
if (response === null || response === undefined) {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
switch (interactionType) {
|
|
69
|
+
case 'true-false':
|
|
70
|
+
// Must be literal "true" or "false" string
|
|
71
|
+
if (typeof response === 'boolean') {
|
|
72
|
+
return response ? 'true' : 'false';
|
|
73
|
+
}
|
|
74
|
+
if (typeof response === 'string') {
|
|
75
|
+
const normalized = response.toLowerCase().trim();
|
|
76
|
+
if (normalized === 'true' || normalized === 'false') {
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Invalid response for true-false - return empty (will fail validation, but that's correct)
|
|
81
|
+
return '';
|
|
82
|
+
|
|
83
|
+
case 'choice':
|
|
84
|
+
// Format: identifier[,]identifier[,]identifier
|
|
85
|
+
if (Array.isArray(response)) {
|
|
86
|
+
return response.join('[,]');
|
|
87
|
+
}
|
|
88
|
+
if (typeof response === 'string') {
|
|
89
|
+
// Already a string, might be single choice or already formatted
|
|
90
|
+
return response;
|
|
91
|
+
}
|
|
92
|
+
return '';
|
|
93
|
+
|
|
94
|
+
case 'matching':
|
|
95
|
+
// Format: source[.]target[,]source[.]target
|
|
96
|
+
if (typeof response === 'object' && response !== null && !Array.isArray(response)) {
|
|
97
|
+
const pairs = Object.entries(response)
|
|
98
|
+
.filter(([_key, value]) => value !== null && value !== undefined && String(value).trim() !== '')
|
|
99
|
+
.map(([source, target]) => `${source}[.]${target}`);
|
|
100
|
+
return pairs.join('[,]');
|
|
101
|
+
}
|
|
102
|
+
if (typeof response === 'string') {
|
|
103
|
+
// Try to parse as JSON and format
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(response);
|
|
106
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
107
|
+
const pairs = Object.entries(parsed)
|
|
108
|
+
.filter(([_key, value]) => value !== null && value !== undefined && String(value).trim() !== '')
|
|
109
|
+
.map(([source, target]) => `${source}[.]${target}`);
|
|
110
|
+
return pairs.join('[,]');
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Not JSON, return as-is
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return '';
|
|
118
|
+
|
|
119
|
+
case 'sequencing':
|
|
120
|
+
// Format: item[,]item[,]item
|
|
121
|
+
if (Array.isArray(response)) {
|
|
122
|
+
return response.join('[,]');
|
|
123
|
+
}
|
|
124
|
+
if (typeof response === 'string') {
|
|
125
|
+
// Try to parse as JSON array
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(response);
|
|
128
|
+
if (Array.isArray(parsed)) {
|
|
129
|
+
return parsed.join('[,]');
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// Not JSON, return as-is
|
|
133
|
+
return response;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return '';
|
|
137
|
+
|
|
138
|
+
case 'fill-in':
|
|
139
|
+
case 'long-fill-in':
|
|
140
|
+
// Plain text string
|
|
141
|
+
if (typeof response === 'object') {
|
|
142
|
+
// fill-in might return {blankId: answer} - join values
|
|
143
|
+
const values = Object.values(response).filter(v => v !== null && v !== undefined);
|
|
144
|
+
return values.join('[,]');
|
|
145
|
+
}
|
|
146
|
+
return String(response);
|
|
147
|
+
|
|
148
|
+
case 'numeric':
|
|
149
|
+
// Numeric string or range object
|
|
150
|
+
// SCORM 2004 correct_responses format: "value" or "min[:max]"
|
|
151
|
+
if (typeof response === 'number') {
|
|
152
|
+
return String(response);
|
|
153
|
+
}
|
|
154
|
+
if (typeof response === 'object' && response !== null) {
|
|
155
|
+
// Handle correctRange object: {exact, min, max}
|
|
156
|
+
// Try to parse if it's a JSON string representation
|
|
157
|
+
let rangeObj = response;
|
|
158
|
+
if (typeof response === 'string') {
|
|
159
|
+
try {
|
|
160
|
+
rangeObj = JSON.parse(response);
|
|
161
|
+
} catch {
|
|
162
|
+
return response; // Return as-is if not parseable
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (rangeObj.exact !== undefined) {
|
|
167
|
+
return String(rangeObj.exact);
|
|
168
|
+
}
|
|
169
|
+
if (rangeObj.min !== undefined && rangeObj.max !== undefined) {
|
|
170
|
+
return `${rangeObj.min}[:${rangeObj.max}]`;
|
|
171
|
+
}
|
|
172
|
+
if (rangeObj.min !== undefined) {
|
|
173
|
+
return String(rangeObj.min);
|
|
174
|
+
}
|
|
175
|
+
if (rangeObj.max !== undefined) {
|
|
176
|
+
return String(rangeObj.max);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (typeof response === 'string') {
|
|
180
|
+
// Try to parse as JSON for correctRange
|
|
181
|
+
try {
|
|
182
|
+
const parsed = JSON.parse(response);
|
|
183
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
184
|
+
if (parsed.exact !== undefined) {
|
|
185
|
+
return String(parsed.exact);
|
|
186
|
+
}
|
|
187
|
+
if (parsed.min !== undefined && parsed.max !== undefined) {
|
|
188
|
+
return `${parsed.min}[:${parsed.max}]`;
|
|
189
|
+
}
|
|
190
|
+
if (parsed.min !== undefined) {
|
|
191
|
+
return String(parsed.min);
|
|
192
|
+
}
|
|
193
|
+
if (parsed.max !== undefined) {
|
|
194
|
+
return String(parsed.max);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
// Not JSON, return as-is
|
|
199
|
+
return response;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return String(response);
|
|
203
|
+
|
|
204
|
+
case 'likert':
|
|
205
|
+
// Single identifier or value
|
|
206
|
+
if (typeof response === 'object' && response !== null) {
|
|
207
|
+
// Likert might return {questionId: value} - use first value
|
|
208
|
+
const values = Object.values(response);
|
|
209
|
+
return values.length > 0 ? String(values[0]) : '';
|
|
210
|
+
}
|
|
211
|
+
return String(response);
|
|
212
|
+
|
|
213
|
+
case 'performance':
|
|
214
|
+
case 'other':
|
|
215
|
+
default:
|
|
216
|
+
// For other/performance types, convert to string representation
|
|
217
|
+
if (typeof response === 'object') {
|
|
218
|
+
try {
|
|
219
|
+
// If it's an empty object, return empty string
|
|
220
|
+
if (Object.keys(response).length === 0) {
|
|
221
|
+
return '';
|
|
222
|
+
}
|
|
223
|
+
return JSON.stringify(response);
|
|
224
|
+
} catch {
|
|
225
|
+
return '';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return String(response);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* SCORM 2004 4th Edition valid completion status values.
|
|
234
|
+
* @constant {string[]}
|
|
235
|
+
*/
|
|
236
|
+
export const SCORM_COMPLETION_STATUS = [
|
|
237
|
+
'completed',
|
|
238
|
+
'incomplete',
|
|
239
|
+
'not attempted',
|
|
240
|
+
'unknown'
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* SCORM 2004 4th Edition valid success status values.
|
|
245
|
+
* @constant {string[]}
|
|
246
|
+
*/
|
|
247
|
+
export const SCORM_SUCCESS_STATUS = [
|
|
248
|
+
'passed',
|
|
249
|
+
'failed',
|
|
250
|
+
'unknown'
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generates a SCORM 2004 4th Edition compliant timestamp for the current time.
|
|
255
|
+
* SCORM 2004 requires: YYYY-MM-DDTHH:MM:SS (no milliseconds, no Z suffix)
|
|
256
|
+
*
|
|
257
|
+
* @returns {string} SCORM 2004 compliant timestamp (e.g., "2025-01-15T10:30:00")
|
|
258
|
+
* @example
|
|
259
|
+
* generateScormTimestamp() // "2025-01-15T10:30:00"
|
|
260
|
+
*/
|
|
261
|
+
export function generateScormTimestamp() {
|
|
262
|
+
const date = new Date();
|
|
263
|
+
|
|
264
|
+
// Format: YYYY-MM-DDTHH:MM:SS (UTC, no milliseconds, no Z suffix)
|
|
265
|
+
const year = date.getUTCFullYear();
|
|
266
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
|
267
|
+
const day = String(date.getUTCDate()).padStart(2, '0');
|
|
268
|
+
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
269
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
270
|
+
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
|
|
271
|
+
|
|
272
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Validates if a string is a valid ISO 8601 timestamp.
|
|
277
|
+
* @param {string} timestamp - The timestamp to validate
|
|
278
|
+
* @returns {boolean} True if valid ISO 8601 timestamp
|
|
279
|
+
*/
|
|
280
|
+
export function isValidISO8601Timestamp(timestamp) {
|
|
281
|
+
if (typeof timestamp !== 'string') {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const date = new Date(timestamp);
|
|
287
|
+
// Check if it's a valid date and can be converted back to ISO format
|
|
288
|
+
return date.toISOString() === timestamp || !isNaN(date.getTime());
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Validates if a string is a valid ISO 8601 duration format.
|
|
296
|
+
* Format: P[nY][nM][nD][T[nH][nM][nS]]
|
|
297
|
+
* @param {string} duration - The duration to validate
|
|
298
|
+
* @returns {boolean} True if valid ISO 8601 duration
|
|
299
|
+
*/
|
|
300
|
+
export function isValidISO8601Duration(duration) {
|
|
301
|
+
if (typeof duration !== 'string') {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ISO 8601 duration pattern: P[nY][nM][nD][T[nH][nM][nS]]
|
|
306
|
+
// Each component is optional, but at least one must be present
|
|
307
|
+
const durationPattern = /^P(?:\d+Y)?(?:\d+M)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$/;
|
|
308
|
+
|
|
309
|
+
// Must match pattern and not be just 'P' or 'PT' (empty duration)
|
|
310
|
+
return durationPattern.test(duration) && duration !== 'P' && duration !== 'PT';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Validates if a value is in an allowed list of values.
|
|
315
|
+
* @param {*} value - The value to validate
|
|
316
|
+
* @param {Array} allowedValues - Array of allowed values
|
|
317
|
+
* @param {string} [fieldName='value'] - Name of the field for error messaging
|
|
318
|
+
* @returns {{valid: boolean, error: string|null}} Validation result
|
|
319
|
+
*/
|
|
320
|
+
export function validateEnum(value, allowedValues, fieldName = 'value') {
|
|
321
|
+
if (!allowedValues.includes(value)) {
|
|
322
|
+
return {
|
|
323
|
+
valid: false,
|
|
324
|
+
error: `Invalid ${fieldName} "${value}". Must be one of: ${allowedValues.join(', ')}`
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return { valid: true, error: null };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Validates if a value is a valid number.
|
|
332
|
+
* @param {*} value - The value to validate
|
|
333
|
+
* @param {string} [fieldName='value'] - Name of the field for error messaging
|
|
334
|
+
* @returns {{valid: boolean, error: string|null}} Validation result
|
|
335
|
+
*/
|
|
336
|
+
export function validateNumeric(value, fieldName = 'value') {
|
|
337
|
+
const num = Number(value);
|
|
338
|
+
if (isNaN(num)) {
|
|
339
|
+
return {
|
|
340
|
+
valid: false,
|
|
341
|
+
error: `Invalid ${fieldName} "${value}". Must be a number`
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return { valid: true, error: null };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Validates if a value is an array.
|
|
349
|
+
* @param {*} value - The value to validate
|
|
350
|
+
* @param {string} [fieldName='value'] - Name of the field for error messaging
|
|
351
|
+
* @returns {{valid: boolean, error: string|null}} Validation result
|
|
352
|
+
*/
|
|
353
|
+
export function validateArray(value, fieldName = 'value') {
|
|
354
|
+
if (!Array.isArray(value)) {
|
|
355
|
+
return {
|
|
356
|
+
valid: false,
|
|
357
|
+
error: `Field "${fieldName}" must be an array`
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
return { valid: true, error: null };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Validates if an array contains only string values.
|
|
365
|
+
* @param {Array} arr - The array to validate
|
|
366
|
+
* @param {string} [fieldName='value'] - Name of the field for error messaging
|
|
367
|
+
* @returns {{valid: boolean, error: string|null}} Validation result
|
|
368
|
+
*/
|
|
369
|
+
export function validateStringArray(arr, fieldName = 'value') {
|
|
370
|
+
if (!Array.isArray(arr)) {
|
|
371
|
+
return {
|
|
372
|
+
valid: false,
|
|
373
|
+
error: `Field "${fieldName}" must be an array`
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (arr.some(item => typeof item !== 'string')) {
|
|
378
|
+
return {
|
|
379
|
+
valid: false,
|
|
380
|
+
error: `Field "${fieldName}" must contain only string values`
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { valid: true, error: null };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Validates required fields are present in an object.
|
|
389
|
+
* @param {Object} data - The data object to validate
|
|
390
|
+
* @param {string[]} requiredFields - Array of required field names
|
|
391
|
+
* @returns {{valid: boolean, errors: string[]}} Validation result with array of missing fields
|
|
392
|
+
*/
|
|
393
|
+
export function validateRequiredFields(data, requiredFields) {
|
|
394
|
+
const errors = [];
|
|
395
|
+
|
|
396
|
+
for (const field of requiredFields) {
|
|
397
|
+
if (data[field] === undefined || data[field] === null || data[field] === '') {
|
|
398
|
+
errors.push(`Missing required field "${field}"`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
valid: errors.length === 0,
|
|
404
|
+
errors
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Creates a detailed validation error message from multiple errors.
|
|
410
|
+
* @param {string[]} errors - Array of error messages
|
|
411
|
+
* @param {string} [context=''] - Optional context (e.g., record ID)
|
|
412
|
+
* @param {string} [prefix='Validation failed'] - Error message prefix
|
|
413
|
+
* @returns {string} Formatted error message
|
|
414
|
+
*/
|
|
415
|
+
export function formatValidationError(errors, context = '', prefix = 'Validation failed') {
|
|
416
|
+
const contextStr = context ? ` for ${context}` : '';
|
|
417
|
+
return `${prefix}${contextStr}:\n - ${errors.join('\n - ')}`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** Convenience function to validate SCORM interaction type. */
|
|
421
|
+
export function validateInteractionType(type) {
|
|
422
|
+
return validateEnum(type, SCORM_INTERACTION_TYPES, 'type');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Convenience function to validate SCORM interaction result. */
|
|
426
|
+
export function validateInteractionResult(result) {
|
|
427
|
+
return validateEnum(result, SCORM_INTERACTION_RESULTS, 'result');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Convenience function to validate SCORM completion status. */
|
|
431
|
+
export function validateCompletionStatus(status) {
|
|
432
|
+
return validateEnum(status, SCORM_COMPLETION_STATUS, 'completion_status');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Convenience function to validate SCORM success status. */
|
|
436
|
+
export function validateSuccessStatus(status) {
|
|
437
|
+
return validateEnum(status, SCORM_SUCCESS_STATUS, 'success_status');
|
|
438
|
+
}
|