@growthbeaker/vscode-help-docs 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.
@@ -0,0 +1,312 @@
1
+ // @ts-nocheck
2
+ /* Webview-side script for the Help Viewer panel */
3
+
4
+ (function () {
5
+ const vscode = acquireVsCodeApi();
6
+ let currentPath = null;
7
+ let mermaidReady = false;
8
+
9
+ /** Initialize the application */
10
+ function initialize() {
11
+ // Restore previous state
12
+ const state = vscode.getState();
13
+ if (state) {
14
+ currentPath = state.currentPath || null;
15
+ }
16
+
17
+ // Listen for messages from the extension host
18
+ window.addEventListener('message', handleMessage);
19
+
20
+ // Intercept link clicks in content area
21
+ const contentBody = document.getElementById('content-body');
22
+ if (contentBody) {
23
+ contentBody.addEventListener('click', handleLinkClick);
24
+ }
25
+
26
+ // Initialize mermaid if available
27
+ initMermaid();
28
+
29
+ // Signal ready to extension host
30
+ vscode.postMessage({ type: 'ready' });
31
+ }
32
+
33
+ /** Initialize mermaid.js if it's loaded */
34
+ function initMermaid() {
35
+ if (typeof window.mermaid === 'undefined') {
36
+ return;
37
+ }
38
+
39
+ // Detect dark/light theme from VS Code CSS variables
40
+ var isDark = getComputedStyle(document.body)
41
+ .getPropertyValue('--vscode-editor-background')
42
+ .trim()
43
+ .match(/^#([0-9a-f]{2})/i);
44
+ var theme = 'default';
45
+ if (isDark) {
46
+ // If the red channel of the background is below 0x80, it's a dark theme
47
+ var brightness = parseInt(isDark[1], 16);
48
+ theme = brightness < 128 ? 'dark' : 'default';
49
+ }
50
+
51
+ window.mermaid.initialize({
52
+ startOnLoad: false,
53
+ theme: theme,
54
+ securityLevel: 'strict',
55
+ });
56
+ mermaidReady = true;
57
+ }
58
+
59
+ /** Render any mermaid diagrams in the content pane */
60
+ function renderMermaidDiagrams() {
61
+ if (!mermaidReady) return;
62
+
63
+ var mermaidDivs = document.querySelectorAll('#content-body .mermaid');
64
+ if (mermaidDivs.length === 0) return;
65
+
66
+ // mermaid.run() finds and renders all .mermaid elements
67
+ try {
68
+ window.mermaid.run({ nodes: mermaidDivs });
69
+ } catch (e) {
70
+ console.warn('Mermaid rendering error:', e);
71
+ }
72
+ }
73
+
74
+ /** Handle messages from the extension host */
75
+ function handleMessage(event) {
76
+ const message = event.data;
77
+
78
+ switch (message.type) {
79
+ case 'contentLoaded':
80
+ handleContentLoaded(message);
81
+ break;
82
+
83
+ case 'navTree':
84
+ renderNavTree(message.tree);
85
+ break;
86
+
87
+ case 'error':
88
+ showErrorBanner(message.message);
89
+ break;
90
+
91
+ case 'anchorNotFound':
92
+ showNotification(
93
+ 'Section \u0060' + message.anchor + '\u0060 not found on this page'
94
+ );
95
+ scrollToTop();
96
+ break;
97
+
98
+ case 'scrollToAnchor':
99
+ scrollToAnchor(message.anchor);
100
+ break;
101
+
102
+ case 'scrollToTop':
103
+ scrollToTop();
104
+ break;
105
+ }
106
+ }
107
+
108
+ /** Update the content pane with rendered HTML */
109
+ function handleContentLoaded(message) {
110
+ const contentBody = document.getElementById('content-body');
111
+ if (contentBody) {
112
+ contentBody.innerHTML = message.html;
113
+ }
114
+
115
+ currentPath = message.path;
116
+
117
+ // Update active nav item
118
+ updateActiveNavItem();
119
+
120
+ // Persist state
121
+ vscode.setState({ currentPath });
122
+
123
+ // Hide any previous error banner
124
+ hideErrorBanner();
125
+
126
+ // Scroll to top of content
127
+ scrollToTop();
128
+
129
+ // Render any mermaid diagrams
130
+ renderMermaidDiagrams();
131
+ }
132
+
133
+ /** Build and render the navigation tree sidebar */
134
+ function renderNavTree(tree) {
135
+ const sidebar = document.getElementById('nav-sidebar');
136
+ if (!sidebar) return;
137
+
138
+ sidebar.innerHTML = '';
139
+ const fragment = document.createDocumentFragment();
140
+
141
+ for (const node of tree) {
142
+ fragment.appendChild(createNavNode(node));
143
+ }
144
+
145
+ sidebar.appendChild(fragment);
146
+ }
147
+
148
+ /** Create a DOM element for a nav tree node */
149
+ function createNavNode(node) {
150
+ if (node.type === 'folder') {
151
+ const details = document.createElement('details');
152
+ details.className = 'nav-folder';
153
+ if (!node.collapsed) {
154
+ details.open = true;
155
+ }
156
+
157
+ const summary = document.createElement('summary');
158
+ summary.className = 'nav-folder-header';
159
+ summary.textContent = node.title;
160
+ details.appendChild(summary);
161
+
162
+ // Persist collapse state
163
+ details.addEventListener('toggle', function () {
164
+ vscode.setState({
165
+ ...vscode.getState(),
166
+ ['collapse_' + node.path]: !details.open,
167
+ });
168
+ });
169
+
170
+ // Restore collapse state
171
+ const state = vscode.getState();
172
+ if (state && state['collapse_' + node.path] !== undefined) {
173
+ details.open = !state['collapse_' + node.path];
174
+ }
175
+
176
+ if (node.children) {
177
+ for (const child of node.children) {
178
+ details.appendChild(createNavNode(child));
179
+ }
180
+ }
181
+
182
+ return details;
183
+ } else {
184
+ const link = document.createElement('a');
185
+ link.className = 'nav-item';
186
+ link.textContent = node.title;
187
+ link.href = '#';
188
+ link.dataset.path = node.path;
189
+
190
+ link.addEventListener('click', function (e) {
191
+ e.preventDefault();
192
+ vscode.postMessage({ type: 'navigateTo', path: node.path });
193
+ });
194
+
195
+ return link;
196
+ }
197
+ }
198
+
199
+ /** Intercept clicks on links within the content pane */
200
+ function handleLinkClick(event) {
201
+ const link = event.target.closest('a');
202
+ if (!link) return;
203
+
204
+ const href = link.getAttribute('href');
205
+ if (!href) return;
206
+
207
+ event.preventDefault();
208
+
209
+ // External links
210
+ if (href.startsWith('http://') || href.startsWith('https://')) {
211
+ vscode.postMessage({ type: 'openExternal', url: href });
212
+ return;
213
+ }
214
+
215
+ // Anchor-only links (#section-name)
216
+ if (href.startsWith('#')) {
217
+ const anchor = href.substring(1);
218
+ scrollToAnchor(anchor);
219
+ return;
220
+ }
221
+
222
+ // Internal links — may include anchor (page.md#section)
223
+ const hashIndex = href.indexOf('#');
224
+ if (hashIndex !== -1) {
225
+ const pagePath = href.substring(0, hashIndex);
226
+ const anchor = href.substring(hashIndex + 1);
227
+ vscode.postMessage({
228
+ type: 'navigateToAnchor',
229
+ path: pagePath || undefined,
230
+ anchor: anchor,
231
+ });
232
+ } else {
233
+ vscode.postMessage({ type: 'navigateTo', path: href });
234
+ }
235
+ }
236
+
237
+ /** Scroll to an anchor element by ID */
238
+ function scrollToAnchor(anchor) {
239
+ const element = document.getElementById(anchor);
240
+ if (element) {
241
+ element.scrollIntoView({ behavior: 'smooth' });
242
+ } else {
243
+ // AC 1.5.5: anchor not found — show notification and scroll to top
244
+ showNotification(
245
+ 'Section \u0060' + anchor + '\u0060 not found on this page'
246
+ );
247
+ scrollToTop();
248
+ }
249
+ }
250
+
251
+ /** Scroll the content pane to the top */
252
+ function scrollToTop() {
253
+ const contentPane = document.getElementById('content-pane');
254
+ if (contentPane) {
255
+ contentPane.scrollTop = 0;
256
+ }
257
+ }
258
+
259
+ /** Show a non-blocking error banner (AC 1.5.4) */
260
+ function showErrorBanner(message) {
261
+ const banner = document.getElementById('error-banner');
262
+ if (!banner) return;
263
+
264
+ banner.textContent = message;
265
+ banner.style.display = 'block';
266
+
267
+ // Auto-dismiss after 5 seconds
268
+ setTimeout(function () {
269
+ hideErrorBanner();
270
+ }, 5000);
271
+ }
272
+
273
+ /** Hide the error banner */
274
+ function hideErrorBanner() {
275
+ const banner = document.getElementById('error-banner');
276
+ if (banner) {
277
+ banner.style.display = 'none';
278
+ }
279
+ }
280
+
281
+ /** Show a brief notification toast (AC 1.5.5) */
282
+ function showNotification(message) {
283
+ const toast = document.getElementById('notification-toast');
284
+ if (!toast) return;
285
+
286
+ toast.textContent = message;
287
+ toast.style.display = 'block';
288
+
289
+ setTimeout(function () {
290
+ toast.style.display = 'none';
291
+ }, 3000);
292
+ }
293
+
294
+ /** Update the active state on nav items */
295
+ function updateActiveNavItem() {
296
+ document.querySelectorAll('.nav-item.active').forEach(function (el) {
297
+ el.classList.remove('active');
298
+ });
299
+ document.querySelectorAll('.nav-item').forEach(function (el) {
300
+ if (el.dataset.path === currentPath) {
301
+ el.classList.add('active');
302
+ }
303
+ });
304
+ }
305
+
306
+ // Initialize when DOM is ready
307
+ if (document.readyState === 'loading') {
308
+ document.addEventListener('DOMContentLoaded', initialize);
309
+ } else {
310
+ initialize();
311
+ }
312
+ })();
@@ -0,0 +1,256 @@
1
+ /* Help Viewer — VS Code theme-aware styles */
2
+
3
+ * {
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ body {
10
+ color: var(--vscode-foreground);
11
+ background: var(--vscode-editor-background);
12
+ font-family: var(--vscode-font-family);
13
+ font-size: var(--vscode-font-size);
14
+ line-height: 1.6;
15
+ height: 100vh;
16
+ overflow: hidden;
17
+ }
18
+
19
+ .help-viewer {
20
+ display: flex;
21
+ height: 100vh;
22
+ }
23
+
24
+ /* --- Navigation Sidebar --- */
25
+
26
+ .nav-sidebar {
27
+ width: 220px;
28
+ min-width: 180px;
29
+ background: var(--vscode-sideBar-background);
30
+ border-right: 1px solid var(--vscode-panel-border);
31
+ overflow-y: auto;
32
+ padding: 8px 0;
33
+ flex-shrink: 0;
34
+ }
35
+
36
+ .nav-folder {
37
+ margin: 0;
38
+ }
39
+
40
+ .nav-folder-header {
41
+ display: block;
42
+ padding: 6px 16px;
43
+ cursor: pointer;
44
+ font-weight: 600;
45
+ font-size: 0.85em;
46
+ text-transform: uppercase;
47
+ letter-spacing: 0.03em;
48
+ color: var(--vscode-sideBarSectionHeader-foreground, var(--vscode-foreground));
49
+ user-select: none;
50
+ list-style: none;
51
+ }
52
+
53
+ .nav-folder-header::-webkit-details-marker {
54
+ display: none;
55
+ }
56
+
57
+ .nav-folder-header::before {
58
+ content: '\25B6';
59
+ display: inline-block;
60
+ margin-right: 6px;
61
+ font-size: 0.7em;
62
+ transition: transform 0.15s ease;
63
+ }
64
+
65
+ details[open] > .nav-folder-header::before {
66
+ transform: rotate(90deg);
67
+ }
68
+
69
+ .nav-folder-header:hover {
70
+ background: var(--vscode-list-hoverBackground);
71
+ }
72
+
73
+ .nav-item {
74
+ display: block;
75
+ padding: 4px 16px 4px 32px;
76
+ color: var(--vscode-foreground);
77
+ text-decoration: none;
78
+ font-size: 0.9em;
79
+ cursor: pointer;
80
+ border-left: 2px solid transparent;
81
+ }
82
+
83
+ .nav-item:hover {
84
+ background: var(--vscode-list-hoverBackground);
85
+ }
86
+
87
+ .nav-item.active {
88
+ background: var(--vscode-list-activeSelectionBackground);
89
+ color: var(--vscode-list-activeSelectionForeground, var(--vscode-foreground));
90
+ border-left-color: var(--vscode-focusBorder);
91
+ }
92
+
93
+ /* --- Content Pane --- */
94
+
95
+ .content-pane {
96
+ flex: 1;
97
+ overflow-y: auto;
98
+ padding: 24px 32px;
99
+ position: relative;
100
+ }
101
+
102
+ #content-body {
103
+ max-width: 780px;
104
+ margin: 0 auto;
105
+ }
106
+
107
+ /* --- Error Banner (AC 1.5.4) --- */
108
+
109
+ .error-banner {
110
+ background: var(--vscode-inputValidation-errorBackground, #5a1d1d);
111
+ border: 1px solid var(--vscode-inputValidation-errorBorder, #be1100);
112
+ color: var(--vscode-errorForeground, var(--vscode-foreground));
113
+ padding: 8px 16px;
114
+ border-radius: 4px;
115
+ margin-bottom: 16px;
116
+ font-size: 0.9em;
117
+ }
118
+
119
+ /* --- Notification Toast (AC 1.5.5) --- */
120
+
121
+ .notification-toast {
122
+ position: fixed;
123
+ bottom: 24px;
124
+ right: 24px;
125
+ background: var(--vscode-notifications-background, var(--vscode-editor-background));
126
+ border: 1px solid var(--vscode-notifications-border, var(--vscode-panel-border));
127
+ color: var(--vscode-notifications-foreground, var(--vscode-foreground));
128
+ padding: 10px 16px;
129
+ border-radius: 4px;
130
+ font-size: 0.85em;
131
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
132
+ z-index: 100;
133
+ max-width: 350px;
134
+ }
135
+
136
+ /* --- Typography --- */
137
+
138
+ #content-body h1,
139
+ #content-body h2,
140
+ #content-body h3,
141
+ #content-body h4,
142
+ #content-body h5,
143
+ #content-body h6 {
144
+ color: var(--vscode-foreground);
145
+ margin-top: 1.5em;
146
+ margin-bottom: 0.5em;
147
+ line-height: 1.3;
148
+ }
149
+
150
+ #content-body h1 { font-size: 1.8em; margin-top: 0; }
151
+ #content-body h2 { font-size: 1.4em; border-bottom: 1px solid var(--vscode-panel-border); padding-bottom: 0.3em; }
152
+ #content-body h3 { font-size: 1.15em; }
153
+
154
+ #content-body p {
155
+ margin-bottom: 1em;
156
+ }
157
+
158
+ #content-body a {
159
+ color: var(--vscode-textLink-foreground);
160
+ text-decoration: none;
161
+ }
162
+
163
+ #content-body a:hover {
164
+ text-decoration: underline;
165
+ color: var(--vscode-textLink-activeForeground, var(--vscode-textLink-foreground));
166
+ }
167
+
168
+ #content-body code {
169
+ background: var(--vscode-textCodeBlock-background);
170
+ padding: 2px 5px;
171
+ border-radius: 3px;
172
+ font-family: var(--vscode-editor-font-family, monospace);
173
+ font-size: 0.9em;
174
+ }
175
+
176
+ #content-body pre {
177
+ background: var(--vscode-textCodeBlock-background);
178
+ padding: 16px;
179
+ border-radius: 4px;
180
+ overflow-x: auto;
181
+ margin-bottom: 1em;
182
+ }
183
+
184
+ #content-body pre code {
185
+ background: none;
186
+ padding: 0;
187
+ }
188
+
189
+ #content-body ul,
190
+ #content-body ol {
191
+ margin-bottom: 1em;
192
+ padding-left: 2em;
193
+ }
194
+
195
+ #content-body li {
196
+ margin-bottom: 0.25em;
197
+ }
198
+
199
+ #content-body blockquote {
200
+ border-left: 3px solid var(--vscode-textBlockQuote-border, var(--vscode-panel-border));
201
+ margin: 0 0 1em 0;
202
+ padding: 8px 16px;
203
+ color: var(--vscode-textBlockQuote-foreground, var(--vscode-foreground));
204
+ background: var(--vscode-textBlockQuote-background, transparent);
205
+ }
206
+
207
+ #content-body hr {
208
+ border: none;
209
+ border-top: 1px solid var(--vscode-panel-border);
210
+ margin: 1.5em 0;
211
+ }
212
+
213
+ #content-body table {
214
+ border-collapse: collapse;
215
+ margin-bottom: 1em;
216
+ width: 100%;
217
+ }
218
+
219
+ #content-body th,
220
+ #content-body td {
221
+ border: 1px solid var(--vscode-panel-border);
222
+ padding: 8px 12px;
223
+ text-align: left;
224
+ }
225
+
226
+ #content-body th {
227
+ background: var(--vscode-editorGroupHeader-tabsBackground, var(--vscode-sideBar-background));
228
+ font-weight: 600;
229
+ }
230
+
231
+ #content-body img {
232
+ max-width: 100%;
233
+ height: auto;
234
+ }
235
+
236
+ /* --- Responsive --- */
237
+
238
+ @media (max-width: 500px) {
239
+ .nav-sidebar {
240
+ position: fixed;
241
+ left: -100%;
242
+ top: 0;
243
+ height: 100vh;
244
+ z-index: 50;
245
+ transition: left 0.2s ease;
246
+ width: 260px;
247
+ }
248
+
249
+ .nav-sidebar.open {
250
+ left: 0;
251
+ }
252
+
253
+ .content-pane {
254
+ padding: 16px;
255
+ }
256
+ }