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,689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stub-player/outline-mode.js - Status Dashboard
|
|
3
|
+
*
|
|
4
|
+
* General-purpose project status dashboard showing authoring stage,
|
|
5
|
+
* progress checklist, and stage-specific guidance/content.
|
|
6
|
+
*
|
|
7
|
+
* Auto-shows at stages 1-2 (before course is built).
|
|
8
|
+
* Always accessible via header button or ?dashboard URL param.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const STAGES = [
|
|
12
|
+
{ num: 1, id: 'source-ingestion', label: 'Ingest', title: 'Source Ingestion', desc: 'Add reference files and convert them to markdown for AI processing.' },
|
|
13
|
+
{ num: 2, id: 'outline-creation', label: 'Outline', title: 'Outline Creation', desc: 'Generate or write a course outline from your reference materials.' },
|
|
14
|
+
{ num: 3, id: 'course-building', label: 'Build', title: 'Course Building', desc: 'Create slide content and configure course structure.' },
|
|
15
|
+
{ num: 4, id: 'preview-polish', label: 'Polish', title: 'Preview & Polish', desc: 'Iterate on visual quality, fix issues, and refine interactions.' },
|
|
16
|
+
{ num: 5, id: 'export-ready', label: 'Export', title: 'Export Ready', desc: 'Lint passes — ready for LMS deployment.' }
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate dashboard overlay HTML
|
|
21
|
+
*/
|
|
22
|
+
export function generateOutlineMode() {
|
|
23
|
+
return `
|
|
24
|
+
<div id="stub-player-outline-mode">
|
|
25
|
+
<div id="stub-player-outline-content">
|
|
26
|
+
<div class="outline-loading">Checking project stage...</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialize Dashboard Handlers
|
|
34
|
+
*/
|
|
35
|
+
export function createOutlineModeHandlers(context) {
|
|
36
|
+
const outlineMode = document.getElementById('stub-player-outline-mode');
|
|
37
|
+
const outlineContent = document.getElementById('stub-player-outline-content');
|
|
38
|
+
const courseFrame = document.getElementById('stub-player-course-frame');
|
|
39
|
+
|
|
40
|
+
let stageData = null;
|
|
41
|
+
let isVisible = false;
|
|
42
|
+
let courseLoaded = false;
|
|
43
|
+
let viewingStage = null; // Which stage the dashboard is showing
|
|
44
|
+
|
|
45
|
+
async function checkStage() {
|
|
46
|
+
const params = new URLSearchParams(window.location.search);
|
|
47
|
+
|
|
48
|
+
// MCP headless browser sets ?headless — never show the dashboard
|
|
49
|
+
if (params.has('headless')) return false;
|
|
50
|
+
|
|
51
|
+
// Desktop companion app manages its own workflow — skip auto-show
|
|
52
|
+
const stubConfig = window.STUB_CONFIG || {};
|
|
53
|
+
if (stubConfig.isDesktop) return false;
|
|
54
|
+
|
|
55
|
+
const urlForce = params.has('dashboard');
|
|
56
|
+
const stageParam = params.has('stage') ? parseInt(params.get('stage'), 10) : null;
|
|
57
|
+
const skipPref = localStorage.getItem('coursecode-skipOutline') === 'true';
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch('/__stage');
|
|
61
|
+
if (!res.ok) return false;
|
|
62
|
+
stageData = await res.json();
|
|
63
|
+
viewingStage = stageParam || stageData.stageNumber;
|
|
64
|
+
|
|
65
|
+
if (urlForce || stageParam) {
|
|
66
|
+
show();
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
// Auto-show for early stages and freshly imported presentations
|
|
70
|
+
const isImport = stageData.checklist?.source === 'powerpoint-import';
|
|
71
|
+
if ((stageData.stageNumber < 3 || (isImport && stageData.stageNumber === 4)) && !skipPref) {
|
|
72
|
+
show();
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function toggle() {
|
|
82
|
+
if (isVisible) {
|
|
83
|
+
hide();
|
|
84
|
+
if (!courseLoaded && context.loadCourse) {
|
|
85
|
+
context.loadCourse();
|
|
86
|
+
courseLoaded = true;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
fetchAndShow();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function fetchAndShow() {
|
|
94
|
+
show();
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch('/__stage');
|
|
97
|
+
if (res.ok) {
|
|
98
|
+
stageData = await res.json();
|
|
99
|
+
viewingStage = stageData.stageNumber;
|
|
100
|
+
}
|
|
101
|
+
} catch { /* use cached */ }
|
|
102
|
+
renderDashboard();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function show() {
|
|
106
|
+
if (!outlineMode || !courseFrame) return;
|
|
107
|
+
isVisible = true;
|
|
108
|
+
outlineMode.classList.add('visible');
|
|
109
|
+
courseFrame.style.display = 'none';
|
|
110
|
+
if (stageData) renderDashboard();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function hide() {
|
|
114
|
+
if (!outlineMode || !courseFrame) return;
|
|
115
|
+
isVisible = false;
|
|
116
|
+
outlineMode.classList.remove('visible');
|
|
117
|
+
courseFrame.style.display = '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Rendering ────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
async function renderDashboard() {
|
|
123
|
+
if (!stageData) return;
|
|
124
|
+
const detectedStage = stageData.stageNumber;
|
|
125
|
+
const stage = STAGES[viewingStage - 1];
|
|
126
|
+
if (!stage) return;
|
|
127
|
+
|
|
128
|
+
const parts = [];
|
|
129
|
+
|
|
130
|
+
// Stage stepper
|
|
131
|
+
parts.push(renderStepper(detectedStage, viewingStage));
|
|
132
|
+
|
|
133
|
+
// Stage header
|
|
134
|
+
parts.push(`
|
|
135
|
+
<div class="outline-stage-header">
|
|
136
|
+
<h1>${stage.title}</h1>
|
|
137
|
+
<p class="outline-stage-desc">${stage.desc}</p>
|
|
138
|
+
<button id="stub-player-skip-outline-btn" class="outline-skip-btn">
|
|
139
|
+
${courseLoaded ? '← Back to Course' : 'Skip to Course ▸'}
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
`);
|
|
143
|
+
|
|
144
|
+
// Stage-specific content
|
|
145
|
+
parts.push(await renderStageContent(viewingStage));
|
|
146
|
+
|
|
147
|
+
outlineContent.innerHTML = parts.join('');
|
|
148
|
+
attachHandlers();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function renderStepper(detected, viewing) {
|
|
152
|
+
const steps = STAGES.map(s => {
|
|
153
|
+
const classes = [];
|
|
154
|
+
if (s.num < detected) classes.push('completed');
|
|
155
|
+
if (s.num === detected) classes.push('current');
|
|
156
|
+
if (s.num === viewing) classes.push('viewing');
|
|
157
|
+
return `<button class="stepper-step ${classes.join(' ')}" data-stage="${s.num}">
|
|
158
|
+
<span class="stepper-dot">${s.num < detected ? '✓' : s.num}</span>
|
|
159
|
+
<span class="stepper-label">${s.label}</span>
|
|
160
|
+
</button>`;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return `<div class="outline-stepper">${steps.join('<span class="stepper-line"></span>')}</div>`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function renderStageContent(stageNum) {
|
|
167
|
+
switch (stageNum) {
|
|
168
|
+
case 1: return await renderStage1();
|
|
169
|
+
case 2: return await renderStage2();
|
|
170
|
+
case 3: return await renderStage3();
|
|
171
|
+
case 4: return await renderStage4();
|
|
172
|
+
case 5: return await renderStage5();
|
|
173
|
+
default: return '';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Stage 1: Source Ingestion ─────────────────────────────
|
|
178
|
+
|
|
179
|
+
async function renderStage1() {
|
|
180
|
+
let refs = null;
|
|
181
|
+
try {
|
|
182
|
+
const res = await fetch('/__refs');
|
|
183
|
+
if (res.ok) refs = await res.json();
|
|
184
|
+
} catch { /* ignore */ }
|
|
185
|
+
|
|
186
|
+
const hasRefs = refs && !refs.isEmpty;
|
|
187
|
+
|
|
188
|
+
if (!hasRefs) {
|
|
189
|
+
// No refs — dropzone is the primary view, with pptx branch below
|
|
190
|
+
return `
|
|
191
|
+
<div class="outline-card">
|
|
192
|
+
<h2>📋 What to do</h2>
|
|
193
|
+
<ol class="outline-steps">
|
|
194
|
+
<li>Add reference documents (PDF, DOCX, PPTX) to <code>course/references/</code></li>
|
|
195
|
+
<li>Click <strong>Convert to Markdown</strong> below, or run <code>coursecode convert</code> from the CLI</li>
|
|
196
|
+
<li>Review converted files in <code>course/references/converted/</code></li>
|
|
197
|
+
</ol>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="outline-card outline-refs-card" id="outline-refs-dropzone">
|
|
200
|
+
<h2>📁 Reference Files</h2>
|
|
201
|
+
<div class="outline-dropzone-empty">
|
|
202
|
+
<div class="outline-dropzone-icon">📂</div>
|
|
203
|
+
<div class="outline-dropzone-text">Drop reference files here to get started</div>
|
|
204
|
+
<div class="outline-dropzone-hint">PDF, DOCX, PPTX, MD — or copy them to <code>course/references/</code></div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="outline-or-divider"><span class="outline-or-line"></span><span class="outline-or-text">or</span><span class="outline-or-line"></span></div>
|
|
208
|
+
<button class="outline-branch-btn" id="branch-convert">
|
|
209
|
+
<span class="outline-branch-icon">📊</span>
|
|
210
|
+
<div>
|
|
211
|
+
<strong>Convert PowerPoint to Course</strong>
|
|
212
|
+
<span>Each slide becomes a page — then enhance with AI</span>
|
|
213
|
+
</div>
|
|
214
|
+
</button>
|
|
215
|
+
${renderChecklist()}
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Has refs — show normal flow (no pptx branch needed)
|
|
220
|
+
return `
|
|
221
|
+
<div class="outline-card">
|
|
222
|
+
<h2>📋 What to do</h2>
|
|
223
|
+
<ol class="outline-steps">
|
|
224
|
+
<li>Add reference documents (PDF, DOCX, PPTX) to <code>course/references/</code></li>
|
|
225
|
+
<li>Click <strong>Convert to Markdown</strong> below, or run <code>coursecode convert</code> from the CLI</li>
|
|
226
|
+
<li>Review converted files in <code>course/references/converted/</code></li>
|
|
227
|
+
</ol>
|
|
228
|
+
</div>
|
|
229
|
+
${renderRefsWithDropzone(refs)}
|
|
230
|
+
${renderChecklist()}
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function renderConvertView() {
|
|
235
|
+
return `
|
|
236
|
+
<button class="outline-back-btn" id="convert-back-btn">← Back</button>
|
|
237
|
+
<div class="outline-card outline-convert-dropzone" id="outline-convert-dropzone">
|
|
238
|
+
<h2>📊 Convert PowerPoint to Course</h2>
|
|
239
|
+
<div class="outline-dropzone-empty">
|
|
240
|
+
<div class="outline-dropzone-icon">📊</div>
|
|
241
|
+
<div class="outline-dropzone-text">Drop a .pptx file here</div>
|
|
242
|
+
<div class="outline-dropzone-hint">Each slide becomes a page. Text is extracted as reference material.</div>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Stage 2: Outline Creation ────────────────────────────
|
|
249
|
+
|
|
250
|
+
async function renderStage2() {
|
|
251
|
+
let outlineHtml = '';
|
|
252
|
+
try {
|
|
253
|
+
const res = await fetch('/__outline');
|
|
254
|
+
if (res.ok) {
|
|
255
|
+
const data = await res.json();
|
|
256
|
+
const filePath = data.path || '';
|
|
257
|
+
outlineHtml = `
|
|
258
|
+
<div class="outline-card">
|
|
259
|
+
<div class="outline-card-header">
|
|
260
|
+
<h2>📝 Course Outline</h2>
|
|
261
|
+
<button class="outline-open-file-btn" id="outline-open-file-btn" data-path="${filePath}" title="Open in editor">
|
|
262
|
+
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
263
|
+
<path d="M6 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-3"/>
|
|
264
|
+
<path d="M8 8L14 2M10 2h4v4"/>
|
|
265
|
+
</svg>
|
|
266
|
+
Open File
|
|
267
|
+
</button>
|
|
268
|
+
</div>
|
|
269
|
+
<div class="outline-markdown-scroll">
|
|
270
|
+
<div class="outline-markdown">${data.html || escapeHtml(data.raw || '')}</div>
|
|
271
|
+
</div>
|
|
272
|
+
</div>`;
|
|
273
|
+
}
|
|
274
|
+
} catch { /* ignore */ }
|
|
275
|
+
|
|
276
|
+
return `
|
|
277
|
+
<div class="outline-card">
|
|
278
|
+
<h2>📋 What to do</h2>
|
|
279
|
+
<ol class="outline-steps">
|
|
280
|
+
<li>Review your converted reference materials</li>
|
|
281
|
+
<li>Create <code>COURSE_OUTLINE.md</code> in <code>course/</code></li>
|
|
282
|
+
<li>Define modules, lessons, and learning objectives</li>
|
|
283
|
+
</ol>
|
|
284
|
+
</div>
|
|
285
|
+
${outlineHtml || '<div class=\'outline-card outline-card-empty\'><h2>📝 Course Outline</h2><p>No outline found yet. Create <code>COURSE_OUTLINE.md</code> to continue.</p></div>'}
|
|
286
|
+
${renderChecklist()}
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ── Stage 3: Course Building ─────────────────────────────
|
|
291
|
+
|
|
292
|
+
async function renderStage3() {
|
|
293
|
+
let configHtml = '';
|
|
294
|
+
try {
|
|
295
|
+
const res = await fetch('/__config');
|
|
296
|
+
if (res.ok) {
|
|
297
|
+
const config = await res.json();
|
|
298
|
+
const slideCount = config.slideCount || 0;
|
|
299
|
+
const assessmentCount = (config.slideIds || []).filter(s => s.type === 'assessment').length;
|
|
300
|
+
configHtml = `
|
|
301
|
+
<div class="outline-card">
|
|
302
|
+
<h2>📊 Course Structure</h2>
|
|
303
|
+
<div class="outline-stats">
|
|
304
|
+
<div class="outline-stat"><span class="outline-stat-num">${slideCount}</span><span class="outline-stat-label">Slides</span></div>
|
|
305
|
+
<div class="outline-stat"><span class="outline-stat-num">${assessmentCount}</span><span class="outline-stat-label">Assessments</span></div>
|
|
306
|
+
<div class="outline-stat"><span class="outline-stat-num">${config.objectives?.length || 0}</span><span class="outline-stat-label">Objectives</span></div>
|
|
307
|
+
</div>
|
|
308
|
+
</div>`;
|
|
309
|
+
}
|
|
310
|
+
} catch { /* ignore */ }
|
|
311
|
+
|
|
312
|
+
return `
|
|
313
|
+
<div class="outline-card">
|
|
314
|
+
<h2>📋 What to do</h2>
|
|
315
|
+
<ol class="outline-steps">
|
|
316
|
+
<li>Create slide files in <code>course/slides/</code> based on your outline</li>
|
|
317
|
+
<li>Configure <code>course/course-config.js</code> with slide order and metadata</li>
|
|
318
|
+
<li>Add assessments and interactions as needed</li>
|
|
319
|
+
</ol>
|
|
320
|
+
</div>
|
|
321
|
+
${configHtml}
|
|
322
|
+
${renderChecklist()}
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ── Stage 4: Preview & Polish ────────────────────────────
|
|
327
|
+
|
|
328
|
+
async function renderStage4() {
|
|
329
|
+
// Check for powerpoint-import source — show tailored post-import view
|
|
330
|
+
if (stageData.checklist?.source === 'powerpoint-import') {
|
|
331
|
+
return renderImportView();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let lintHtml = '';
|
|
335
|
+
try {
|
|
336
|
+
const runtimeErrors = window._stubPlayerState?.errorLog || [];
|
|
337
|
+
const errorCount = runtimeErrors.filter(e => !e.isWarning).length;
|
|
338
|
+
const warningCount = runtimeErrors.filter(e => e.isWarning).length;
|
|
339
|
+
const status = errorCount === 0 && warningCount === 0 ? 'clean' : errorCount > 0 ? 'errors' : 'warnings';
|
|
340
|
+
lintHtml = `
|
|
341
|
+
<div class="outline-card">
|
|
342
|
+
<h2>🔍 Build Status</h2>
|
|
343
|
+
<div class="outline-stats">
|
|
344
|
+
<div class="outline-stat ${status === 'errors' ? 'stat-error' : ''}"><span class="outline-stat-num">${errorCount}</span><span class="outline-stat-label">Errors</span></div>
|
|
345
|
+
<div class="outline-stat ${status === 'warnings' ? 'stat-warning' : ''}"><span class="outline-stat-num">${warningCount}</span><span class="outline-stat-label">Warnings</span></div>
|
|
346
|
+
<div class="outline-stat ${status === 'clean' ? 'stat-success' : ''}"><span class="outline-stat-num">${status === 'clean' ? '✓' : '—'}</span><span class="outline-stat-label">${status === 'clean' ? 'All Clear' : 'Status'}</span></div>
|
|
347
|
+
</div>
|
|
348
|
+
</div>`;
|
|
349
|
+
} catch { /* ignore */ }
|
|
350
|
+
|
|
351
|
+
return `
|
|
352
|
+
<div class="outline-card">
|
|
353
|
+
<h2>📋 What to do</h2>
|
|
354
|
+
<ol class="outline-steps">
|
|
355
|
+
<li>Preview each slide and check layout, styling, and content</li>
|
|
356
|
+
<li>Test all interactions and verify correct/incorrect responses</li>
|
|
357
|
+
<li>Fix any lint errors or warnings shown in the Debug panel</li>
|
|
358
|
+
<li>Customize theme colors and typography in <code>course/theme.css</code></li>
|
|
359
|
+
</ol>
|
|
360
|
+
</div>
|
|
361
|
+
${lintHtml}
|
|
362
|
+
${renderChecklist()}
|
|
363
|
+
`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function renderImportView() {
|
|
367
|
+
const slideCount = stageData.checklist?.slideCount || 0;
|
|
368
|
+
|
|
369
|
+
return `
|
|
370
|
+
<div class="outline-card outline-card-success">
|
|
371
|
+
<h2>📊 Presentation Imported</h2>
|
|
372
|
+
<div class="outline-stats">
|
|
373
|
+
<div class="outline-stat stat-success"><span class="outline-stat-num">${slideCount}</span><span class="outline-stat-label">Slides Imported</span></div>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
<div class="outline-card">
|
|
377
|
+
<h2>🤖 Enhance with AI</h2>
|
|
378
|
+
<p>Your presentation slides are ready. Use AI editing to transform them into an interactive course:</p>
|
|
379
|
+
<ol class="outline-steps">
|
|
380
|
+
<li><strong>Add engagement tracking</strong> — require learners to interact with tabs, accordions, or other components before advancing</li>
|
|
381
|
+
<li><strong>Insert assessments</strong> — add knowledge checks between sections with multiple-choice, drag-drop, or other question types</li>
|
|
382
|
+
<li><strong>Group into sections</strong> — organize slides into logical modules with section headers</li>
|
|
383
|
+
<li><strong>Replace image slides</strong> — convert static image slides into interactive HTML with text, components, and styling</li>
|
|
384
|
+
<li><strong>Customize theme</strong> — update colors and typography in <code>course/theme.css</code></li>
|
|
385
|
+
</ol>
|
|
386
|
+
<p class="outline-hint">💡 Extracted text from the presentation is available in <code>course/references/converted/</code> for AI context.</p>
|
|
387
|
+
</div>
|
|
388
|
+
${renderChecklist()}
|
|
389
|
+
`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ── Stage 5: Export Ready ────────────────────────────────
|
|
393
|
+
|
|
394
|
+
async function renderStage5() {
|
|
395
|
+
return `
|
|
396
|
+
<div class="outline-card outline-card-success">
|
|
397
|
+
<h2>🎉 Ready for Export</h2>
|
|
398
|
+
<p>Your course is complete and passing all checks. Export it for your LMS:</p>
|
|
399
|
+
<div class="outline-export-cmds">
|
|
400
|
+
<div class="outline-cmd"><code>coursecode build</code><span>cmi5 (default)</span></div>
|
|
401
|
+
<div class="outline-cmd"><code>coursecode build --format scorm2004</code><span>SCORM 2004</span></div>
|
|
402
|
+
<div class="outline-cmd"><code>coursecode build --format scorm1.2</code><span>SCORM 1.2</span></div>
|
|
403
|
+
<div class="outline-cmd"><code>coursecode build --format lti</code><span>LTI 1.3</span></div>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
${renderChecklist()}
|
|
407
|
+
`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ── Shared Components ────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
function renderChecklist() {
|
|
413
|
+
if (!stageData?.checklist) return '';
|
|
414
|
+
const c = stageData.checklist;
|
|
415
|
+
const items = [
|
|
416
|
+
{ label: 'Reference files', done: c.hasRawRefs, detail: c.hasRawRefs ? `${c.rawRefCount || '—'} file(s)` : 'None yet' },
|
|
417
|
+
{ label: 'Converted to markdown', done: c.hasConvertedRefs, detail: c.hasConvertedRefs ? `${c.convertedRefCount || '—'} file(s)` : 'Pending' },
|
|
418
|
+
{ label: 'Course outline', done: c.hasOutline, detail: c.hasOutline ? 'COURSE_OUTLINE.md' : 'Not created' },
|
|
419
|
+
{ label: 'Slide files', done: c.hasSlides, detail: c.hasSlides ? `${c.slideCount || '—'} slide(s)` : 'Not created' },
|
|
420
|
+
{ label: 'Course config', done: c.hasCourseConfig, detail: c.hasCourseConfig ? 'course-config.js' : 'Not created' }
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
return `<div class="outline-card outline-checklist">
|
|
424
|
+
<h2>✅ Progress</h2>
|
|
425
|
+
${items.map(i => `
|
|
426
|
+
<div class="outline-checklist-item ${i.done ? 'done' : ''}">
|
|
427
|
+
<span class="outline-check">${i.done ? '✓' : ''}</span>
|
|
428
|
+
<span class="outline-check-label">${i.label}</span>
|
|
429
|
+
<span class="outline-check-detail">${i.detail}</span>
|
|
430
|
+
</div>
|
|
431
|
+
`).join('')}
|
|
432
|
+
</div>`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function renderRefsWithDropzone(refs) {
|
|
436
|
+
// Has refs — show list with convert action bar and drop bar
|
|
437
|
+
const pendingCount = (refs.needsConversion || []).length;
|
|
438
|
+
|
|
439
|
+
let html = '<div class="outline-card outline-refs-card"><h2>📁 Reference Files</h2>';
|
|
440
|
+
for (const file of (refs.raw || [])) {
|
|
441
|
+
const converted = !(refs.needsConversion || []).includes(file);
|
|
442
|
+
html += `<div class="outline-ref-item ${converted ? 'converted' : 'pending'}">
|
|
443
|
+
<span class="outline-ref-status">${converted ? '✓' : '⚠'}</span>
|
|
444
|
+
<span class="outline-ref-name">${file}</span>
|
|
445
|
+
<span class="outline-ref-badge">${converted ? 'Converted' : 'Needs conversion'}</span>
|
|
446
|
+
</div>`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Convert action bar
|
|
450
|
+
html += `<div class="outline-convert-bar">
|
|
451
|
+
<label class="outline-convert-overwrite">
|
|
452
|
+
<input type="checkbox" id="outline-convert-overwrite" checked>
|
|
453
|
+
<span>Overwrite existing conversions</span>
|
|
454
|
+
</label>
|
|
455
|
+
<button class="outline-convert-btn" id="outline-convert-btn">
|
|
456
|
+
${pendingCount > 0 ? `Convert ${pendingCount} File${pendingCount > 1 ? 's' : ''} to Markdown` : 'Re-convert All to Markdown'}
|
|
457
|
+
</button>
|
|
458
|
+
</div>`;
|
|
459
|
+
|
|
460
|
+
html += `<div class="outline-dropzone-bar" id="outline-refs-dropzone">
|
|
461
|
+
<span class="outline-dropzone-bar-text">📂 Drop more files here</span>
|
|
462
|
+
</div>`;
|
|
463
|
+
html += '</div>';
|
|
464
|
+
return html;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ── Event Handlers ───────────────────────────────────────
|
|
468
|
+
|
|
469
|
+
function attachHandlers() {
|
|
470
|
+
// Skip / Back button
|
|
471
|
+
document.getElementById('stub-player-skip-outline-btn')?.addEventListener('click', () => {
|
|
472
|
+
if (stageData && stageData.stageNumber < 3) {
|
|
473
|
+
localStorage.setItem('coursecode-skipOutline', 'true');
|
|
474
|
+
}
|
|
475
|
+
hide();
|
|
476
|
+
if (!courseLoaded && context.loadCourse) {
|
|
477
|
+
context.loadCourse();
|
|
478
|
+
courseLoaded = true;
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Stepper clicks
|
|
483
|
+
outlineContent.querySelectorAll('.stepper-step').forEach(btn => {
|
|
484
|
+
btn.addEventListener('click', () => {
|
|
485
|
+
viewingStage = parseInt(btn.dataset.stage, 10);
|
|
486
|
+
renderDashboard();
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// Open file in editor
|
|
491
|
+
const openBtn = document.getElementById('outline-open-file-btn');
|
|
492
|
+
if (openBtn) {
|
|
493
|
+
openBtn.addEventListener('click', () => {
|
|
494
|
+
const filePath = openBtn.dataset.path;
|
|
495
|
+
if (filePath) window.open('vscode://file' + filePath, '_blank');
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Convert button
|
|
500
|
+
const convertBtn = document.getElementById('outline-convert-btn');
|
|
501
|
+
if (convertBtn) {
|
|
502
|
+
convertBtn.addEventListener('click', async () => {
|
|
503
|
+
const overwrite = document.getElementById('outline-convert-overwrite')?.checked ?? true;
|
|
504
|
+
convertBtn.disabled = true;
|
|
505
|
+
convertBtn.textContent = 'Converting…';
|
|
506
|
+
convertBtn.classList.add('converting');
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
const res = await fetch(`/__refs-convert?overwrite=${overwrite}`, { method: 'POST' });
|
|
510
|
+
const result = await res.json();
|
|
511
|
+
if (result.success) {
|
|
512
|
+
convertBtn.textContent = '✅ Conversion complete';
|
|
513
|
+
setTimeout(async () => {
|
|
514
|
+
try {
|
|
515
|
+
const stageRes = await fetch('/__stage');
|
|
516
|
+
if (stageRes.ok) stageData = await stageRes.json();
|
|
517
|
+
} catch { /* ignore */ }
|
|
518
|
+
renderDashboard();
|
|
519
|
+
}, 1500);
|
|
520
|
+
} else {
|
|
521
|
+
convertBtn.textContent = '❌ Conversion failed';
|
|
522
|
+
convertBtn.disabled = false;
|
|
523
|
+
convertBtn.classList.remove('converting');
|
|
524
|
+
}
|
|
525
|
+
} catch {
|
|
526
|
+
convertBtn.textContent = '❌ Conversion failed';
|
|
527
|
+
convertBtn.disabled = false;
|
|
528
|
+
convertBtn.classList.remove('converting');
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Dropzone for reference files (the card itself is the drop target)
|
|
534
|
+
const dropzone = document.getElementById('outline-refs-dropzone');
|
|
535
|
+
if (dropzone) {
|
|
536
|
+
setupRefsDropzone(dropzone);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
// Branch option: Convert PowerPoint to Course
|
|
541
|
+
document.getElementById('branch-convert')?.addEventListener('click', () => {
|
|
542
|
+
showConvertSubView();
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Convert view back button
|
|
546
|
+
document.getElementById('convert-back-btn')?.addEventListener('click', () => {
|
|
547
|
+
renderDashboard();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Convert-to-course dropzone
|
|
551
|
+
const convertZone = document.getElementById('outline-convert-dropzone');
|
|
552
|
+
if (convertZone) {
|
|
553
|
+
setupConvertDropzone(convertZone);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
function showConvertSubView() {
|
|
559
|
+
const stageContentStart = outlineContent.querySelector('.outline-stage-header');
|
|
560
|
+
if (!stageContentStart) return;
|
|
561
|
+
|
|
562
|
+
const siblings = [];
|
|
563
|
+
let el = stageContentStart.nextElementSibling;
|
|
564
|
+
while (el) { siblings.push(el); el = el.nextElementSibling; }
|
|
565
|
+
siblings.forEach(s => s.remove());
|
|
566
|
+
|
|
567
|
+
const convertHtml = document.createElement('div');
|
|
568
|
+
convertHtml.innerHTML = renderConvertView();
|
|
569
|
+
stageContentStart.after(convertHtml);
|
|
570
|
+
|
|
571
|
+
document.getElementById('convert-back-btn')?.addEventListener('click', () => renderDashboard());
|
|
572
|
+
const convertZone = document.getElementById('outline-convert-dropzone');
|
|
573
|
+
if (convertZone) setupConvertDropzone(convertZone);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function setupRefsDropzone(dropzone) {
|
|
577
|
+
const ALLOWED = ['.pdf', '.docx', '.doc', '.pptx', '.ppt', '.md'];
|
|
578
|
+
const card = dropzone.closest('.outline-refs-card') || dropzone;
|
|
579
|
+
|
|
580
|
+
card.addEventListener('dragover', e => {
|
|
581
|
+
e.preventDefault();
|
|
582
|
+
card.classList.add('dragover');
|
|
583
|
+
});
|
|
584
|
+
card.addEventListener('dragleave', (e) => {
|
|
585
|
+
if (!card.contains(e.relatedTarget)) card.classList.remove('dragover');
|
|
586
|
+
});
|
|
587
|
+
card.addEventListener('drop', async (e) => {
|
|
588
|
+
e.preventDefault();
|
|
589
|
+
card.classList.remove('dragover');
|
|
590
|
+
|
|
591
|
+
const files = [...e.dataTransfer.files].filter(f =>
|
|
592
|
+
ALLOWED.some(ext => f.name.toLowerCase().endsWith(ext))
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
const textEl = card.querySelector('.outline-dropzone-text') || card.querySelector('.outline-dropzone-bar-text');
|
|
596
|
+
if (files.length === 0) {
|
|
597
|
+
if (textEl) textEl.textContent = 'No supported files found';
|
|
598
|
+
setTimeout(() => renderDashboard(), 2000);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
card.classList.add('uploading');
|
|
603
|
+
if (textEl) textEl.textContent = `Uploading ${files.length} file(s)...`;
|
|
604
|
+
|
|
605
|
+
const formData = new FormData();
|
|
606
|
+
for (const file of files) formData.append('files', file, file.name);
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
const res = await fetch('/__refs-upload', { method: 'POST', body: formData });
|
|
610
|
+
const result = await res.json();
|
|
611
|
+
if (result.success) {
|
|
612
|
+
if (textEl) textEl.textContent = `✅ Uploaded ${files.length} file(s)`;
|
|
613
|
+
setTimeout(async () => {
|
|
614
|
+
try {
|
|
615
|
+
const stageRes = await fetch('/__stage');
|
|
616
|
+
if (stageRes.ok) stageData = await stageRes.json();
|
|
617
|
+
} catch { /* ignore */ }
|
|
618
|
+
renderDashboard();
|
|
619
|
+
}, 1500);
|
|
620
|
+
} else {
|
|
621
|
+
if (textEl) textEl.textContent = '❌ Upload failed';
|
|
622
|
+
}
|
|
623
|
+
} catch {
|
|
624
|
+
if (textEl) textEl.textContent = '❌ Upload failed';
|
|
625
|
+
}
|
|
626
|
+
card.classList.remove('uploading');
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function setupConvertDropzone(convertZone) {
|
|
631
|
+
convertZone.addEventListener('dragover', e => {
|
|
632
|
+
e.preventDefault();
|
|
633
|
+
convertZone.classList.add('dragover');
|
|
634
|
+
});
|
|
635
|
+
convertZone.addEventListener('dragleave', (e) => {
|
|
636
|
+
if (!convertZone.contains(e.relatedTarget)) convertZone.classList.remove('dragover');
|
|
637
|
+
});
|
|
638
|
+
convertZone.addEventListener('drop', async (e) => {
|
|
639
|
+
e.preventDefault();
|
|
640
|
+
convertZone.classList.remove('dragover');
|
|
641
|
+
|
|
642
|
+
const pptxFiles = [...e.dataTransfer.files].filter(f =>
|
|
643
|
+
f.name.toLowerCase().endsWith('.pptx')
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
const textEl = convertZone.querySelector('.outline-dropzone-text');
|
|
647
|
+
if (pptxFiles.length === 0) {
|
|
648
|
+
if (textEl) textEl.textContent = 'Only .pptx files can be converted';
|
|
649
|
+
setTimeout(() => renderDashboard(), 2000);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
convertZone.classList.add('uploading');
|
|
654
|
+
if (textEl) textEl.textContent = `Converting ${pptxFiles[0].name}…`;
|
|
655
|
+
|
|
656
|
+
const formData = new FormData();
|
|
657
|
+
formData.append('file', pptxFiles[0], pptxFiles[0].name);
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
const res = await fetch('/__import', { method: 'POST', body: formData });
|
|
661
|
+
const result = await res.json();
|
|
662
|
+
if (result.success) {
|
|
663
|
+
if (textEl) textEl.textContent = `✅ ${result.slideCount} slides converted`;
|
|
664
|
+
setTimeout(async () => {
|
|
665
|
+
try {
|
|
666
|
+
const stageRes = await fetch('/__stage');
|
|
667
|
+
if (stageRes.ok) {
|
|
668
|
+
stageData = await stageRes.json();
|
|
669
|
+
viewingStage = stageData.stageNumber;
|
|
670
|
+
}
|
|
671
|
+
} catch { /* ignore */ }
|
|
672
|
+
renderDashboard();
|
|
673
|
+
}, 1500);
|
|
674
|
+
} else {
|
|
675
|
+
if (textEl) textEl.textContent = `❌ Conversion failed: ${result.error || 'Unknown error'}`;
|
|
676
|
+
}
|
|
677
|
+
} catch {
|
|
678
|
+
if (textEl) textEl.textContent = '❌ Conversion failed';
|
|
679
|
+
}
|
|
680
|
+
convertZone.classList.remove('uploading');
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function escapeHtml(str) {
|
|
685
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return { checkStage, toggle, hide, isVisible: () => isVisible };
|
|
689
|
+
}
|