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,586 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stub-player/content-generator.js - Server-side content HTML generator
|
|
3
|
+
*
|
|
4
|
+
* Handles rendering of course content directly to HTML for the content viewer.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { parseCourse } from '../course-parser.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Escape HTML special characters
|
|
11
|
+
*/
|
|
12
|
+
function escapeHtml(text) {
|
|
13
|
+
if (!text) return '';
|
|
14
|
+
return text
|
|
15
|
+
.replace(/&/g, '&')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>')
|
|
18
|
+
.replace(/"/g, '"');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render an element to HTML based on its semantic type
|
|
23
|
+
*/
|
|
24
|
+
function renderElement(el) {
|
|
25
|
+
const text = escapeHtml(el.innerText);
|
|
26
|
+
|
|
27
|
+
switch (el.semantic) {
|
|
28
|
+
case 'title':
|
|
29
|
+
return text ? `<p><strong>${text}</strong></p>` : '';
|
|
30
|
+
case 'description':
|
|
31
|
+
return text ? `<p>${text}</p>` : '';
|
|
32
|
+
case 'heading':
|
|
33
|
+
return text ? `<h4>${text}</h4>` : '';
|
|
34
|
+
case 'subheading':
|
|
35
|
+
return text ? `<h5>${text}</h5>` : '';
|
|
36
|
+
case 'paragraph':
|
|
37
|
+
return text ? `<p>${text}</p>` : '';
|
|
38
|
+
case 'callout':
|
|
39
|
+
return text ? `<blockquote>${text}</blockquote>` : '';
|
|
40
|
+
case 'list-item':
|
|
41
|
+
return text ? `<li>${text}</li>` : '';
|
|
42
|
+
|
|
43
|
+
// Pattern layouts - extract child content for readability
|
|
44
|
+
case 'intro-cards':
|
|
45
|
+
return renderPatternCards(el, 'intro-card');
|
|
46
|
+
case 'steps':
|
|
47
|
+
return renderPatternSteps(el);
|
|
48
|
+
case 'features':
|
|
49
|
+
return renderPatternCards(el, 'feature');
|
|
50
|
+
case 'comparison':
|
|
51
|
+
return renderPatternComparison(el);
|
|
52
|
+
case 'timeline':
|
|
53
|
+
return renderPatternTimeline(el);
|
|
54
|
+
case 'stats':
|
|
55
|
+
return renderPatternStats(el);
|
|
56
|
+
case 'checklist':
|
|
57
|
+
return renderPatternChecklist(el);
|
|
58
|
+
case 'hero':
|
|
59
|
+
return renderPatternHero(el);
|
|
60
|
+
case 'quote':
|
|
61
|
+
return text ? `<blockquote class="pattern-quote">${text}</blockquote>` : '';
|
|
62
|
+
case 'content-image':
|
|
63
|
+
// Just render the text content, images are visual
|
|
64
|
+
return text ? `<p>${text}</p>` : '';
|
|
65
|
+
|
|
66
|
+
case 'accordion':
|
|
67
|
+
if (!el.children || el.children.length === 0) return '';
|
|
68
|
+
let html = '<div class="accordion-content">';
|
|
69
|
+
for (const panel of el.children) {
|
|
70
|
+
const title = escapeHtml(panel.attributes?.['data-title']) || 'Untitled';
|
|
71
|
+
const content = escapeHtml(panel.innerText) || '';
|
|
72
|
+
html += `<details><summary>${title}</summary><div class="accordion-panel">${content}</div></details>`;
|
|
73
|
+
}
|
|
74
|
+
html += '</div>';
|
|
75
|
+
return html;
|
|
76
|
+
case 'accordion-panel':
|
|
77
|
+
return ''; // Handled by parent
|
|
78
|
+
case 'tabs':
|
|
79
|
+
return renderPatternTabs(el);
|
|
80
|
+
case 'card':
|
|
81
|
+
case 'flip-card':
|
|
82
|
+
return ''; // Handled by parent
|
|
83
|
+
default:
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Render intro-cards or feature cards as a clean list
|
|
90
|
+
*/
|
|
91
|
+
function renderPatternCards(el, childClass) {
|
|
92
|
+
if (!el.children || el.children.length === 0) return '';
|
|
93
|
+
|
|
94
|
+
let html = '<div class="pattern-cards">';
|
|
95
|
+
for (const child of el.children) {
|
|
96
|
+
// Find cards within this pattern
|
|
97
|
+
if (child.className?.includes(childClass) || child.className?.includes('card')) {
|
|
98
|
+
const title = getChildHeading(child);
|
|
99
|
+
const content = getChildParagraph(child);
|
|
100
|
+
if (title || content) {
|
|
101
|
+
html += '<div class="pattern-card-item">';
|
|
102
|
+
if (title) html += `<h5>${escapeHtml(title)}</h5>`;
|
|
103
|
+
if (content) html += `<p>${escapeHtml(content)}</p>`;
|
|
104
|
+
html += '</div>';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
html += '</div>';
|
|
109
|
+
return html;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Render steps pattern as numbered list
|
|
114
|
+
*/
|
|
115
|
+
function renderPatternSteps(el) {
|
|
116
|
+
if (!el.children || el.children.length === 0) return '';
|
|
117
|
+
|
|
118
|
+
let html = '<ol class="pattern-steps">';
|
|
119
|
+
for (const child of el.children) {
|
|
120
|
+
if (child.className?.includes('step')) {
|
|
121
|
+
const title = getChildHeading(child);
|
|
122
|
+
const content = getChildParagraph(child);
|
|
123
|
+
html += '<li>';
|
|
124
|
+
if (title) html += `<strong>${escapeHtml(title)}</strong> - `;
|
|
125
|
+
if (content) html += escapeHtml(content);
|
|
126
|
+
html += '</li>';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
html += '</ol>';
|
|
130
|
+
return html;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Render timeline as sequential entries
|
|
135
|
+
*/
|
|
136
|
+
function renderPatternTimeline(el) {
|
|
137
|
+
if (!el.children || el.children.length === 0) return '';
|
|
138
|
+
|
|
139
|
+
let html = '<div class="pattern-timeline">';
|
|
140
|
+
for (const child of el.children) {
|
|
141
|
+
if (child.className?.includes('event') || child.className?.includes('timeline')) {
|
|
142
|
+
const date = child.attributes?.['data-date'] || child.attributes?.['data-year'] || '';
|
|
143
|
+
const title = getChildHeading(child);
|
|
144
|
+
const content = getChildParagraph(child);
|
|
145
|
+
html += '<div class="timeline-entry">';
|
|
146
|
+
if (date) html += `<span class="timeline-date"><strong>${escapeHtml(date)}</strong></span> `;
|
|
147
|
+
if (title) html += `<span class="timeline-title">${escapeHtml(title)}</span>: `;
|
|
148
|
+
if (content) html += `<span class="timeline-content">${escapeHtml(content)}</span>`;
|
|
149
|
+
html += '</div>';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
html += '</div>';
|
|
153
|
+
return html;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Render comparison as two-column display
|
|
158
|
+
*/
|
|
159
|
+
function renderPatternComparison(el) {
|
|
160
|
+
if (!el.children || el.children.length < 2) return '';
|
|
161
|
+
|
|
162
|
+
let html = '<div class="pattern-comparison">';
|
|
163
|
+
for (const child of el.children) {
|
|
164
|
+
const title = getChildHeading(child);
|
|
165
|
+
const items = getChildListItems(child);
|
|
166
|
+
html += '<div class="comparison-column">';
|
|
167
|
+
if (title) html += `<h5>${escapeHtml(title)}</h5>`;
|
|
168
|
+
if (items.length > 0) {
|
|
169
|
+
html += '<ul>';
|
|
170
|
+
for (const item of items) {
|
|
171
|
+
html += `<li>${escapeHtml(item)}</li>`;
|
|
172
|
+
}
|
|
173
|
+
html += '</ul>';
|
|
174
|
+
}
|
|
175
|
+
html += '</div>';
|
|
176
|
+
}
|
|
177
|
+
html += '</div>';
|
|
178
|
+
return html;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Render stats as key metrics
|
|
183
|
+
*/
|
|
184
|
+
function renderPatternStats(el) {
|
|
185
|
+
if (!el.children || el.children.length === 0) return '';
|
|
186
|
+
|
|
187
|
+
let html = '<div class="pattern-stats">';
|
|
188
|
+
for (const child of el.children) {
|
|
189
|
+
if (child.className?.includes('stat')) {
|
|
190
|
+
const value = getChildByClass(child, 'stat-value') || getChildHeading(child);
|
|
191
|
+
const label = getChildByClass(child, 'stat-label') || getChildParagraph(child);
|
|
192
|
+
if (value || label) {
|
|
193
|
+
html += '<div class="stat-item">';
|
|
194
|
+
if (value) html += `<strong>${escapeHtml(value)}</strong>`;
|
|
195
|
+
if (label) html += ` - ${escapeHtml(label)}`;
|
|
196
|
+
html += '</div>';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
html += '</div>';
|
|
201
|
+
return html;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Render checklist as bullet points
|
|
206
|
+
*/
|
|
207
|
+
function renderPatternChecklist(el) {
|
|
208
|
+
const items = getChildListItems(el);
|
|
209
|
+
if (items.length === 0) return '';
|
|
210
|
+
|
|
211
|
+
let html = '<ul class="pattern-checklist">';
|
|
212
|
+
for (const item of items) {
|
|
213
|
+
html += `<li>☑ ${escapeHtml(item)}</li>`;
|
|
214
|
+
}
|
|
215
|
+
html += '</ul>';
|
|
216
|
+
return html;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Render hero as prominent heading
|
|
221
|
+
*/
|
|
222
|
+
function renderPatternHero(el) {
|
|
223
|
+
const title = getChildHeading(el);
|
|
224
|
+
const content = getChildParagraph(el);
|
|
225
|
+
if (!title && !content) return '';
|
|
226
|
+
|
|
227
|
+
let html = '<div class="pattern-hero">';
|
|
228
|
+
if (title) html += `<h3>${escapeHtml(title)}</h3>`;
|
|
229
|
+
if (content) html += `<p>${escapeHtml(content)}</p>`;
|
|
230
|
+
html += '</div>';
|
|
231
|
+
return html;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Render tabs content
|
|
236
|
+
*/
|
|
237
|
+
function renderPatternTabs(el) {
|
|
238
|
+
if (!el.children || el.children.length === 0) return '';
|
|
239
|
+
|
|
240
|
+
let html = '<div class="pattern-tabs">';
|
|
241
|
+
for (const child of el.children) {
|
|
242
|
+
const title = child.attributes?.['data-tab'] || child.attributes?.['data-title'] || 'Tab';
|
|
243
|
+
const content = escapeHtml(child.innerText) || '';
|
|
244
|
+
html += `<details><summary>${escapeHtml(title)}</summary><div class="tab-content">${content}</div></details>`;
|
|
245
|
+
}
|
|
246
|
+
html += '</div>';
|
|
247
|
+
return html;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Helper functions to extract child content
|
|
251
|
+
function getChildHeading(el) {
|
|
252
|
+
const heading = el.children?.find(c => ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(c.tag));
|
|
253
|
+
return heading?.innerText || '';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getChildParagraph(el) {
|
|
257
|
+
const para = el.children?.find(c => c.tag === 'p');
|
|
258
|
+
return para?.innerText || '';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function getChildListItems(el) {
|
|
262
|
+
return el.children?.filter(c => c.tag === 'li').map(c => c.innerText || '') || [];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function getChildByClass(el, className) {
|
|
266
|
+
const child = el.children?.find(c => c.className?.includes(className));
|
|
267
|
+
return child?.innerText || '';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Render elements array to HTML
|
|
272
|
+
*/
|
|
273
|
+
function renderElements(elements, skipHeader = true) {
|
|
274
|
+
const parts = [];
|
|
275
|
+
let inList = false;
|
|
276
|
+
|
|
277
|
+
for (const el of elements) {
|
|
278
|
+
if (skipHeader && (el.semantic === 'title' || el.semantic === 'description')) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const isListItem = el.semantic === 'list-item';
|
|
283
|
+
|
|
284
|
+
if (isListItem && !inList) {
|
|
285
|
+
parts.push('<ul>');
|
|
286
|
+
inList = true;
|
|
287
|
+
} else if (!isListItem && inList) {
|
|
288
|
+
parts.push('</ul>');
|
|
289
|
+
inList = false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const html = renderElement(el);
|
|
293
|
+
if (html) parts.push(html);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (inList) parts.push('</ul>');
|
|
297
|
+
return parts.join('\n');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Render course header/metadata
|
|
302
|
+
*/
|
|
303
|
+
function renderMetadata(config) {
|
|
304
|
+
const meta = config.metadata || {};
|
|
305
|
+
const title = meta.title || 'CourseCode';
|
|
306
|
+
const desc = meta.description || '';
|
|
307
|
+
|
|
308
|
+
let html = '<div class="content-hero">';
|
|
309
|
+
html += `<h1 id="course-title">${escapeHtml(title)}</h1>`;
|
|
310
|
+
if (desc) {
|
|
311
|
+
html += `<p class="content-hero-description">${escapeHtml(desc)}</p>`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const details = [];
|
|
315
|
+
if (meta.version) details.push(`<span class="meta-item"><span class="meta-label">Version</span> ${escapeHtml(meta.version)}</span>`);
|
|
316
|
+
if (meta.author) details.push(`<span class="meta-item"><span class="meta-label">Author</span> ${escapeHtml(meta.author)}</span>`);
|
|
317
|
+
if (meta.language) details.push(`<span class="meta-item"><span class="meta-label">Language</span> ${escapeHtml(meta.language)}</span>`);
|
|
318
|
+
|
|
319
|
+
if (details.length > 0) {
|
|
320
|
+
html += `<div class="content-hero-meta">${details.join('')}</div>`;
|
|
321
|
+
}
|
|
322
|
+
html += '</div>';
|
|
323
|
+
|
|
324
|
+
return html;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Render course structure overview
|
|
329
|
+
*/
|
|
330
|
+
function renderStructureOverview(structure, depth = 0) {
|
|
331
|
+
const items = [];
|
|
332
|
+
|
|
333
|
+
for (const item of structure) {
|
|
334
|
+
const typeClass = item.type === 'section' ? 'toc-section' :
|
|
335
|
+
item.type === 'assessment' ? 'toc-assessment' : 'toc-slide';
|
|
336
|
+
const typeLabel = item.type === 'section' ? 'Section' :
|
|
337
|
+
item.type === 'assessment' ? 'Assessment' : 'Slide';
|
|
338
|
+
const title = item.title || item.menu?.label || item.id;
|
|
339
|
+
const anchor = `slide-${item.id}`;
|
|
340
|
+
|
|
341
|
+
let entry = `<li class="toc-item ${typeClass}">`;
|
|
342
|
+
entry += `<a href="#${anchor}" class="toc-link">`;
|
|
343
|
+
entry += `<span class="toc-type-badge">${typeLabel}</span>`;
|
|
344
|
+
entry += `<span class="toc-title">${escapeHtml(title)}</span>`;
|
|
345
|
+
|
|
346
|
+
// Add tags
|
|
347
|
+
const tags = [];
|
|
348
|
+
if (item.engagement?.required) tags.push('Engagement');
|
|
349
|
+
if (item.navigation?.gating) tags.push('Gated');
|
|
350
|
+
if (tags.length > 0) {
|
|
351
|
+
entry += `<span class="toc-tags">${tags.map(t => `<span class="toc-tag">${t}</span>`).join('')}</span>`;
|
|
352
|
+
}
|
|
353
|
+
entry += '</a>';
|
|
354
|
+
|
|
355
|
+
// Recurse for sections
|
|
356
|
+
if (item.children && item.children.length > 0) {
|
|
357
|
+
entry += '<ul class="toc-children">' + renderStructureOverview(item.children, depth + 1) + '</ul>';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
entry += '</li>';
|
|
361
|
+
items.push(entry);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return items.join('\n');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Render an interaction to HTML
|
|
369
|
+
*/
|
|
370
|
+
function renderInteractionHtml(interaction, options = {}) {
|
|
371
|
+
const { includeAnswers = true, includeFeedback = true } = options;
|
|
372
|
+
|
|
373
|
+
const typeLabel = interaction.type?.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) || 'Interaction';
|
|
374
|
+
const id = interaction.id || 'unknown';
|
|
375
|
+
|
|
376
|
+
let html = '<div class="interaction-card">';
|
|
377
|
+
html += '<div class="interaction-header">';
|
|
378
|
+
html += `<span class="interaction-type-badge">${escapeHtml(typeLabel)}</span>`;
|
|
379
|
+
html += `<span class="interaction-id"><code>${escapeHtml(id)}</code></span>`;
|
|
380
|
+
html += '</div>';
|
|
381
|
+
html += '<div class="interaction-body">';
|
|
382
|
+
|
|
383
|
+
// Prompt/Question
|
|
384
|
+
if (interaction.prompt) {
|
|
385
|
+
html += `<div class="interaction-prompt">${escapeHtml(interaction.prompt)}</div>`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Options for choice-based interactions
|
|
389
|
+
if (interaction.options && interaction.options.length > 0) {
|
|
390
|
+
html += '<ul class="interaction-options">';
|
|
391
|
+
for (const opt of interaction.options) {
|
|
392
|
+
const isCorrect = includeAnswers && opt.correct;
|
|
393
|
+
const correctClass = isCorrect ? 'option-correct' : '';
|
|
394
|
+
html += `<li class="interaction-option ${correctClass}">`;
|
|
395
|
+
html += escapeHtml(opt.text || opt.label || opt);
|
|
396
|
+
if (isCorrect) html += ' <span class="correct-indicator">✓</span>';
|
|
397
|
+
html += '</li>';
|
|
398
|
+
}
|
|
399
|
+
html += '</ul>';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Correct answer for non-choice interactions
|
|
403
|
+
if (includeAnswers && interaction.correctAnswer && !interaction.options) {
|
|
404
|
+
html += `<div class="interaction-answer"><strong>Answer:</strong> <code>${escapeHtml(String(interaction.correctAnswer))}</code></div>`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Feedback
|
|
408
|
+
if (includeFeedback) {
|
|
409
|
+
if (interaction.feedback?.correct) {
|
|
410
|
+
html += `<div class="interaction-feedback feedback-correct"><span class="feedback-label">Correct:</span> ${escapeHtml(interaction.feedback.correct)}</div>`;
|
|
411
|
+
}
|
|
412
|
+
if (interaction.feedback?.incorrect) {
|
|
413
|
+
html += `<div class="interaction-feedback feedback-incorrect"><span class="feedback-label">Incorrect:</span> ${escapeHtml(interaction.feedback.incorrect)}</div>`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
html += '</div></div>';
|
|
418
|
+
return html;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Render a slide to HTML
|
|
423
|
+
*/
|
|
424
|
+
function renderSlide(item, parsedData, options = {}) {
|
|
425
|
+
const { includeNarration = true, includeAnswers = true, includeFeedback = true } = options;
|
|
426
|
+
const title = item.title || item.menu?.label || item.id;
|
|
427
|
+
const typeClass = item.type === 'assessment' ? 'slide-card-assessment' : 'slide-card-standard';
|
|
428
|
+
const typeLabel = item.type === 'assessment' ? 'Assessment' : 'Slide';
|
|
429
|
+
|
|
430
|
+
let html = `<span id="slide-${item.id}"></span>`;
|
|
431
|
+
html += `<article class="slide-card ${typeClass}">`;
|
|
432
|
+
html += '<header class="slide-card-header">';
|
|
433
|
+
html += '<div class="slide-card-title-row">';
|
|
434
|
+
html += `<span class="slide-type-badge">${typeLabel}</span>`;
|
|
435
|
+
html += `<h2 class="slide-card-title">${escapeHtml(title)}</h2>`;
|
|
436
|
+
html += '</div>';
|
|
437
|
+
html += '<div class="slide-card-meta">';
|
|
438
|
+
html += `<span class="slide-meta-item"><span class="meta-label">ID</span> <code>${item.id}</code></span>`;
|
|
439
|
+
if (item.component) {
|
|
440
|
+
html += `<span class="slide-meta-item"><span class="meta-label">Component</span> <code>${item.component}</code></span>`;
|
|
441
|
+
}
|
|
442
|
+
html += '</div>';
|
|
443
|
+
html += '</header>';
|
|
444
|
+
|
|
445
|
+
html += '<div class="slide-card-body">';
|
|
446
|
+
|
|
447
|
+
if (parsedData) {
|
|
448
|
+
// Header content
|
|
449
|
+
if (parsedData.header?.title || parsedData.header?.description) {
|
|
450
|
+
html += '<div class="slide-content-header">';
|
|
451
|
+
if (parsedData.header.title) {
|
|
452
|
+
html += `<h3 class="slide-content-title">${escapeHtml(parsedData.header.title)}</h3>`;
|
|
453
|
+
}
|
|
454
|
+
if (parsedData.header.description) {
|
|
455
|
+
html += `<p class="slide-content-description">${escapeHtml(parsedData.header.description)}</p>`;
|
|
456
|
+
}
|
|
457
|
+
html += '</div>';
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Elements
|
|
461
|
+
if (parsedData.elements && parsedData.elements.length > 0) {
|
|
462
|
+
html += '<div class="slide-elements">';
|
|
463
|
+
html += renderElements(parsedData.elements);
|
|
464
|
+
html += '</div>';
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Interactions
|
|
468
|
+
if (parsedData.interactions && parsedData.interactions.length > 0) {
|
|
469
|
+
html += '<div class="slide-interactions">';
|
|
470
|
+
html += `<h3 class="section-label">Interactions <span class="count-badge">${parsedData.interactions.length}</span></h3>`;
|
|
471
|
+
for (const interaction of parsedData.interactions) {
|
|
472
|
+
html += renderInteractionHtml(interaction, { includeAnswers, includeFeedback });
|
|
473
|
+
}
|
|
474
|
+
html += '</div>';
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Narration
|
|
478
|
+
if (includeNarration && parsedData.narration) {
|
|
479
|
+
html += '<div class="slide-narration">';
|
|
480
|
+
html += '<h3 class="section-label narration-header">Narration</h3>';
|
|
481
|
+
html += '<div class="narration-section">';
|
|
482
|
+
for (const [key, text] of Object.entries(parsedData.narration)) {
|
|
483
|
+
if (Object.keys(parsedData.narration).length > 1 && key !== 'slide') {
|
|
484
|
+
html += `<p class="narration-key"><strong>${escapeHtml(key)}:</strong></p>`;
|
|
485
|
+
}
|
|
486
|
+
html += `<p>${escapeHtml(text)}</p>`;
|
|
487
|
+
}
|
|
488
|
+
html += '</div>';
|
|
489
|
+
html += '</div>';
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
html += '<p class="no-content"><em>No content available</em></p>';
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
html += '</div>';
|
|
496
|
+
html += '<footer class="slide-card-footer"><a href="#course-structure" class="back-to-toc">↑ Back to Course Structure</a></footer>';
|
|
497
|
+
html += '</article>';
|
|
498
|
+
return html;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Render a section to HTML
|
|
503
|
+
*/
|
|
504
|
+
function renderSection(section, slides, options) {
|
|
505
|
+
const title = section.title || section.menu?.label || section.id;
|
|
506
|
+
let html = `<span id="slide-${section.id}"></span>`;
|
|
507
|
+
html += '<div class="section-container">';
|
|
508
|
+
html += '<div class="section-header">';
|
|
509
|
+
html += '<span class="section-icon">📂</span>';
|
|
510
|
+
html += `<h2 class="section-title">${escapeHtml(title)}</h2>`;
|
|
511
|
+
html += '</div>';
|
|
512
|
+
html += '<div class="section-slides">';
|
|
513
|
+
|
|
514
|
+
if (section.children) {
|
|
515
|
+
for (const child of section.children) {
|
|
516
|
+
if (child.type === 'slide' || child.type === 'assessment') {
|
|
517
|
+
const parsedData = slides[child.id] || null;
|
|
518
|
+
html += renderSlide(child, parsedData, options);
|
|
519
|
+
} else if (child.type === 'section') {
|
|
520
|
+
html += renderSection(child, slides, options);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
html += '</div></div>';
|
|
526
|
+
return html;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Generate course content HTML (server-side rendering)
|
|
531
|
+
* Used by preview-server to embed content directly in stub player
|
|
532
|
+
* @param {Object} options - { coursePath, includeNarration }
|
|
533
|
+
* @returns {Promise<string|null>} HTML content or null on error
|
|
534
|
+
*/
|
|
535
|
+
export async function generateContentHtml(options = {}) {
|
|
536
|
+
const {
|
|
537
|
+
coursePath = './course',
|
|
538
|
+
includeNarration = true
|
|
539
|
+
} = options;
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
const courseData = await parseCourse(coursePath);
|
|
543
|
+
const { config, slides } = courseData;
|
|
544
|
+
const structure = config.structure || [];
|
|
545
|
+
|
|
546
|
+
const renderOptions = {
|
|
547
|
+
includeNarration,
|
|
548
|
+
includeAnswers: true,
|
|
549
|
+
includeFeedback: true
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
let html = '<div class="course-content">';
|
|
553
|
+
|
|
554
|
+
// Metadata
|
|
555
|
+
html += renderMetadata(config);
|
|
556
|
+
html += '<hr>';
|
|
557
|
+
|
|
558
|
+
// Structure overview
|
|
559
|
+
html += '<nav class="toc-section">';
|
|
560
|
+
html += '<h2 id="course-structure" class="toc-heading">Course Structure</h2>';
|
|
561
|
+
html += '<ul class="toc-list">';
|
|
562
|
+
html += renderStructureOverview(structure);
|
|
563
|
+
html += '</ul>';
|
|
564
|
+
html += '</nav>';
|
|
565
|
+
|
|
566
|
+
// Process each item
|
|
567
|
+
for (const item of structure) {
|
|
568
|
+
if (item.type === 'slide') {
|
|
569
|
+
const parsedData = slides[item.id] || null;
|
|
570
|
+
html += renderSlide(item, parsedData, renderOptions);
|
|
571
|
+
} else if (item.type === 'assessment') {
|
|
572
|
+
const parsedData = slides[item.id] || null;
|
|
573
|
+
html += renderSlide(item, parsedData, renderOptions);
|
|
574
|
+
} else if (item.type === 'section') {
|
|
575
|
+
html += renderSection(item, slides, renderOptions);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
html += '</div>';
|
|
580
|
+
return html;
|
|
581
|
+
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.error('Failed to generate content HTML:', error.message);
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
}
|