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,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file html-validators.js
|
|
3
|
+
* @description Runtime HTML validation for rendered slide content.
|
|
4
|
+
* Checks for common authoring issues that cause page reloads or break component wiring.
|
|
5
|
+
* Single consumer: view-manager.js (runs after each slide render).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// HTML Structure Validation Helpers
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates buttons inside forms have explicit type attributes to prevent page reloads.
|
|
14
|
+
* @param {HTMLElement} element - The element to validate
|
|
15
|
+
* @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
|
|
16
|
+
*/
|
|
17
|
+
export function validateFormButtons(element) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
const forms = element.querySelectorAll('form');
|
|
20
|
+
|
|
21
|
+
forms.forEach((form) => {
|
|
22
|
+
const buttons = form.querySelectorAll('button:not([type])');
|
|
23
|
+
if (buttons.length > 0) {
|
|
24
|
+
errors.push({
|
|
25
|
+
message: `Found ${buttons.length} button(s) inside a form without a 'type' attribute`,
|
|
26
|
+
context: {
|
|
27
|
+
form: form.outerHTML.substring(0, 200) + '...',
|
|
28
|
+
buttons: Array.from(buttons).map(btn => btn.outerHTML),
|
|
29
|
+
fix: 'Add type="button" to buttons that should not submit the form, or type="submit" for submit buttons.'
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
valid: errors.length === 0,
|
|
37
|
+
errors
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validates that buttons with data-action inside forms have explicit type attributes.
|
|
43
|
+
* @param {HTMLElement} element - The element to validate
|
|
44
|
+
* @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
|
|
45
|
+
*/
|
|
46
|
+
export function validateActionButtonsInForms(element) {
|
|
47
|
+
const errors = [];
|
|
48
|
+
const actionElements = element.querySelectorAll('[data-action]');
|
|
49
|
+
|
|
50
|
+
actionElements.forEach(actionEl => {
|
|
51
|
+
if (actionEl.tagName === 'BUTTON' && actionEl.closest('form') && !actionEl.hasAttribute('type')) {
|
|
52
|
+
errors.push({
|
|
53
|
+
message: 'Button with data-action inside a form is missing type attribute',
|
|
54
|
+
context: {
|
|
55
|
+
button: actionEl.outerHTML,
|
|
56
|
+
action: actionEl.getAttribute('data-action'),
|
|
57
|
+
fix: 'Add type="button" to prevent the button from submitting the form.'
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
valid: errors.length === 0,
|
|
65
|
+
errors
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validates that anchor tags don't have dangerous href values that cause page reloads.
|
|
71
|
+
* @param {HTMLElement} element - The element to validate
|
|
72
|
+
* @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
|
|
73
|
+
*/
|
|
74
|
+
export function validateAnchorTags(element) {
|
|
75
|
+
const errors = [];
|
|
76
|
+
const dangerousAnchors = element.querySelectorAll('a[href="#"], a[href=""]');
|
|
77
|
+
|
|
78
|
+
if (dangerousAnchors.length > 0) {
|
|
79
|
+
errors.push({
|
|
80
|
+
message: `Found ${dangerousAnchors.length} anchor tag(s) with dangerous href attribute`,
|
|
81
|
+
context: {
|
|
82
|
+
anchors: Array.from(dangerousAnchors).map(a => a.outerHTML),
|
|
83
|
+
fix: 'For internal navigation, use NavigationActions.goToSlide(). For other actions, use a <button type="button">. For external links, use a valid URL.'
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
valid: errors.length === 0,
|
|
90
|
+
errors
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// UI Component Validation Helpers
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Required data-action patterns for UI components.
|
|
100
|
+
* @constant {Object}
|
|
101
|
+
*/
|
|
102
|
+
export const UI_COMPONENT_ACTIONS = {
|
|
103
|
+
dropdown: {
|
|
104
|
+
trigger: 'toggle-dropdown',
|
|
105
|
+
item: 'select-item'
|
|
106
|
+
},
|
|
107
|
+
tabs: {
|
|
108
|
+
button: 'select-tab'
|
|
109
|
+
},
|
|
110
|
+
modal: {
|
|
111
|
+
close: 'close-modal'
|
|
112
|
+
},
|
|
113
|
+
interaction: {
|
|
114
|
+
check: 'check-answer',
|
|
115
|
+
reset: 'reset'
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validates dropdown components have required data-action attributes.
|
|
121
|
+
* @param {HTMLElement} element - The element to validate (should contain dropdowns)
|
|
122
|
+
* @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
|
|
123
|
+
*/
|
|
124
|
+
export function validateDropdownActions(element) {
|
|
125
|
+
const errors = [];
|
|
126
|
+
const dropdowns = element.querySelectorAll('.custom-dropdown, [class*="dropdown"]');
|
|
127
|
+
|
|
128
|
+
dropdowns.forEach(dropdown => {
|
|
129
|
+
// Check for dropdown triggers without data-action
|
|
130
|
+
const triggers = dropdown.querySelectorAll('.dropdown-trigger, [class*="dropdown-trigger"]');
|
|
131
|
+
triggers.forEach(trigger => {
|
|
132
|
+
if (!trigger.hasAttribute('data-action')) {
|
|
133
|
+
errors.push({
|
|
134
|
+
message: 'Dropdown trigger is missing data-action attribute',
|
|
135
|
+
context: {
|
|
136
|
+
element: trigger.outerHTML,
|
|
137
|
+
expected: `data-action="${UI_COMPONENT_ACTIONS.dropdown.trigger}"`,
|
|
138
|
+
fix: 'Add data-action="toggle-dropdown" to the dropdown trigger button.'
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
const action = trigger.getAttribute('data-action');
|
|
143
|
+
if (action !== UI_COMPONENT_ACTIONS.dropdown.trigger) {
|
|
144
|
+
errors.push({
|
|
145
|
+
message: `Dropdown trigger has incorrect data-action="${action}"`,
|
|
146
|
+
context: {
|
|
147
|
+
element: trigger.outerHTML,
|
|
148
|
+
actual: action,
|
|
149
|
+
expected: UI_COMPONENT_ACTIONS.dropdown.trigger,
|
|
150
|
+
fix: `Change to data-action="${UI_COMPONENT_ACTIONS.dropdown.trigger}".`
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Check for dropdown items without data-action
|
|
158
|
+
const items = dropdown.querySelectorAll('.dropdown-item, [class*="dropdown-item"]');
|
|
159
|
+
items.forEach(item => {
|
|
160
|
+
if (!item.hasAttribute('data-action')) {
|
|
161
|
+
errors.push({
|
|
162
|
+
message: 'Dropdown item is missing data-action attribute',
|
|
163
|
+
context: {
|
|
164
|
+
element: item.outerHTML,
|
|
165
|
+
expected: `data-action="${UI_COMPONENT_ACTIONS.dropdown.item}"`,
|
|
166
|
+
fix: 'Add data-action="select-item" to the dropdown item.'
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
const action = item.getAttribute('data-action');
|
|
171
|
+
if (action !== UI_COMPONENT_ACTIONS.dropdown.item) {
|
|
172
|
+
errors.push({
|
|
173
|
+
message: `Dropdown item has incorrect data-action="${action}"`,
|
|
174
|
+
context: {
|
|
175
|
+
element: item.outerHTML,
|
|
176
|
+
actual: action,
|
|
177
|
+
expected: UI_COMPONENT_ACTIONS.dropdown.item,
|
|
178
|
+
fix: `Change to data-action="${UI_COMPONENT_ACTIONS.dropdown.item}".`
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
valid: errors.length === 0,
|
|
188
|
+
errors
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Validates tab components have required data-action attributes.
|
|
194
|
+
* @param {HTMLElement} element - The element to validate (should contain tabs)
|
|
195
|
+
* @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
|
|
196
|
+
*/
|
|
197
|
+
export function validateTabActions(element) {
|
|
198
|
+
const errors = [];
|
|
199
|
+
|
|
200
|
+
// Look for tab containers
|
|
201
|
+
const tabContainers = element.querySelectorAll('.tabs, [role="tablist"]');
|
|
202
|
+
|
|
203
|
+
tabContainers.forEach(container => {
|
|
204
|
+
const tabButtons = container.querySelectorAll('.tab-button, [role="tab"]');
|
|
205
|
+
|
|
206
|
+
tabButtons.forEach(button => {
|
|
207
|
+
if (!button.hasAttribute('data-action')) {
|
|
208
|
+
errors.push({
|
|
209
|
+
message: 'Tab button is missing data-action attribute',
|
|
210
|
+
context: {
|
|
211
|
+
element: button.outerHTML,
|
|
212
|
+
expected: `data-action="${UI_COMPONENT_ACTIONS.tabs.button}"`,
|
|
213
|
+
fix: 'Add data-action="select-tab" to the tab button.'
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
} else {
|
|
217
|
+
const action = button.getAttribute('data-action');
|
|
218
|
+
if (action !== UI_COMPONENT_ACTIONS.tabs.button) {
|
|
219
|
+
errors.push({
|
|
220
|
+
message: `Tab button has incorrect data-action="${action}"`,
|
|
221
|
+
context: {
|
|
222
|
+
element: button.outerHTML,
|
|
223
|
+
actual: action,
|
|
224
|
+
expected: UI_COMPONENT_ACTIONS.tabs.button,
|
|
225
|
+
fix: `Change to data-action="${UI_COMPONENT_ACTIONS.tabs.button}".`
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
valid: errors.length === 0,
|
|
235
|
+
errors
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Validates modal components have required data-action attributes.
|
|
241
|
+
* @param {HTMLElement} element - The element to validate (should contain modals)
|
|
242
|
+
* @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
|
|
243
|
+
*/
|
|
244
|
+
export function validateModalActions(element) {
|
|
245
|
+
const errors = [];
|
|
246
|
+
const modals = element.querySelectorAll('.modal, [role="dialog"]');
|
|
247
|
+
|
|
248
|
+
modals.forEach(modal => {
|
|
249
|
+
// Check for close buttons (both .modal-close and any button meant to close)
|
|
250
|
+
const closeButtons = modal.querySelectorAll('.modal-close, button[aria-label*="close" i], button[aria-label*="dismiss" i]');
|
|
251
|
+
|
|
252
|
+
closeButtons.forEach(button => {
|
|
253
|
+
if (!button.hasAttribute('data-action')) {
|
|
254
|
+
errors.push({
|
|
255
|
+
message: 'Modal close button is missing data-action attribute',
|
|
256
|
+
context: {
|
|
257
|
+
element: button.outerHTML,
|
|
258
|
+
expected: `data-action="${UI_COMPONENT_ACTIONS.modal.close}"`,
|
|
259
|
+
fix: 'Add data-action="close-modal" to the modal close button.'
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
} else {
|
|
263
|
+
const action = button.getAttribute('data-action');
|
|
264
|
+
if (action !== UI_COMPONENT_ACTIONS.modal.close) {
|
|
265
|
+
errors.push({
|
|
266
|
+
message: `Modal close button has incorrect data-action="${action}"`,
|
|
267
|
+
context: {
|
|
268
|
+
element: button.outerHTML,
|
|
269
|
+
actual: action,
|
|
270
|
+
expected: UI_COMPONENT_ACTIONS.modal.close,
|
|
271
|
+
fix: `Change to data-action="${UI_COMPONENT_ACTIONS.modal.close}".`
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
valid: errors.length === 0,
|
|
281
|
+
errors
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Validates interaction components have required data-action attributes.
|
|
287
|
+
* @param {HTMLElement} element - The element to validate (should contain interactions)
|
|
288
|
+
* @returns {{valid: boolean, errors: Array<{message: string, context: Object}>}} Validation result
|
|
289
|
+
*/
|
|
290
|
+
export function validateInteractionActions(element) {
|
|
291
|
+
const errors = [];
|
|
292
|
+
|
|
293
|
+
// Look for interaction containers (common patterns)
|
|
294
|
+
const interactions = element.querySelectorAll('[data-interaction-type], .interaction, [class*="interaction-"]');
|
|
295
|
+
|
|
296
|
+
interactions.forEach(interaction => {
|
|
297
|
+
const interactionId = interaction.id || interaction.dataset.interaction || 'unknown';
|
|
298
|
+
|
|
299
|
+
// Check for check-answer buttons
|
|
300
|
+
const checkButtons = interaction.querySelectorAll('.check-answer, button[class*="check"]');
|
|
301
|
+
checkButtons.forEach(button => {
|
|
302
|
+
if (!button.hasAttribute('data-action')) {
|
|
303
|
+
errors.push({
|
|
304
|
+
message: 'Interaction check button is missing data-action attribute',
|
|
305
|
+
context: {
|
|
306
|
+
interactionId,
|
|
307
|
+
element: button.outerHTML,
|
|
308
|
+
expected: `data-action="${UI_COMPONENT_ACTIONS.interaction.check}"`,
|
|
309
|
+
fix: 'Add data-action="check-answer" and data-interaction="[id]" to the check button.'
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
const action = button.getAttribute('data-action');
|
|
314
|
+
if (action !== UI_COMPONENT_ACTIONS.interaction.check && action !== UI_COMPONENT_ACTIONS.interaction.reset) {
|
|
315
|
+
errors.push({
|
|
316
|
+
message: `Interaction button has incorrect data-action="${action}"`,
|
|
317
|
+
context: {
|
|
318
|
+
interactionId,
|
|
319
|
+
element: button.outerHTML,
|
|
320
|
+
actual: action,
|
|
321
|
+
expected: `"${UI_COMPONENT_ACTIONS.interaction.check}" or "${UI_COMPONENT_ACTIONS.interaction.reset}"`,
|
|
322
|
+
fix: 'Use data-action="check-answer" or data-action="reset".'
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Verify data-interaction attribute is present
|
|
329
|
+
if (!button.hasAttribute('data-interaction')) {
|
|
330
|
+
errors.push({
|
|
331
|
+
message: 'Interaction button is missing data-interaction attribute',
|
|
332
|
+
context: {
|
|
333
|
+
interactionId,
|
|
334
|
+
element: button.outerHTML,
|
|
335
|
+
fix: 'Add data-interaction="[id]" to identify which interaction this button controls.'
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
valid: errors.length === 0,
|
|
344
|
+
errors
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Validates all UI components in an element for required data-action attributes.
|
|
350
|
+
* This function checks all component types (dropdowns, tabs, modals, interactions).
|
|
351
|
+
* @param {HTMLElement} element - The element to validate
|
|
352
|
+
* @param {string} [viewName=''] - Optional view name for error context
|
|
353
|
+
* @returns {{valid: boolean, errors: Array<{type: string, message: string, context: Object}>}} Validation result
|
|
354
|
+
*/
|
|
355
|
+
export function validateUIComponentActions(element, viewName = '') {
|
|
356
|
+
const allErrors = [];
|
|
357
|
+
|
|
358
|
+
// Validate dropdowns
|
|
359
|
+
const dropdownResult = validateDropdownActions(element);
|
|
360
|
+
dropdownResult.errors.forEach(error => {
|
|
361
|
+
allErrors.push({ type: 'dropdown', viewName, ...error });
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Validate tabs
|
|
365
|
+
const tabResult = validateTabActions(element);
|
|
366
|
+
tabResult.errors.forEach(error => {
|
|
367
|
+
allErrors.push({ type: 'tabs', viewName, ...error });
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Validate modals
|
|
371
|
+
const modalResult = validateModalActions(element);
|
|
372
|
+
modalResult.errors.forEach(error => {
|
|
373
|
+
allErrors.push({ type: 'modal', viewName, ...error });
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Validate interactions
|
|
377
|
+
const interactionResult = validateInteractionActions(element);
|
|
378
|
+
interactionResult.errors.forEach(error => {
|
|
379
|
+
allErrors.push({ type: 'interaction', viewName, ...error });
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
valid: allErrors.length === 0,
|
|
384
|
+
errors: allErrors
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Validates rendered HTML content for common issues that cause page reloads or errors.
|
|
390
|
+
* This is the master validation function that checks both HTML structure and UI components.
|
|
391
|
+
* @param {HTMLElement} element - The element to validate
|
|
392
|
+
* @param {string} [viewName=''] - Optional view name for error context
|
|
393
|
+
* @returns {{valid: boolean, errors: Array<{type: string, message: string, context: Object}>}} Validation result
|
|
394
|
+
*/
|
|
395
|
+
export function validateRenderedHTML(element, viewName = '') {
|
|
396
|
+
const allErrors = [];
|
|
397
|
+
|
|
398
|
+
// Validate HTML structure (forms, buttons, anchors)
|
|
399
|
+
const formButtonResult = validateFormButtons(element);
|
|
400
|
+
formButtonResult.errors.forEach(error => {
|
|
401
|
+
allErrors.push({ type: 'form-button', viewName, ...error });
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const actionButtonResult = validateActionButtonsInForms(element);
|
|
405
|
+
actionButtonResult.errors.forEach(error => {
|
|
406
|
+
allErrors.push({ type: 'action-button', viewName, ...error });
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const anchorResult = validateAnchorTags(element);
|
|
410
|
+
anchorResult.errors.forEach(error => {
|
|
411
|
+
allErrors.push({ type: 'anchor', viewName, ...error });
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Validate UI components
|
|
415
|
+
const uiComponentResult = validateUIComponentActions(element, viewName);
|
|
416
|
+
allErrors.push(...uiComponentResult.errors);
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
valid: allErrors.length === 0,
|
|
420
|
+
errors: allErrors
|
|
421
|
+
};
|
|
422
|
+
}
|