coursecode 0.1.48 → 0.1.50

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/README.md CHANGED
@@ -6,6 +6,16 @@ CourseCode creates real project files you can inspect, version, and edit directl
6
6
 
7
7
  Bring your own PDFs, Word docs, or PowerPoints, use AI to accelerate authoring, and deploy to any LMS format without vendor lock-in or subscriptions.
8
8
 
9
+ ## View the demo
10
+
11
+ - [View the live CourseCode demo course](https://preview.coursecodecloud.com/preview/coursecode-demo/nTfJU2qvp23P0mguxrrGFZr4FlXfM03N)
12
+
13
+ ## Explore the ecosystem
14
+
15
+ - [Build SCORM courses with the framework](https://coursecodeframework.com/scorm/)
16
+ - [Use CourseCode Desktop if you prefer a GUI](https://coursecodedesktop.com/scorm/)
17
+ - [Use CourseCode Cloud for hosted delivery](https://coursecodecloud.com)
18
+
9
19
  ## Features
10
20
 
11
21
  - **MCP integration**: Works with Claude Code, Codex, Cursor, CourseCode Desktop, and any MCP-capable AI tool — previews, screenshots, linting, and testing without manual file sharing
@@ -20,6 +20,9 @@ import { createLoginHandlers } from './login-screen.js';
20
20
  const config = window.STUB_CONFIG || {};
21
21
  const LAUNCH_URL = config.launchUrl || '/';
22
22
  const START_SLIDE = config.startSlide || null;
23
+ const QUERY = new URLSearchParams(window.location.search);
24
+ const SHOW_HEADER = resolveShowHeader();
25
+ const INITIAL_SKIP_GATING = resolveInitialSkipGating();
23
26
 
24
27
  // State
25
28
  let isInitialized = false;
@@ -44,27 +47,38 @@ function init() {
44
47
  ? createContentViewerHandlers({ initialContent: config.courseContent || null })
45
48
  : null;
46
49
 
50
+ document.body.classList.toggle('stub-player-header-hidden', !SHOW_HEADER);
51
+
47
52
  // Header Bar — viewer mode: Review + More menu (skip gating, reset)
48
- createHeaderBarHandlers({
49
- onToggle: () => {},
50
- onContent: () => {
51
- if (contentPanel) {
52
- contentPanel.classList.toggle('visible');
53
- if (contentPanel.classList.contains('visible') && !contentLoaded && contentHandlers) {
54
- contentHandlers.loadContent();
55
- contentLoaded = true;
53
+ if (SHOW_HEADER) {
54
+ createHeaderBarHandlers({
55
+ onToggle: () => {},
56
+ onContent: () => {
57
+ if (contentPanel) {
58
+ contentPanel.classList.toggle('visible');
59
+ if (contentPanel.classList.contains('visible') && !contentLoaded && contentHandlers) {
60
+ contentHandlers.loadContent();
61
+ contentLoaded = true;
62
+ }
56
63
  }
57
- }
58
- },
59
- onReset: () => doReset(),
60
- onSkipGating: (enabled) => {
61
- if (enabled) {
62
- loadCourse();
63
- } else {
64
- doReset();
65
- }
64
+ },
65
+ onReset: () => doReset(),
66
+ onSkipGating: (enabled) => {
67
+ if (enabled) {
68
+ loadCourse();
69
+ } else {
70
+ doReset();
71
+ }
72
+ },
73
+ initialSkipGating: INITIAL_SKIP_GATING
74
+ });
75
+ } else if (INITIAL_SKIP_GATING !== null) {
76
+ try {
77
+ localStorage.setItem('coursecode-skipGating', INITIAL_SKIP_GATING ? 'true' : 'false');
78
+ } catch {
79
+ // ignore storage failures
66
80
  }
67
- });
81
+ }
68
82
 
69
83
  // Load course if no login screen active
70
84
  if (!document.getElementById('stub-player-login-screen')?.classList.contains('visible')) {
@@ -75,13 +89,39 @@ function init() {
75
89
  setupOutsideClickListener(contentPanel);
76
90
  }
77
91
 
92
+ function resolveShowHeader() {
93
+ const previewHeader = QUERY.get('previewHeader');
94
+ if (previewHeader === 'hidden') return false;
95
+ if (previewHeader === 'visible') return true;
96
+
97
+ const hideHeader = resolveBooleanQuery('hideHeader');
98
+ if (hideHeader !== null) return !hideHeader;
99
+
100
+ return config.showHeader !== false;
101
+ }
102
+
103
+ function resolveInitialSkipGating() {
104
+ const querySkipGating = resolveBooleanQuery('skipGating');
105
+ if (querySkipGating !== null) return querySkipGating;
106
+ return typeof config.skipGating === 'boolean' ? config.skipGating : null;
107
+ }
108
+
109
+ function resolveBooleanQuery(name) {
110
+ const value = QUERY.get(name);
111
+ if (value === null) return null;
112
+ const normalized = value.toLowerCase();
113
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
114
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
115
+ return null;
116
+ }
117
+
78
118
  // =============================================================================
79
119
  // COURSE LOADING & NAVIGATION
80
120
  // =============================================================================
81
121
 
82
122
  function loadCourse() {
83
123
  const frame = document.getElementById('stub-player-course-frame');
84
- const skipGating = document.getElementById('stub-player-skip-gating')?.checked;
124
+ const skipGating = resolveSkipGating();
85
125
  let url = LAUNCH_URL;
86
126
  if (skipGating) url += (url.includes('?') ? '&' : '?') + 'skipGating=true';
87
127
  frame.src = url;
@@ -99,7 +139,7 @@ function handleFrameLoad() {
99
139
  win.cmi5 = window.cmi5;
100
140
  win.lti = window.lti;
101
141
 
102
- if (document.getElementById('stub-player-skip-gating')?.checked) {
142
+ if (resolveSkipGating()) {
103
143
  win.__SCORM_PREVIEW_SKIP_GATING = true;
104
144
  }
105
145
 
@@ -115,6 +155,11 @@ function handleFrameLoad() {
115
155
  }
116
156
  }
117
157
 
158
+ function resolveSkipGating() {
159
+ if (INITIAL_SKIP_GATING !== null) return INITIAL_SKIP_GATING;
160
+ return document.getElementById('stub-player-skip-gating')?.checked === true;
161
+ }
162
+
118
163
  function navigateToSlide(contentWindow, slideIdOrIndex) {
119
164
  const maxAttempts = 50;
120
165
  let attempts = 0;
@@ -102,9 +102,10 @@ export function generateHeaderBar({ isLive, hasContent }) {
102
102
  * @param {Function} callbacks.onReset - () => void
103
103
  * @param {Function} callbacks.onSkipGating - (enabled) => void
104
104
  * @param {Function} callbacks.onStatus - () => void
105
+ * @param {boolean|null} callbacks.initialSkipGating - query/config override for skip-gating state
105
106
  */
106
107
  export function createHeaderBarHandlers(callbacks) {
107
- const { onToggle, onDebug, onConfig, onContent, onInteract, onCatalog, onEdit, onReset, onSkipGating, onStatus } = callbacks;
108
+ const { onToggle, onDebug, onConfig, onContent, onInteract, onCatalog, onEdit, onReset, onSkipGating, onStatus, initialSkipGating = null } = callbacks;
108
109
  const closeMoreMenu = () => {
109
110
  document.getElementById('stub-player-more-menu')?.classList.remove('visible');
110
111
  };
@@ -211,6 +212,7 @@ export function createHeaderBarHandlers(callbacks) {
211
212
  if (skipGatingCheckbox) {
212
213
  // Restore persisted state (defaults to true / skip on)
213
214
  const persistedSkip = (() => {
215
+ if (initialSkipGating !== null) return initialSkipGating;
214
216
  try {
215
217
  const stored = localStorage.getItem(SKIP_GATING_STORAGE_KEY);
216
218
  return stored !== 'false';
@@ -333,6 +333,15 @@
333
333
  height: 100%;
334
334
  }
335
335
 
336
+ body.stub-player-header-hidden #stub-player-header {
337
+ display: none;
338
+ }
339
+
340
+ body.stub-player-header-hidden #stub-player-course-frame {
341
+ top: 0;
342
+ height: 100%;
343
+ }
344
+
336
345
  /* Responsive: icon-only mode on narrow viewports */
337
346
  @media (max-width: 820px) {
338
347
  #stub-player-header .btn-label {
@@ -7,6 +7,8 @@ export interface StubPlayerConfig {
7
7
  liveReload?: boolean
8
8
  courseContent?: string
9
9
  startSlide?: string | number
10
+ showHeader?: boolean
11
+ skipGating?: boolean | null
10
12
  isDesktop?: boolean
11
13
  moduleBasePath?: string
12
14
  }
@@ -53,12 +53,15 @@ export { escapeHtml };
53
53
  * @param {boolean} [config.liveReload] - True to enable live reload via SSE
54
54
  * @param {string} [config.courseContent] - Markdown/HTML content for the content viewer
55
55
  * @param {string|number} [config.startSlide] - Slide ID or index to navigate to on load
56
+ * @param {boolean} [config.showHeader] - False to hide the preview player header
57
+ * @param {boolean} [config.skipGating] - True to bypass navigation locks
56
58
  * @returns {string} - Complete HTML for the player page
57
59
  */
58
60
  export function generateStubPlayer(config) {
59
- const { title, launchUrl, storageKey, passwordHash, isLive, liveReload, courseContent, startSlide, isDesktop, moduleBasePath = '/__stub-player' } = config;
61
+ const { title, launchUrl, storageKey, passwordHash, isLive, liveReload, courseContent, startSlide, isDesktop, showHeader = true, skipGating = null, moduleBasePath = '/__stub-player' } = config;
60
62
  const hasPassword = !!passwordHash;
61
63
  const hasContent = !!courseContent;
64
+ const headerVisible = showHeader !== false;
62
65
 
63
66
  const stubPlayerStyles = loadStyles(isLive);
64
67
 
@@ -73,7 +76,7 @@ export function generateStubPlayer(config) {
73
76
  ${stubPlayerStyles}
74
77
  </style>
75
78
  </head>
76
- <body>
79
+ <body class="${headerVisible ? '' : 'stub-player-header-hidden'}">
77
80
  ${hasPassword ? generateLoginScreen({ title: escapeHtml(title) }) : ''}
78
81
 
79
82
  <iframe id="stub-player-course-frame" name="stub-player-course-frame"></iframe>
@@ -109,6 +112,8 @@ ${stubPlayerStyles}
109
112
  liveReload: ${liveReload || false},
110
113
  startSlide: ${startSlide !== undefined ? JSON.stringify(startSlide) : 'null'},
111
114
  courseContent: ${hasContent ? JSON.stringify(courseContent) : 'null'},
115
+ showHeader: ${headerVisible},
116
+ skipGating: ${typeof skipGating === 'boolean' ? skipGating : 'null'},
112
117
  isDesktop: ${isDesktop || false},
113
118
  isCI: ${!!process.env.CI}
114
119
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coursecode",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "description": "Multi-format course authoring framework with CLI tools (SCORM 2004, SCORM 1.2, cmi5, LTI 1.3)",
5
5
  "type": "module",
6
6
  "bin": {