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,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stub-player/edit-utils.js - Shared editing utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable edit form rendering and save logic for both
|
|
5
|
+
* standalone interactions and assessment questions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Escape HTML entities for safe rendering in form values
|
|
10
|
+
*/
|
|
11
|
+
export function escapeHtml(str) {
|
|
12
|
+
if (!str) return '';
|
|
13
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Render an edit form for an interaction or question
|
|
18
|
+
* Now schema-aware when a schema is provided
|
|
19
|
+
* @param {Object} item - The interaction or question object with type
|
|
20
|
+
* @param {Object|null} schema - Optional schema for the interaction type
|
|
21
|
+
* @returns {string} HTML string for the edit form
|
|
22
|
+
*/
|
|
23
|
+
export function renderEditForm(item, schema = null) {
|
|
24
|
+
let html = '<div class="edit-form">';
|
|
25
|
+
|
|
26
|
+
// If no schema provided, fall back to legacy hardcoded rendering
|
|
27
|
+
if (!schema) {
|
|
28
|
+
return renderLegacyEditForm(item);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const props = schema.properties || {};
|
|
32
|
+
// schema.extends contains base properties directly (not nested under .properties)
|
|
33
|
+
const baseProps = schema.extends || {};
|
|
34
|
+
const allProps = { ...baseProps, ...props };
|
|
35
|
+
|
|
36
|
+
// Priority order for common fields
|
|
37
|
+
const priorityFields = ['label', 'prompt', 'correctAnswer'];
|
|
38
|
+
const sortedKeys = [
|
|
39
|
+
...priorityFields.filter(k => k in allProps),
|
|
40
|
+
...Object.keys(allProps).filter(k => !priorityFields.includes(k) && !['id'].includes(k))
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const propName of sortedKeys) {
|
|
44
|
+
const propDef = allProps[propName];
|
|
45
|
+
// Handle zones/dropZones naming mismatch between schema and data
|
|
46
|
+
let value = item[propName];
|
|
47
|
+
if (propName === 'dropZones' && value === undefined) {
|
|
48
|
+
value = item['zones']; // Fallback to zones if dropZones not found
|
|
49
|
+
}
|
|
50
|
+
const isPresent = value !== undefined && value !== null;
|
|
51
|
+
|
|
52
|
+
// Skip non-editable or complex nested types for now
|
|
53
|
+
if (propDef.type === 'object') continue;
|
|
54
|
+
|
|
55
|
+
const requiredIndicator = propDef.required ? '<span class="required-indicator" title="Required">*</span>' : '';
|
|
56
|
+
const tooltip = propDef.description ? ` title="${escapeHtml(propDef.description)}"` : '';
|
|
57
|
+
const label = formatLabel(propName);
|
|
58
|
+
|
|
59
|
+
html += `<div class="edit-field"${tooltip}>`;
|
|
60
|
+
html += `<label>${label}${requiredIndicator}</label>`;
|
|
61
|
+
|
|
62
|
+
// Render appropriate input based on type
|
|
63
|
+
if (propDef.type === 'boolean') {
|
|
64
|
+
const checked = value === true || value === 'true';
|
|
65
|
+
html += `<select name="${propName}">
|
|
66
|
+
<option value="true" ${checked ? 'selected' : ''}>True</option>
|
|
67
|
+
<option value="false" ${!checked ? 'selected' : ''}>False</option>
|
|
68
|
+
</select>`;
|
|
69
|
+
} else if (propDef.enum) {
|
|
70
|
+
html += `<select name="${propName}">`;
|
|
71
|
+
for (const opt of propDef.enum) {
|
|
72
|
+
html += `<option value="${escapeHtml(opt)}" ${value === opt ? 'selected' : ''}>${escapeHtml(opt)}</option>`;
|
|
73
|
+
}
|
|
74
|
+
html += '</select>';
|
|
75
|
+
} else if (propDef.type === 'array') {
|
|
76
|
+
// Special handling for common array types
|
|
77
|
+
if (propName === 'choices' && Array.isArray(value)) {
|
|
78
|
+
html += renderChoicesEditor(value, item.correctAnswer);
|
|
79
|
+
} else if (propName === 'pairs' && Array.isArray(value)) {
|
|
80
|
+
html += renderPairsEditor(value);
|
|
81
|
+
} else if (propName === 'items' && Array.isArray(value)) {
|
|
82
|
+
html += renderItemsEditor(value);
|
|
83
|
+
} else if ((propName === 'zones' || propName === 'dropZones') && Array.isArray(value)) {
|
|
84
|
+
html += renderZonesEditor(value);
|
|
85
|
+
} else if (propName === 'scale' && Array.isArray(value)) {
|
|
86
|
+
html += renderScaleEditor(value);
|
|
87
|
+
} else if (propName === 'questions' && Array.isArray(value)) {
|
|
88
|
+
html += renderSubQuestionsEditor(value);
|
|
89
|
+
} else {
|
|
90
|
+
// Generic array display
|
|
91
|
+
html += `<textarea name="${propName}" rows="3" readonly>${escapeHtml(JSON.stringify(value, null, 2))}</textarea>`;
|
|
92
|
+
}
|
|
93
|
+
} else if (propName === 'template') {
|
|
94
|
+
// Fill-in-the-blank template with syntax helper
|
|
95
|
+
html += `<textarea name="${propName}" rows="3">${escapeHtml(value || '')}</textarea>`;
|
|
96
|
+
html += '<div class="template-syntax-help">Use <code>{{answer}}</code> for single blank or <code>{{1:answer}}</code> for multiple blanks</div>';
|
|
97
|
+
} else if (propName === 'prompt' || propDef.multiline) {
|
|
98
|
+
html += `<textarea name="${propName}" rows="2">${escapeHtml(value || '')}</textarea>`;
|
|
99
|
+
} else if (propDef.type === 'number') {
|
|
100
|
+
html += `<input type="number" name="${propName}" value="${isPresent ? value : ''}" />`;
|
|
101
|
+
} else {
|
|
102
|
+
html += `<input type="text" name="${propName}" value="${escapeHtml(value || '')}" />`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
html += '</div>';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
html += `<div class="edit-actions">
|
|
109
|
+
<button class="edit-save-btn">💾 Save</button>
|
|
110
|
+
<button class="edit-cancel-btn">Cancel</button>
|
|
111
|
+
</div>`;
|
|
112
|
+
|
|
113
|
+
html += '</div>';
|
|
114
|
+
return html;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Format a camelCase property name as a human-readable label
|
|
119
|
+
*/
|
|
120
|
+
function formatLabel(propName) {
|
|
121
|
+
return propName
|
|
122
|
+
.replace(/([A-Z])/g, ' $1')
|
|
123
|
+
.replace(/^./, s => s.toUpperCase())
|
|
124
|
+
.trim();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Render choices editor for multiple-choice questions
|
|
129
|
+
*/
|
|
130
|
+
function renderChoicesEditor(choices, correctAnswer) {
|
|
131
|
+
let html = '<div class="edit-choices">';
|
|
132
|
+
for (let i = 0; i < choices.length; i++) {
|
|
133
|
+
const c = choices[i];
|
|
134
|
+
const isCorrect = c.value === correctAnswer || c.correct === true;
|
|
135
|
+
html += `<div class="edit-choice" data-index="${i}">
|
|
136
|
+
<input type="radio" name="correctChoice" value="${c.value}" ${isCorrect ? 'checked' : ''} />
|
|
137
|
+
<span class="choice-value">${c.value}:</span>
|
|
138
|
+
<input type="text" name="choice-${i}" value="${escapeHtml(c.text)}" class="choice-text" />
|
|
139
|
+
</div>`;
|
|
140
|
+
}
|
|
141
|
+
html += '</div>';
|
|
142
|
+
return html;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Render pairs editor for matching questions
|
|
147
|
+
*/
|
|
148
|
+
function renderPairsEditor(pairs) {
|
|
149
|
+
let html = '<div class="edit-pairs">';
|
|
150
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
151
|
+
const p = pairs[i];
|
|
152
|
+
html += `<div class="edit-pair" data-index="${i}">
|
|
153
|
+
<input type="text" name="pair-text-${i}" value="${escapeHtml(p.text)}" placeholder="Item" />
|
|
154
|
+
<span class="pair-arrow">→</span>
|
|
155
|
+
<input type="text" name="pair-match-${i}" value="${escapeHtml(p.match)}" placeholder="Match" />
|
|
156
|
+
</div>`;
|
|
157
|
+
}
|
|
158
|
+
html += '</div>';
|
|
159
|
+
return html;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Render items editor for drag-drop/sequencing
|
|
164
|
+
*/
|
|
165
|
+
function renderItemsEditor(items) {
|
|
166
|
+
let html = '<div class="edit-items">';
|
|
167
|
+
for (let i = 0; i < items.length; i++) {
|
|
168
|
+
const item = items[i];
|
|
169
|
+
const text = typeof item === 'string' ? item : (item.text || item.id || JSON.stringify(item));
|
|
170
|
+
html += `<div class="edit-item" data-index="${i}">
|
|
171
|
+
<span class="item-index">${i + 1}.</span>
|
|
172
|
+
<input type="text" name="item-${i}" value="${escapeHtml(text)}" />
|
|
173
|
+
</div>`;
|
|
174
|
+
}
|
|
175
|
+
html += '</div>';
|
|
176
|
+
return html;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Render zones editor for drag-drop questions
|
|
181
|
+
*/
|
|
182
|
+
function renderZonesEditor(zones) {
|
|
183
|
+
let html = '<div class="edit-zones">';
|
|
184
|
+
for (let i = 0; i < zones.length; i++) {
|
|
185
|
+
const zone = zones[i];
|
|
186
|
+
const id = typeof zone === 'string' ? zone : (zone.id || '');
|
|
187
|
+
const label = typeof zone === 'string' ? zone : (zone.label || zone.text || zone.id || '');
|
|
188
|
+
html += `<div class="edit-zone" data-index="${i}">
|
|
189
|
+
<input type="text" name="zone-id-${i}" value="${escapeHtml(id)}" placeholder="Zone ID" class="zone-id" />
|
|
190
|
+
<input type="text" name="zone-label-${i}" value="${escapeHtml(label)}" placeholder="Zone Label" class="zone-label" />
|
|
191
|
+
</div>`;
|
|
192
|
+
}
|
|
193
|
+
html += '</div>';
|
|
194
|
+
return html;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Render scale editor for likert questions
|
|
199
|
+
*/
|
|
200
|
+
function renderScaleEditor(scale) {
|
|
201
|
+
let html = '<div class="edit-scale">';
|
|
202
|
+
for (let i = 0; i < scale.length; i++) {
|
|
203
|
+
const point = scale[i];
|
|
204
|
+
html += `<div class="edit-scale-point" data-index="${i}">
|
|
205
|
+
<input type="text" name="scale-value-${i}" value="${escapeHtml(point.value || String(i + 1))}" class="scale-value" />
|
|
206
|
+
<input type="text" name="scale-label-${i}" value="${escapeHtml(point.label || '')}" class="scale-label" placeholder="Label" />
|
|
207
|
+
</div>`;
|
|
208
|
+
}
|
|
209
|
+
html += '</div>';
|
|
210
|
+
return html;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Render sub-questions editor for likert (multi-statement)
|
|
215
|
+
*/
|
|
216
|
+
function renderSubQuestionsEditor(questions) {
|
|
217
|
+
let html = '<div class="edit-subquestions">';
|
|
218
|
+
for (let i = 0; i < questions.length; i++) {
|
|
219
|
+
const q = questions[i];
|
|
220
|
+
html += `<div class="edit-subquestion" data-index="${i}">
|
|
221
|
+
<span class="subq-index">${i + 1}.</span>
|
|
222
|
+
<input type="text" name="subq-${i}" value="${escapeHtml(q.text || q)}" />
|
|
223
|
+
</div>`;
|
|
224
|
+
}
|
|
225
|
+
html += '</div>';
|
|
226
|
+
return html;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Legacy fallback for when no schema is available
|
|
231
|
+
*/
|
|
232
|
+
function renderLegacyEditForm(item) {
|
|
233
|
+
let html = '<div class="edit-form">';
|
|
234
|
+
|
|
235
|
+
// Label field
|
|
236
|
+
if (item.label !== undefined) {
|
|
237
|
+
html += `<div class="edit-field">
|
|
238
|
+
<label>Label</label>
|
|
239
|
+
<input type="text" name="label" value="${escapeHtml(item.label)}" />
|
|
240
|
+
</div>`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Prompt field
|
|
244
|
+
if (item.prompt !== undefined) {
|
|
245
|
+
html += `<div class="edit-field">
|
|
246
|
+
<label>Prompt</label>
|
|
247
|
+
<textarea name="prompt" rows="2">${escapeHtml(item.prompt)}</textarea>
|
|
248
|
+
</div>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Correct answer field (for T/F, text input, numeric - not MC)
|
|
252
|
+
if (item.correctAnswer !== undefined && item.type !== 'multiple-choice') {
|
|
253
|
+
if (item.type === 'true-false') {
|
|
254
|
+
const isTrue = item.correctAnswer === true || item.correctAnswer === 'true';
|
|
255
|
+
html += `<div class="edit-field">
|
|
256
|
+
<label>Correct Answer</label>
|
|
257
|
+
<select name="correctAnswer">
|
|
258
|
+
<option value="true" ${isTrue ? 'selected' : ''}>True</option>
|
|
259
|
+
<option value="false" ${!isTrue ? 'selected' : ''}>False</option>
|
|
260
|
+
</select>
|
|
261
|
+
</div>`;
|
|
262
|
+
} else {
|
|
263
|
+
html += `<div class="edit-field">
|
|
264
|
+
<label>Correct Answer</label>
|
|
265
|
+
<input type="text" name="correctAnswer" value="${escapeHtml(item.correctAnswer)}" />
|
|
266
|
+
</div>`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Choices field (for multiple-choice)
|
|
271
|
+
if (item.choices && item.choices.length > 0) {
|
|
272
|
+
html += '<div class="edit-field"><label>Choices</label>';
|
|
273
|
+
html += renderChoicesEditor(item.choices, item.correctAnswer);
|
|
274
|
+
html += '</div>';
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Pairs field (for matching)
|
|
278
|
+
if (item.pairs && item.pairs.length > 0) {
|
|
279
|
+
html += '<div class="edit-field"><label>Pairs</label>';
|
|
280
|
+
html += renderPairsEditor(item.pairs);
|
|
281
|
+
html += '</div>';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
html += `<div class="edit-actions">
|
|
285
|
+
<button class="edit-save-btn">💾 Save</button>
|
|
286
|
+
<button class="edit-cancel-btn">Cancel</button>
|
|
287
|
+
</div>`;
|
|
288
|
+
|
|
289
|
+
html += '</div>';
|
|
290
|
+
return html;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Save edits by posting to the server
|
|
295
|
+
* @param {string} endpoint - API endpoint (e.g., '/__edit-interaction')
|
|
296
|
+
* @param {Element} form - The form DOM element
|
|
297
|
+
* @param {string} slideId - Slide identifier
|
|
298
|
+
* @param {string} itemId - Interaction or question ID
|
|
299
|
+
* @returns {Promise<boolean>} Success status
|
|
300
|
+
*/
|
|
301
|
+
export async function saveItemEdits(endpoint, form, slideId, itemId) {
|
|
302
|
+
const edits = {};
|
|
303
|
+
|
|
304
|
+
// 1. Label
|
|
305
|
+
const labelInput = form.querySelector('[name="label"]');
|
|
306
|
+
if (labelInput) edits.label = labelInput.value;
|
|
307
|
+
|
|
308
|
+
// 2. Prompt
|
|
309
|
+
const promptInput = form.querySelector('[name="prompt"]');
|
|
310
|
+
if (promptInput) edits.prompt = promptInput.value;
|
|
311
|
+
|
|
312
|
+
// 3. Choices (Array)
|
|
313
|
+
const choiceInputs = form.querySelectorAll('[name^="choice-"]');
|
|
314
|
+
if (choiceInputs.length > 0) {
|
|
315
|
+
// Collect choices by finding container elements to ensure we get value+text pairs
|
|
316
|
+
const choiceContainers = form.querySelectorAll('.edit-choice');
|
|
317
|
+
const choices = [];
|
|
318
|
+
|
|
319
|
+
choiceContainers.forEach(container => {
|
|
320
|
+
const textInput = container.querySelector('.choice-text');
|
|
321
|
+
// The radio value typically holds the choice ID/Value
|
|
322
|
+
const radio = container.querySelector('input[type="radio"]');
|
|
323
|
+
|
|
324
|
+
if (textInput && radio) {
|
|
325
|
+
choices.push({
|
|
326
|
+
value: radio.value,
|
|
327
|
+
text: textInput.value
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (choices.length > 0) edits.choices = choices;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 4. Pairs (Matching)
|
|
336
|
+
const pairContainers = form.querySelectorAll('.edit-pair');
|
|
337
|
+
if (pairContainers.length > 0) {
|
|
338
|
+
const pairs = [];
|
|
339
|
+
pairContainers.forEach(container => {
|
|
340
|
+
const textInput = container.querySelector('input[placeholder="Item"]');
|
|
341
|
+
const matchInput = container.querySelector('input[placeholder="Match"]');
|
|
342
|
+
if (textInput && matchInput) {
|
|
343
|
+
pairs.push({
|
|
344
|
+
text: textInput.value,
|
|
345
|
+
match: matchInput.value
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
if (pairs.length > 0) edits.pairs = pairs;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 5. Correct Answer
|
|
353
|
+
// Handle Boolean (True/False)
|
|
354
|
+
const correctSelect = form.querySelector('select[name="correctAnswer"]');
|
|
355
|
+
if (correctSelect) {
|
|
356
|
+
edits.correctAnswer = correctSelect.value === 'true';
|
|
357
|
+
} else {
|
|
358
|
+
// Handle Text Input
|
|
359
|
+
const correctInput = form.querySelector('input[name="correctAnswer"]');
|
|
360
|
+
if (correctInput) {
|
|
361
|
+
edits.correctAnswer = correctInput.value;
|
|
362
|
+
} else {
|
|
363
|
+
// Handle Choice Radio
|
|
364
|
+
const correctRadio = form.querySelector('input[name="correctChoice"]:checked');
|
|
365
|
+
if (correctRadio) {
|
|
366
|
+
edits.correctAnswer = correctRadio.value;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 6. Generic Loop for other simple fields
|
|
372
|
+
const otherInputs = form.querySelectorAll('input:not([name^="choice-"]):not([name^="pair-"]):not([name^="item-"]):not([name="correctAnswer"]):not([name="correctChoice"]), select:not([name="correctAnswer"])');
|
|
373
|
+
otherInputs.forEach(input => {
|
|
374
|
+
if (input.type === 'radio' || input.type === 'checkbox' || input.type === 'submit' || input.type === 'button') return;
|
|
375
|
+
if (input.name && !edits[input.name]) {
|
|
376
|
+
// Basic type conversion
|
|
377
|
+
edits[input.name] = input.type === 'number' ? parseFloat(input.value) : input.value;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const res = await fetch(endpoint, {
|
|
383
|
+
method: 'POST',
|
|
384
|
+
headers: { 'Content-Type': 'application/json' },
|
|
385
|
+
body: JSON.stringify({ slideId, interactionId: itemId, edits })
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
if (!res.ok) {
|
|
389
|
+
const data = await res.json();
|
|
390
|
+
console.error('Edit failed:', data.error);
|
|
391
|
+
alert('Failed to save: ' + (data.error || 'Unknown error'));
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
} catch (err) {
|
|
396
|
+
console.error('Edit error:', err);
|
|
397
|
+
alert('Error saving edits: ' + err.message);
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|