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,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access Control - validates client tokens for multi-tenant CDN hosting
|
|
3
|
+
*
|
|
4
|
+
* Checks URL params (clientId, token) against course config.
|
|
5
|
+
* Used by external hosting modes (scorm*-proxy, cmi5-remote).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { courseConfig } from '../../../course/course-config.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Constant-time string comparison to prevent timing side-channel attacks.
|
|
12
|
+
* Returns false for mismatched lengths without leaking which byte differs.
|
|
13
|
+
* @param {string} a
|
|
14
|
+
* @param {string} b
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function timingSafeEqual(a, b) {
|
|
18
|
+
if (typeof a !== 'string' || typeof b !== 'string') return false;
|
|
19
|
+
if (a.length !== b.length) return false;
|
|
20
|
+
let mismatch = 0;
|
|
21
|
+
for (let i = 0; i < a.length; i++) {
|
|
22
|
+
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
23
|
+
}
|
|
24
|
+
return mismatch === 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate access based on URL token
|
|
29
|
+
* @returns {{ valid: boolean, clientId: string | null, error: string | null }}
|
|
30
|
+
*/
|
|
31
|
+
export function validateAccess() {
|
|
32
|
+
const accessControl = courseConfig.accessControl;
|
|
33
|
+
|
|
34
|
+
// If no access control clients configured, allow all
|
|
35
|
+
if (!accessControl?.clients) {
|
|
36
|
+
return { valid: true, clientId: null, error: null };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const params = new URLSearchParams(window.location.search);
|
|
40
|
+
const clientId = params.get('clientId');
|
|
41
|
+
const token = params.get('token');
|
|
42
|
+
|
|
43
|
+
// Missing credentials
|
|
44
|
+
if (!clientId || !token) {
|
|
45
|
+
return {
|
|
46
|
+
valid: false,
|
|
47
|
+
clientId: null,
|
|
48
|
+
error: 'Missing clientId or token'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Unknown client
|
|
53
|
+
const client = accessControl.clients?.[clientId];
|
|
54
|
+
if (!client) {
|
|
55
|
+
return {
|
|
56
|
+
valid: false,
|
|
57
|
+
clientId,
|
|
58
|
+
error: `Unknown client: ${clientId}`
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Invalid token (constant-time comparison)
|
|
63
|
+
if (!timingSafeEqual(client.token, token)) {
|
|
64
|
+
return {
|
|
65
|
+
valid: false,
|
|
66
|
+
clientId,
|
|
67
|
+
error: 'Invalid token'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Success
|
|
72
|
+
return { valid: true, clientId, error: null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Show unauthorized screen and halt initialization
|
|
77
|
+
* @param {string} error - Error message to display
|
|
78
|
+
*/
|
|
79
|
+
export function showUnauthorizedScreen(_error) {
|
|
80
|
+
document.body.innerHTML = `
|
|
81
|
+
<div style="
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
min-height: 100vh;
|
|
86
|
+
font-family: system-ui, sans-serif;
|
|
87
|
+
background: #1a1a2e;
|
|
88
|
+
color: #fff;
|
|
89
|
+
">
|
|
90
|
+
<div style="text-align: center; padding: 2rem;">
|
|
91
|
+
<div style="font-size: 4rem; margin-bottom: 1rem;">🔒</div>
|
|
92
|
+
<h1 style="margin: 0 0 0.5rem; font-size: 1.5rem;">Access Denied</h1>
|
|
93
|
+
<p style="margin: 0; opacity: 0.7; font-size: 0.9rem;">
|
|
94
|
+
This course is not authorized for this deployment.
|
|
95
|
+
</p>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breakpoint Manager - Responsive breakpoint detection and class management
|
|
3
|
+
* ============================================================================
|
|
4
|
+
*
|
|
5
|
+
* PURPOSE: Dynamically applies breakpoint CSS classes to the <html> element
|
|
6
|
+
* based on viewport width, enabling responsive styles throughout the framework.
|
|
7
|
+
*
|
|
8
|
+
* BREAKPOINTS (matching design-tokens.css):
|
|
9
|
+
* - Large Desktop: >= 1440px → adds .bp-min-large-desktop
|
|
10
|
+
* - Desktop: < 1440px → adds .bp-max-desktop
|
|
11
|
+
* - Tablet Land: < 1200px → adds .bp-max-tablet-landscape
|
|
12
|
+
* - Tablet Port: < 1024px → adds .bp-max-tablet-portrait
|
|
13
|
+
* - Mobile Land: < 768px → adds .bp-max-mobile-landscape
|
|
14
|
+
* - Mobile Port: < 480px → adds .bp-max-mobile-portrait
|
|
15
|
+
*
|
|
16
|
+
* USAGE:
|
|
17
|
+
* import { breakpointManager } from './utilities/breakpoint-manager.js';
|
|
18
|
+
*
|
|
19
|
+
* // Initialize (called automatically in main.js)
|
|
20
|
+
* breakpointManager.init();
|
|
21
|
+
*
|
|
22
|
+
* // Get current breakpoint name
|
|
23
|
+
* const bp = breakpointManager.getCurrentBreakpoint(); // e.g., 'tablet-portrait'
|
|
24
|
+
*
|
|
25
|
+
* // Check if at or below a breakpoint
|
|
26
|
+
* if (breakpointManager.isAtMost('tablet-portrait')) { ... }
|
|
27
|
+
*
|
|
28
|
+
* // Listen for breakpoint changes
|
|
29
|
+
* breakpointManager.onChange((newBreakpoint, oldBreakpoint) => {
|
|
30
|
+
* console.log(`Changed from ${oldBreakpoint} to ${newBreakpoint}`);
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* DEPENDENCIES:
|
|
34
|
+
* - Requires: design-tokens.css breakpoint values
|
|
35
|
+
* - Used by: responsive.css (applies styles based on .bp-* classes)
|
|
36
|
+
*
|
|
37
|
+
* LMS COMPATIBILITY:
|
|
38
|
+
* - Works reliably in iframe contexts (Litmos, Cornerstone, etc.)
|
|
39
|
+
* - Uses standard DOM APIs (window.innerWidth, resize event)
|
|
40
|
+
* - Passive event listener for performance
|
|
41
|
+
*
|
|
42
|
+
* LAST UPDATED: 2024-12-10
|
|
43
|
+
* ============================================================================
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { logger } from './logger.js';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Breakpoint definitions ordered from largest to smallest
|
|
50
|
+
* Order matters for determining the "current" breakpoint
|
|
51
|
+
*/
|
|
52
|
+
const BREAKPOINTS = [
|
|
53
|
+
{ name: 'large-desktop', minWidth: 1440, className: 'bp-min-large-desktop' },
|
|
54
|
+
{ name: 'desktop', maxWidth: 1439, className: 'bp-max-desktop' },
|
|
55
|
+
{ name: 'tablet-landscape', maxWidth: 1199, className: 'bp-max-tablet-landscape' },
|
|
56
|
+
{ name: 'tablet-portrait', maxWidth: 1023, className: 'bp-max-tablet-portrait' },
|
|
57
|
+
{ name: 'mobile-landscape', maxWidth: 767, className: 'bp-max-mobile-landscape' },
|
|
58
|
+
{ name: 'mobile-portrait', maxWidth: 479, className: 'bp-max-mobile-portrait' }
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* All possible breakpoint class names for easy removal
|
|
63
|
+
*/
|
|
64
|
+
const ALL_BREAKPOINT_CLASSES = BREAKPOINTS.map(bp => bp.className);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Breakpoint Manager singleton
|
|
68
|
+
*/
|
|
69
|
+
class BreakpointManager {
|
|
70
|
+
constructor() {
|
|
71
|
+
this._initialized = false;
|
|
72
|
+
this._currentBreakpoint = null;
|
|
73
|
+
this._listeners = [];
|
|
74
|
+
this._resizeTimeout = null;
|
|
75
|
+
|
|
76
|
+
// Bind methods for event listener
|
|
77
|
+
this._handleResize = this._handleResize.bind(this);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Initialize the breakpoint manager
|
|
82
|
+
* Sets up resize listener and applies initial breakpoint classes
|
|
83
|
+
*/
|
|
84
|
+
init() {
|
|
85
|
+
if (this._initialized) {
|
|
86
|
+
logger.warn('[BreakpointManager] Already initialized');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Apply initial breakpoint classes
|
|
91
|
+
this._updateBreakpoints();
|
|
92
|
+
|
|
93
|
+
// Listen for resize with debouncing for performance
|
|
94
|
+
window.addEventListener('resize', this._handleResize, { passive: true });
|
|
95
|
+
|
|
96
|
+
this._initialized = true;
|
|
97
|
+
logger.debug('[BreakpointManager] Initialized, current breakpoint:', this._currentBreakpoint);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Clean up event listeners (useful for testing or cleanup)
|
|
102
|
+
*/
|
|
103
|
+
destroy() {
|
|
104
|
+
if (!this._initialized) return;
|
|
105
|
+
|
|
106
|
+
window.removeEventListener('resize', this._handleResize);
|
|
107
|
+
|
|
108
|
+
if (this._resizeTimeout) {
|
|
109
|
+
clearTimeout(this._resizeTimeout);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Remove all breakpoint classes
|
|
113
|
+
const html = document.documentElement;
|
|
114
|
+
html.classList.remove(...ALL_BREAKPOINT_CLASSES);
|
|
115
|
+
|
|
116
|
+
this._listeners = [];
|
|
117
|
+
this._initialized = false;
|
|
118
|
+
this._currentBreakpoint = null;
|
|
119
|
+
|
|
120
|
+
logger.debug('[BreakpointManager] Destroyed');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Handle resize events with debouncing
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
_handleResize() {
|
|
128
|
+
// Debounce resize events (16ms ≈ 60fps)
|
|
129
|
+
if (this._resizeTimeout) {
|
|
130
|
+
clearTimeout(this._resizeTimeout);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this._resizeTimeout = setTimeout(() => {
|
|
134
|
+
this._updateBreakpoints();
|
|
135
|
+
}, 16);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Update breakpoint classes on the <html> element
|
|
140
|
+
* @private
|
|
141
|
+
*/
|
|
142
|
+
_updateBreakpoints() {
|
|
143
|
+
const width = window.innerWidth;
|
|
144
|
+
const html = document.documentElement;
|
|
145
|
+
const oldBreakpoint = this._currentBreakpoint;
|
|
146
|
+
|
|
147
|
+
// Remove all existing breakpoint classes
|
|
148
|
+
html.classList.remove(...ALL_BREAKPOINT_CLASSES);
|
|
149
|
+
|
|
150
|
+
// Determine which breakpoint classes to add
|
|
151
|
+
// Multiple classes can be active (cascade down)
|
|
152
|
+
let primaryBreakpoint = null;
|
|
153
|
+
|
|
154
|
+
for (const bp of BREAKPOINTS) {
|
|
155
|
+
let shouldAdd = false;
|
|
156
|
+
|
|
157
|
+
if (bp.minWidth !== undefined && width >= bp.minWidth) {
|
|
158
|
+
shouldAdd = true;
|
|
159
|
+
} else if (bp.maxWidth !== undefined && width <= bp.maxWidth) {
|
|
160
|
+
shouldAdd = true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (shouldAdd) {
|
|
164
|
+
html.classList.add(bp.className);
|
|
165
|
+
|
|
166
|
+
// The first matching breakpoint is the "primary" one
|
|
167
|
+
if (!primaryBreakpoint) {
|
|
168
|
+
primaryBreakpoint = bp.name;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this._currentBreakpoint = primaryBreakpoint;
|
|
174
|
+
|
|
175
|
+
// Notify listeners if breakpoint changed
|
|
176
|
+
if (oldBreakpoint !== this._currentBreakpoint) {
|
|
177
|
+
logger.debug('[BreakpointManager] Breakpoint changed:', oldBreakpoint, '→', this._currentBreakpoint);
|
|
178
|
+
this._notifyListeners(this._currentBreakpoint, oldBreakpoint);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Notify all registered listeners of breakpoint change
|
|
184
|
+
* @private
|
|
185
|
+
*/
|
|
186
|
+
_notifyListeners(newBreakpoint, oldBreakpoint) {
|
|
187
|
+
for (const callback of this._listeners) {
|
|
188
|
+
try {
|
|
189
|
+
callback(newBreakpoint, oldBreakpoint);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
logger.error('[BreakpointManager] Listener error:', error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get the current primary breakpoint name
|
|
198
|
+
* @returns {string|null} Current breakpoint name (e.g., 'tablet-portrait')
|
|
199
|
+
*/
|
|
200
|
+
getCurrentBreakpoint() {
|
|
201
|
+
return this._currentBreakpoint;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get the current viewport width
|
|
206
|
+
* @returns {number} Current viewport width in pixels
|
|
207
|
+
*/
|
|
208
|
+
getViewportWidth() {
|
|
209
|
+
return window.innerWidth;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Check if the current viewport is at or below a specific breakpoint
|
|
214
|
+
* @param {string} breakpointName - Name of breakpoint to check (e.g., 'tablet-portrait')
|
|
215
|
+
* @returns {boolean} True if viewport is at or below the specified breakpoint
|
|
216
|
+
*/
|
|
217
|
+
isAtMost(breakpointName) {
|
|
218
|
+
const bp = BREAKPOINTS.find(b => b.name === breakpointName);
|
|
219
|
+
if (!bp || bp.maxWidth === undefined) {
|
|
220
|
+
logger.warn('[BreakpointManager] Unknown breakpoint:', breakpointName);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return window.innerWidth <= bp.maxWidth;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Check if the current viewport is at or above a specific breakpoint
|
|
228
|
+
* @param {string} breakpointName - Name of breakpoint to check (e.g., 'large-desktop')
|
|
229
|
+
* @returns {boolean} True if viewport is at or above the specified breakpoint
|
|
230
|
+
*/
|
|
231
|
+
isAtLeast(breakpointName) {
|
|
232
|
+
const bp = BREAKPOINTS.find(b => b.name === breakpointName);
|
|
233
|
+
if (!bp) {
|
|
234
|
+
logger.warn('[BreakpointManager] Unknown breakpoint:', breakpointName);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (bp.minWidth !== undefined) {
|
|
239
|
+
return window.innerWidth >= bp.minWidth;
|
|
240
|
+
}
|
|
241
|
+
// For max-width breakpoints, "at least" means above their threshold
|
|
242
|
+
if (bp.maxWidth !== undefined) {
|
|
243
|
+
return window.innerWidth > bp.maxWidth;
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if viewport matches a mobile breakpoint (portrait or landscape)
|
|
250
|
+
* @returns {boolean} True if viewport is mobile-sized
|
|
251
|
+
*/
|
|
252
|
+
isMobile() {
|
|
253
|
+
return this.isAtMost('mobile-landscape');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Check if viewport matches a tablet breakpoint
|
|
258
|
+
* @returns {boolean} True if viewport is tablet-sized
|
|
259
|
+
*/
|
|
260
|
+
isTablet() {
|
|
261
|
+
const width = window.innerWidth;
|
|
262
|
+
return width >= 768 && width < 1200;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if viewport matches a desktop breakpoint
|
|
267
|
+
* @returns {boolean} True if viewport is desktop-sized
|
|
268
|
+
*/
|
|
269
|
+
isDesktop() {
|
|
270
|
+
return window.innerWidth >= 1200;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Register a callback for breakpoint changes
|
|
275
|
+
* @param {Function} callback - Function called with (newBreakpoint, oldBreakpoint)
|
|
276
|
+
* @returns {Function} Unsubscribe function
|
|
277
|
+
*/
|
|
278
|
+
onChange(callback) {
|
|
279
|
+
if (typeof callback !== 'function') {
|
|
280
|
+
logger.error('[BreakpointManager] onChange requires a function');
|
|
281
|
+
return () => {};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this._listeners.push(callback);
|
|
285
|
+
|
|
286
|
+
// Return unsubscribe function
|
|
287
|
+
return () => {
|
|
288
|
+
const index = this._listeners.indexOf(callback);
|
|
289
|
+
if (index > -1) {
|
|
290
|
+
this._listeners.splice(index, 1);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get all breakpoint definitions
|
|
297
|
+
* @returns {Array} Array of breakpoint objects
|
|
298
|
+
*/
|
|
299
|
+
getBreakpoints() {
|
|
300
|
+
return [...BREAKPOINTS];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Force a breakpoint update (useful after orientation changes)
|
|
305
|
+
*/
|
|
306
|
+
refresh() {
|
|
307
|
+
this._updateBreakpoints();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Export singleton instance
|
|
312
|
+
export const breakpointManager = new BreakpointManager();
|
|
313
|
+
|
|
314
|
+
// Also export class for testing purposes
|
|
315
|
+
export { BreakpointManager };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file canvas-slide.js
|
|
3
|
+
* @description Convenience helper for canvas layout authors.
|
|
4
|
+
* Wraps an HTML string and optional init callback into a valid slide export.
|
|
5
|
+
*
|
|
6
|
+
* Usage (in a slide .js file):
|
|
7
|
+
*
|
|
8
|
+
* const { canvasSlide } = CourseCode;
|
|
9
|
+
*
|
|
10
|
+
* export const slide = canvasSlide(`
|
|
11
|
+
* <style>.my-app { color: white; }</style>
|
|
12
|
+
* <div class="my-app">
|
|
13
|
+
* <h1>Hello</h1>
|
|
14
|
+
* <button id="next">Next</button>
|
|
15
|
+
* </div>
|
|
16
|
+
* `, (el, api) => {
|
|
17
|
+
* el.querySelector('#next').onclick = () => api.NavigationActions.goToNextAvailableSlide();
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* @param {string} html - The HTML content (can include <style> tags)
|
|
21
|
+
* @param {Function} [init] - Optional callback: (element, CourseCode) => void
|
|
22
|
+
* @returns {object} A valid slide export with render() method
|
|
23
|
+
*/
|
|
24
|
+
export function canvasSlide(html, init) {
|
|
25
|
+
return {
|
|
26
|
+
render() {
|
|
27
|
+
const el = document.createElement('div');
|
|
28
|
+
el.innerHTML = html;
|
|
29
|
+
if (init) {
|
|
30
|
+
init(el, window.CourseCode);
|
|
31
|
+
}
|
|
32
|
+
return el;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|