ted-mosby 1.0.0 → 1.1.1
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 +175 -20
- package/dist/cli.js +433 -16
- package/dist/cli.js.map +1 -1
- package/dist/prompts/wiki-system.d.ts +1 -1
- package/dist/prompts/wiki-system.d.ts.map +1 -1
- package/dist/prompts/wiki-system.js +52 -0
- package/dist/prompts/wiki-system.js.map +1 -1
- package/dist/rag/index.d.ts +68 -9
- package/dist/rag/index.d.ts.map +1 -1
- package/dist/rag/index.js +384 -79
- package/dist/rag/index.js.map +1 -1
- package/dist/site/scripts.d.ts +22 -0
- package/dist/site/scripts.d.ts.map +1 -0
- package/dist/site/scripts.js +855 -0
- package/dist/site/scripts.js.map +1 -0
- package/dist/site/styles.d.ts +11 -0
- package/dist/site/styles.d.ts.map +1 -0
- package/dist/site/styles.js +1572 -0
- package/dist/site/styles.js.map +1 -0
- package/dist/site/templates.d.ts +40 -0
- package/dist/site/templates.d.ts.map +1 -0
- package/dist/site/templates.js +336 -0
- package/dist/site/templates.js.map +1 -0
- package/dist/site-generator.d.ts +197 -0
- package/dist/site-generator.d.ts.map +1 -0
- package/dist/site-generator.js +694 -0
- package/dist/site-generator.js.map +1 -0
- package/dist/wiki-agent.d.ts +73 -0
- package/dist/wiki-agent.d.ts.map +1 -1
- package/dist/wiki-agent.js +1133 -7
- package/dist/wiki-agent.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-Side JavaScript for Interactive Wiki Features
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Full-text search
|
|
6
|
+
* - Guided tours / onboarding
|
|
7
|
+
* - Code explorer with syntax highlighting
|
|
8
|
+
* - Keyboard navigation
|
|
9
|
+
* - Theme switching
|
|
10
|
+
* - Progress tracking
|
|
11
|
+
* - Mermaid diagram interactions
|
|
12
|
+
*/
|
|
13
|
+
export function getClientScripts(features) {
|
|
14
|
+
return `
|
|
15
|
+
(function() {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
// ========================================
|
|
19
|
+
// Configuration & State
|
|
20
|
+
// ========================================
|
|
21
|
+
const config = window.WIKI_CONFIG || {};
|
|
22
|
+
const state = {
|
|
23
|
+
manifest: null,
|
|
24
|
+
searchIndex: [],
|
|
25
|
+
readPages: new Set(),
|
|
26
|
+
currentTour: null,
|
|
27
|
+
tourStep: 0
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ========================================
|
|
31
|
+
// Initialization
|
|
32
|
+
// ========================================
|
|
33
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
34
|
+
// Load manifest
|
|
35
|
+
await loadManifest();
|
|
36
|
+
|
|
37
|
+
// Initialize features
|
|
38
|
+
initTheme();
|
|
39
|
+
initMobileMenu();
|
|
40
|
+
initScrollToTop();
|
|
41
|
+
initCopyButtons();
|
|
42
|
+
initMermaid();
|
|
43
|
+
initSourceLinks();
|
|
44
|
+
initTocHighlight();
|
|
45
|
+
|
|
46
|
+
${features.search ? 'initSearch();' : ''}
|
|
47
|
+
${features.guidedTour ? 'initTours();' : ''}
|
|
48
|
+
${features.codeExplorer ? 'initCodeExplorer();' : ''}
|
|
49
|
+
${features.keyboardNav ? 'initKeyboardNav();' : ''}
|
|
50
|
+
${features.progressTracking ? 'initProgressTracking();' : ''}
|
|
51
|
+
|
|
52
|
+
// Highlight code blocks
|
|
53
|
+
if (window.Prism) {
|
|
54
|
+
Prism.highlightAll();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ========================================
|
|
59
|
+
// Manifest Loading
|
|
60
|
+
// ========================================
|
|
61
|
+
async function loadManifest() {
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch(config.rootPath + 'manifest.json');
|
|
64
|
+
state.manifest = await response.json();
|
|
65
|
+
state.searchIndex = state.manifest.searchIndex || [];
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.warn('Failed to load manifest:', e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ========================================
|
|
72
|
+
// Theme Management
|
|
73
|
+
// ========================================
|
|
74
|
+
function initTheme() {
|
|
75
|
+
const toggle = document.querySelector('.theme-toggle');
|
|
76
|
+
if (!toggle) return;
|
|
77
|
+
|
|
78
|
+
// Load saved theme
|
|
79
|
+
const savedTheme = localStorage.getItem('wiki-theme');
|
|
80
|
+
if (savedTheme) {
|
|
81
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
toggle.addEventListener('click', () => {
|
|
85
|
+
const current = document.documentElement.getAttribute('data-theme');
|
|
86
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
87
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
88
|
+
localStorage.setItem('wiki-theme', next);
|
|
89
|
+
showToast('Theme switched to ' + next, 'info');
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ========================================
|
|
94
|
+
// Mobile Menu
|
|
95
|
+
// ========================================
|
|
96
|
+
function initMobileMenu() {
|
|
97
|
+
const toggle = document.querySelector('.mobile-menu-toggle');
|
|
98
|
+
const sidebar = document.querySelector('.sidebar');
|
|
99
|
+
|
|
100
|
+
if (!toggle || !sidebar) return;
|
|
101
|
+
|
|
102
|
+
toggle.addEventListener('click', () => {
|
|
103
|
+
sidebar.classList.toggle('open');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Close on outside click
|
|
107
|
+
document.addEventListener('click', (e) => {
|
|
108
|
+
if (sidebar.classList.contains('open') &&
|
|
109
|
+
!sidebar.contains(e.target) &&
|
|
110
|
+
!toggle.contains(e.target)) {
|
|
111
|
+
sidebar.classList.remove('open');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ========================================
|
|
117
|
+
// Scroll to Top
|
|
118
|
+
// ========================================
|
|
119
|
+
function initScrollToTop() {
|
|
120
|
+
const btn = document.querySelector('.scroll-to-top');
|
|
121
|
+
if (!btn) return;
|
|
122
|
+
|
|
123
|
+
btn.addEventListener('click', () => {
|
|
124
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ========================================
|
|
129
|
+
// Code Copy Buttons
|
|
130
|
+
// ========================================
|
|
131
|
+
function initCopyButtons() {
|
|
132
|
+
document.querySelectorAll('.code-copy').forEach(btn => {
|
|
133
|
+
btn.addEventListener('click', async () => {
|
|
134
|
+
const codeBlock = btn.closest('.code-block');
|
|
135
|
+
const code = codeBlock.querySelector('code').textContent;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await navigator.clipboard.writeText(code);
|
|
139
|
+
btn.classList.add('copied');
|
|
140
|
+
showToast('Code copied!', 'success');
|
|
141
|
+
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
btn.classList.remove('copied');
|
|
144
|
+
}, 2000);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
showToast('Failed to copy', 'error');
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ========================================
|
|
153
|
+
// Mermaid Diagrams
|
|
154
|
+
// ========================================
|
|
155
|
+
function initMermaid() {
|
|
156
|
+
// Initialize mermaid
|
|
157
|
+
if (window.mermaid) {
|
|
158
|
+
const isDark = document.documentElement.getAttribute('data-theme') === 'dark' ||
|
|
159
|
+
(document.documentElement.getAttribute('data-theme') === 'auto' &&
|
|
160
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
161
|
+
|
|
162
|
+
mermaid.initialize({
|
|
163
|
+
startOnLoad: true,
|
|
164
|
+
theme: isDark ? 'dark' : 'default',
|
|
165
|
+
securityLevel: 'loose'
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Fullscreen buttons
|
|
170
|
+
document.querySelectorAll('.mermaid-fullscreen').forEach(btn => {
|
|
171
|
+
btn.addEventListener('click', () => {
|
|
172
|
+
const container = btn.closest('.mermaid-container');
|
|
173
|
+
const diagram = container.querySelector('.mermaid');
|
|
174
|
+
openMermaidFullscreen(diagram.innerHTML);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Close fullscreen
|
|
179
|
+
const modal = document.querySelector('.mermaid-fullscreen-modal');
|
|
180
|
+
if (modal) {
|
|
181
|
+
modal.querySelector('.mermaid-fullscreen-backdrop')?.addEventListener('click', closeMermaidFullscreen);
|
|
182
|
+
modal.querySelector('.mermaid-fullscreen-close')?.addEventListener('click', closeMermaidFullscreen);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function openMermaidFullscreen(content) {
|
|
187
|
+
const modal = document.querySelector('.mermaid-fullscreen-modal');
|
|
188
|
+
const diagramContainer = modal.querySelector('.mermaid-fullscreen-diagram');
|
|
189
|
+
diagramContainer.innerHTML = content;
|
|
190
|
+
modal.classList.add('open');
|
|
191
|
+
document.body.style.overflow = 'hidden';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function closeMermaidFullscreen() {
|
|
195
|
+
const modal = document.querySelector('.mermaid-fullscreen-modal');
|
|
196
|
+
modal.classList.remove('open');
|
|
197
|
+
document.body.style.overflow = '';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ========================================
|
|
201
|
+
// Source Links (Code Explorer)
|
|
202
|
+
// ========================================
|
|
203
|
+
function initSourceLinks() {
|
|
204
|
+
document.querySelectorAll('.source-link, .code-source').forEach(link => {
|
|
205
|
+
link.addEventListener('click', (e) => {
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
const source = link.dataset.source;
|
|
208
|
+
if (source && config.features?.codeExplorer) {
|
|
209
|
+
openCodeExplorer(source);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ========================================
|
|
216
|
+
// Table of Contents Highlighting
|
|
217
|
+
// ========================================
|
|
218
|
+
function initTocHighlight() {
|
|
219
|
+
const toc = document.querySelector('.toc-list');
|
|
220
|
+
if (!toc) return;
|
|
221
|
+
|
|
222
|
+
const headings = document.querySelectorAll('.heading-anchor');
|
|
223
|
+
const tocLinks = toc.querySelectorAll('a');
|
|
224
|
+
|
|
225
|
+
const observer = new IntersectionObserver((entries) => {
|
|
226
|
+
entries.forEach(entry => {
|
|
227
|
+
if (entry.isIntersecting) {
|
|
228
|
+
const id = entry.target.id;
|
|
229
|
+
tocLinks.forEach(link => {
|
|
230
|
+
link.classList.toggle('active', link.getAttribute('href') === '#' + id);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}, { rootMargin: '-100px 0px -66%' });
|
|
235
|
+
|
|
236
|
+
headings.forEach(h => observer.observe(h));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ========================================
|
|
240
|
+
// Search
|
|
241
|
+
// ========================================
|
|
242
|
+
${features.search ? `
|
|
243
|
+
function initSearch() {
|
|
244
|
+
const modal = document.querySelector('.search-modal');
|
|
245
|
+
const trigger = document.querySelector('.search-trigger');
|
|
246
|
+
const input = modal?.querySelector('.search-input');
|
|
247
|
+
const results = modal?.querySelector('.search-results');
|
|
248
|
+
const backdrop = modal?.querySelector('.search-modal-backdrop');
|
|
249
|
+
|
|
250
|
+
if (!modal || !trigger || !input) return;
|
|
251
|
+
|
|
252
|
+
let selectedIndex = -1;
|
|
253
|
+
|
|
254
|
+
// Open search
|
|
255
|
+
trigger.addEventListener('click', openSearch);
|
|
256
|
+
|
|
257
|
+
function openSearch() {
|
|
258
|
+
modal.classList.add('open');
|
|
259
|
+
input.focus();
|
|
260
|
+
document.body.style.overflow = 'hidden';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function closeSearch() {
|
|
264
|
+
modal.classList.remove('open');
|
|
265
|
+
input.value = '';
|
|
266
|
+
results.innerHTML = '<div class="search-empty"><p>Start typing to search...</p></div>';
|
|
267
|
+
document.body.style.overflow = '';
|
|
268
|
+
selectedIndex = -1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Close on backdrop/escape
|
|
272
|
+
backdrop?.addEventListener('click', closeSearch);
|
|
273
|
+
document.addEventListener('keydown', (e) => {
|
|
274
|
+
if (e.key === 'Escape' && modal.classList.contains('open')) {
|
|
275
|
+
closeSearch();
|
|
276
|
+
}
|
|
277
|
+
if (e.key === '/' && !e.ctrlKey && !e.metaKey && !isInputFocused()) {
|
|
278
|
+
e.preventDefault();
|
|
279
|
+
openSearch();
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Search input handling
|
|
284
|
+
let debounceTimer;
|
|
285
|
+
input.addEventListener('input', () => {
|
|
286
|
+
clearTimeout(debounceTimer);
|
|
287
|
+
debounceTimer = setTimeout(() => {
|
|
288
|
+
performSearch(input.value.trim());
|
|
289
|
+
}, 200);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Keyboard navigation in results
|
|
293
|
+
input.addEventListener('keydown', (e) => {
|
|
294
|
+
const items = results.querySelectorAll('.search-result');
|
|
295
|
+
|
|
296
|
+
if (e.key === 'ArrowDown') {
|
|
297
|
+
e.preventDefault();
|
|
298
|
+
selectedIndex = Math.min(selectedIndex + 1, items.length - 1);
|
|
299
|
+
updateSelection(items);
|
|
300
|
+
} else if (e.key === 'ArrowUp') {
|
|
301
|
+
e.preventDefault();
|
|
302
|
+
selectedIndex = Math.max(selectedIndex - 1, 0);
|
|
303
|
+
updateSelection(items);
|
|
304
|
+
} else if (e.key === 'Enter' && selectedIndex >= 0) {
|
|
305
|
+
e.preventDefault();
|
|
306
|
+
items[selectedIndex]?.click();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
function updateSelection(items) {
|
|
311
|
+
items.forEach((item, i) => {
|
|
312
|
+
item.classList.toggle('selected', i === selectedIndex);
|
|
313
|
+
});
|
|
314
|
+
if (items[selectedIndex]) {
|
|
315
|
+
items[selectedIndex].scrollIntoView({ block: 'nearest' });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function performSearch(query) {
|
|
320
|
+
selectedIndex = -1;
|
|
321
|
+
|
|
322
|
+
if (!query || query.length < 2) {
|
|
323
|
+
results.innerHTML = '<div class="search-empty"><p>Start typing to search...</p><div class="search-hints"><p><kbd>Enter</kbd> to select</p><p><kbd>↑</kbd> <kbd>↓</kbd> to navigate</p><p><kbd>ESC</kbd> to close</p></div></div>';
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const queryLower = query.toLowerCase();
|
|
328
|
+
const matches = state.searchIndex.filter(page => {
|
|
329
|
+
return page.title.toLowerCase().includes(queryLower) ||
|
|
330
|
+
page.content.toLowerCase().includes(queryLower) ||
|
|
331
|
+
page.headings.some(h => h.toLowerCase().includes(queryLower));
|
|
332
|
+
}).slice(0, 10);
|
|
333
|
+
|
|
334
|
+
if (matches.length === 0) {
|
|
335
|
+
results.innerHTML = '<div class="search-empty"><p>No results found for "' + escapeHtml(query) + '"</p></div>';
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
results.innerHTML = matches.map(match => {
|
|
340
|
+
const snippet = getSnippet(match.content, query);
|
|
341
|
+
return \`
|
|
342
|
+
<a href="\${config.rootPath}\${match.path}" class="search-result">
|
|
343
|
+
<div class="search-result-title">\${escapeHtml(match.title)}</div>
|
|
344
|
+
<div class="search-result-snippet">\${snippet}</div>
|
|
345
|
+
</a>
|
|
346
|
+
\`;
|
|
347
|
+
}).join('');
|
|
348
|
+
|
|
349
|
+
// Add click handlers to close search
|
|
350
|
+
results.querySelectorAll('.search-result').forEach(result => {
|
|
351
|
+
result.addEventListener('click', closeSearch);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function getSnippet(content, query) {
|
|
356
|
+
const index = content.toLowerCase().indexOf(query.toLowerCase());
|
|
357
|
+
if (index === -1) return '';
|
|
358
|
+
|
|
359
|
+
const start = Math.max(0, index - 50);
|
|
360
|
+
const end = Math.min(content.length, index + query.length + 50);
|
|
361
|
+
let snippet = content.slice(start, end);
|
|
362
|
+
|
|
363
|
+
if (start > 0) snippet = '...' + snippet;
|
|
364
|
+
if (end < content.length) snippet = snippet + '...';
|
|
365
|
+
|
|
366
|
+
// Highlight matches
|
|
367
|
+
const regex = new RegExp('(' + escapeRegex(query) + ')', 'gi');
|
|
368
|
+
return escapeHtml(snippet).replace(regex, '<mark>$1</mark>');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
` : ''}
|
|
372
|
+
|
|
373
|
+
// ========================================
|
|
374
|
+
// Guided Tours
|
|
375
|
+
// ========================================
|
|
376
|
+
${features.guidedTour ? `
|
|
377
|
+
function initTours() {
|
|
378
|
+
const triggerBtn = document.querySelector('.tour-trigger');
|
|
379
|
+
const selectorModal = document.querySelector('.tour-selector-modal');
|
|
380
|
+
const overlay = document.querySelector('.tour-overlay');
|
|
381
|
+
|
|
382
|
+
if (!triggerBtn || !state.manifest?.tours) return;
|
|
383
|
+
|
|
384
|
+
// Build tour list
|
|
385
|
+
const tourList = selectorModal?.querySelector('.tour-list');
|
|
386
|
+
if (tourList && state.manifest.tours.length > 0) {
|
|
387
|
+
tourList.innerHTML = state.manifest.tours.map(tour => \`
|
|
388
|
+
<button class="tour-item" data-tour-id="\${tour.id}">
|
|
389
|
+
<div class="tour-item-name">\${escapeHtml(tour.name)}</div>
|
|
390
|
+
<div class="tour-item-desc">\${escapeHtml(tour.description)}</div>
|
|
391
|
+
</button>
|
|
392
|
+
\`).join('');
|
|
393
|
+
|
|
394
|
+
tourList.querySelectorAll('.tour-item').forEach(btn => {
|
|
395
|
+
btn.addEventListener('click', () => {
|
|
396
|
+
closeTourSelector();
|
|
397
|
+
startTour(btn.dataset.tourId);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Tour trigger button
|
|
403
|
+
triggerBtn.addEventListener('click', () => {
|
|
404
|
+
if (state.manifest.tours.length === 1) {
|
|
405
|
+
startTour(state.manifest.tours[0].id);
|
|
406
|
+
} else {
|
|
407
|
+
openTourSelector();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Tour selector modal
|
|
412
|
+
selectorModal?.querySelector('.tour-selector-backdrop')?.addEventListener('click', closeTourSelector);
|
|
413
|
+
selectorModal?.querySelector('.tour-selector-close')?.addEventListener('click', closeTourSelector);
|
|
414
|
+
|
|
415
|
+
// Tour overlay buttons
|
|
416
|
+
overlay?.querySelector('.tour-btn-skip')?.addEventListener('click', endTour);
|
|
417
|
+
overlay?.querySelector('.tour-btn-prev')?.addEventListener('click', prevTourStep);
|
|
418
|
+
overlay?.querySelector('.tour-btn-next')?.addEventListener('click', nextTourStep);
|
|
419
|
+
|
|
420
|
+
// Check if first visit - show tour offer
|
|
421
|
+
if (!localStorage.getItem('wiki-tour-seen')) {
|
|
422
|
+
setTimeout(() => {
|
|
423
|
+
if (state.manifest.tours.length > 0) {
|
|
424
|
+
openTourSelector();
|
|
425
|
+
localStorage.setItem('wiki-tour-seen', 'true');
|
|
426
|
+
}
|
|
427
|
+
}, 1000);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function openTourSelector() {
|
|
432
|
+
document.querySelector('.tour-selector-modal')?.classList.add('open');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function closeTourSelector() {
|
|
436
|
+
document.querySelector('.tour-selector-modal')?.classList.remove('open');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function startTour(tourId) {
|
|
440
|
+
const tour = state.manifest?.tours?.find(t => t.id === tourId);
|
|
441
|
+
if (!tour || tour.steps.length === 0) return;
|
|
442
|
+
|
|
443
|
+
state.currentTour = tour;
|
|
444
|
+
state.tourStep = 0;
|
|
445
|
+
|
|
446
|
+
document.querySelector('.tour-overlay')?.classList.add('active');
|
|
447
|
+
showTourStep();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function showTourStep() {
|
|
451
|
+
const overlay = document.querySelector('.tour-overlay');
|
|
452
|
+
const spotlight = overlay?.querySelector('.tour-spotlight');
|
|
453
|
+
const tooltip = overlay?.querySelector('.tour-tooltip');
|
|
454
|
+
const tour = state.currentTour;
|
|
455
|
+
|
|
456
|
+
if (!tour || !overlay || !spotlight || !tooltip) return;
|
|
457
|
+
|
|
458
|
+
const step = tour.steps[state.tourStep];
|
|
459
|
+
if (!step) return;
|
|
460
|
+
|
|
461
|
+
// Check if step is on a different page
|
|
462
|
+
if (step.page && step.page !== config.currentPath) {
|
|
463
|
+
// Navigate to the page
|
|
464
|
+
window.location.href = config.rootPath + step.page + '?tour=' + tour.id + '&step=' + state.tourStep;
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Find target element
|
|
469
|
+
const target = document.querySelector(step.targetSelector);
|
|
470
|
+
if (!target) {
|
|
471
|
+
// Skip to next step if target not found
|
|
472
|
+
if (state.tourStep < tour.steps.length - 1) {
|
|
473
|
+
state.tourStep++;
|
|
474
|
+
showTourStep();
|
|
475
|
+
} else {
|
|
476
|
+
endTour();
|
|
477
|
+
}
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Position spotlight
|
|
482
|
+
const rect = target.getBoundingClientRect();
|
|
483
|
+
const padding = 8;
|
|
484
|
+
spotlight.style.top = (rect.top + window.scrollY - padding) + 'px';
|
|
485
|
+
spotlight.style.left = (rect.left - padding) + 'px';
|
|
486
|
+
spotlight.style.width = (rect.width + padding * 2) + 'px';
|
|
487
|
+
spotlight.style.height = (rect.height + padding * 2) + 'px';
|
|
488
|
+
|
|
489
|
+
// Scroll target into view
|
|
490
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
491
|
+
|
|
492
|
+
// Position tooltip
|
|
493
|
+
const pos = step.position || 'bottom';
|
|
494
|
+
tooltip.className = 'tour-tooltip tour-tooltip-' + pos;
|
|
495
|
+
|
|
496
|
+
// Update tooltip content
|
|
497
|
+
tooltip.querySelector('.tour-step-title').textContent = step.title;
|
|
498
|
+
tooltip.querySelector('.tour-step-description').textContent = step.description;
|
|
499
|
+
tooltip.querySelector('.tour-step-counter').textContent =
|
|
500
|
+
(state.tourStep + 1) + ' of ' + tour.steps.length;
|
|
501
|
+
|
|
502
|
+
// Update buttons
|
|
503
|
+
const prevBtn = tooltip.querySelector('.tour-btn-prev');
|
|
504
|
+
const nextBtn = tooltip.querySelector('.tour-btn-next');
|
|
505
|
+
|
|
506
|
+
prevBtn.style.display = state.tourStep === 0 ? 'none' : 'block';
|
|
507
|
+
nextBtn.textContent = state.tourStep === tour.steps.length - 1 ? 'Finish' : 'Next';
|
|
508
|
+
|
|
509
|
+
// Position tooltip based on direction
|
|
510
|
+
setTimeout(() => {
|
|
511
|
+
const tooltipRect = tooltip.getBoundingClientRect();
|
|
512
|
+
let top, left;
|
|
513
|
+
|
|
514
|
+
switch (pos) {
|
|
515
|
+
case 'top':
|
|
516
|
+
top = rect.top + window.scrollY - tooltipRect.height - 16;
|
|
517
|
+
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
518
|
+
break;
|
|
519
|
+
case 'bottom':
|
|
520
|
+
top = rect.bottom + window.scrollY + 16;
|
|
521
|
+
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
522
|
+
break;
|
|
523
|
+
case 'left':
|
|
524
|
+
top = rect.top + window.scrollY + (rect.height - tooltipRect.height) / 2;
|
|
525
|
+
left = rect.left - tooltipRect.width - 16;
|
|
526
|
+
break;
|
|
527
|
+
case 'right':
|
|
528
|
+
top = rect.top + window.scrollY + (rect.height - tooltipRect.height) / 2;
|
|
529
|
+
left = rect.right + 16;
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Keep tooltip in viewport
|
|
534
|
+
left = Math.max(16, Math.min(left, window.innerWidth - tooltipRect.width - 16));
|
|
535
|
+
top = Math.max(16, top);
|
|
536
|
+
|
|
537
|
+
tooltip.style.top = top + 'px';
|
|
538
|
+
tooltip.style.left = left + 'px';
|
|
539
|
+
}, 50);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function nextTourStep() {
|
|
543
|
+
if (state.tourStep < state.currentTour.steps.length - 1) {
|
|
544
|
+
state.tourStep++;
|
|
545
|
+
showTourStep();
|
|
546
|
+
} else {
|
|
547
|
+
endTour();
|
|
548
|
+
showToast('Tour complete! Explore freely.', 'success');
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function prevTourStep() {
|
|
553
|
+
if (state.tourStep > 0) {
|
|
554
|
+
state.tourStep--;
|
|
555
|
+
showTourStep();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function endTour() {
|
|
560
|
+
state.currentTour = null;
|
|
561
|
+
state.tourStep = 0;
|
|
562
|
+
document.querySelector('.tour-overlay')?.classList.remove('active');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Resume tour from URL parameters
|
|
566
|
+
(function checkTourResume() {
|
|
567
|
+
const params = new URLSearchParams(window.location.search);
|
|
568
|
+
const tourId = params.get('tour');
|
|
569
|
+
const step = parseInt(params.get('step'));
|
|
570
|
+
|
|
571
|
+
if (tourId && !isNaN(step)) {
|
|
572
|
+
// Clean URL
|
|
573
|
+
history.replaceState({}, '', window.location.pathname);
|
|
574
|
+
|
|
575
|
+
// Wait for manifest then resume
|
|
576
|
+
const checkManifest = setInterval(() => {
|
|
577
|
+
if (state.manifest?.tours) {
|
|
578
|
+
clearInterval(checkManifest);
|
|
579
|
+
const tour = state.manifest.tours.find(t => t.id === tourId);
|
|
580
|
+
if (tour) {
|
|
581
|
+
state.currentTour = tour;
|
|
582
|
+
state.tourStep = step;
|
|
583
|
+
document.querySelector('.tour-overlay')?.classList.add('active');
|
|
584
|
+
showTourStep();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}, 100);
|
|
588
|
+
}
|
|
589
|
+
})();
|
|
590
|
+
` : ''}
|
|
591
|
+
|
|
592
|
+
// ========================================
|
|
593
|
+
// Code Explorer
|
|
594
|
+
// ========================================
|
|
595
|
+
${features.codeExplorer ? `
|
|
596
|
+
function initCodeExplorer() {
|
|
597
|
+
const modal = document.querySelector('.code-explorer-modal');
|
|
598
|
+
if (!modal) return;
|
|
599
|
+
|
|
600
|
+
modal.querySelector('.code-explorer-backdrop')?.addEventListener('click', closeCodeExplorer);
|
|
601
|
+
modal.querySelector('.code-explorer-close')?.addEventListener('click', closeCodeExplorer);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function openCodeExplorer(sourceRef) {
|
|
605
|
+
const modal = document.querySelector('.code-explorer-modal');
|
|
606
|
+
if (!modal) return;
|
|
607
|
+
|
|
608
|
+
// Parse source reference (e.g., "src/auth.ts:23-45")
|
|
609
|
+
const match = sourceRef.match(/^(.+?)(?::(\\d+)(?:-(\\d+))?)?$/);
|
|
610
|
+
if (!match) return;
|
|
611
|
+
|
|
612
|
+
const [, filePath, startLine, endLine] = match;
|
|
613
|
+
|
|
614
|
+
modal.querySelector('.code-explorer-file').textContent = filePath;
|
|
615
|
+
modal.querySelector('.code-explorer-info').textContent =
|
|
616
|
+
startLine ? 'Lines ' + startLine + (endLine ? '-' + endLine : '') : 'Full file';
|
|
617
|
+
|
|
618
|
+
// Show loading state
|
|
619
|
+
const codeEl = modal.querySelector('.code-explorer-code');
|
|
620
|
+
codeEl.textContent = 'Loading...';
|
|
621
|
+
modal.classList.add('open');
|
|
622
|
+
document.body.style.overflow = 'hidden';
|
|
623
|
+
|
|
624
|
+
// In a real implementation, this would fetch from the repo
|
|
625
|
+
// For static sites, we show a placeholder with navigation hint
|
|
626
|
+
setTimeout(() => {
|
|
627
|
+
codeEl.innerHTML = \`<span class="comment">// Source: \${escapeHtml(sourceRef)}</span>
|
|
628
|
+
<span class="comment">// This code viewer shows source references from the documentation.</span>
|
|
629
|
+
<span class="comment">// In the full version, code is fetched from your repository.</span>
|
|
630
|
+
|
|
631
|
+
<span class="comment">// Navigate to:</span>
|
|
632
|
+
<span class="string">"\${escapeHtml(filePath)}"</span>
|
|
633
|
+
\${startLine ? '<span class="comment">// Lines: ' + startLine + (endLine ? '-' + endLine : '') + '</span>' : ''}
|
|
634
|
+
|
|
635
|
+
<span class="comment">// Tip: Use the source links in code blocks to navigate</span>
|
|
636
|
+
<span class="comment">// directly to the relevant code in your editor or IDE.</span>\`;
|
|
637
|
+
|
|
638
|
+
if (window.Prism) {
|
|
639
|
+
Prism.highlightElement(codeEl);
|
|
640
|
+
}
|
|
641
|
+
}, 300);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function closeCodeExplorer() {
|
|
645
|
+
document.querySelector('.code-explorer-modal')?.classList.remove('open');
|
|
646
|
+
document.body.style.overflow = '';
|
|
647
|
+
}
|
|
648
|
+
` : ''}
|
|
649
|
+
|
|
650
|
+
// ========================================
|
|
651
|
+
// Keyboard Navigation
|
|
652
|
+
// ========================================
|
|
653
|
+
${features.keyboardNav ? `
|
|
654
|
+
function initKeyboardNav() {
|
|
655
|
+
const helpModal = document.querySelector('.keyboard-help-modal');
|
|
656
|
+
|
|
657
|
+
helpModal?.querySelector('.keyboard-help-backdrop')?.addEventListener('click', closeKeyboardHelp);
|
|
658
|
+
helpModal?.querySelector('.keyboard-help-close')?.addEventListener('click', closeKeyboardHelp);
|
|
659
|
+
|
|
660
|
+
// Track g key for gg command
|
|
661
|
+
let lastKey = '';
|
|
662
|
+
let lastKeyTime = 0;
|
|
663
|
+
|
|
664
|
+
document.addEventListener('keydown', (e) => {
|
|
665
|
+
// Ignore when typing in inputs
|
|
666
|
+
if (isInputFocused()) return;
|
|
667
|
+
|
|
668
|
+
// Ignore with modifiers (except shift for capital letters)
|
|
669
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
670
|
+
|
|
671
|
+
const key = e.key;
|
|
672
|
+
const now = Date.now();
|
|
673
|
+
|
|
674
|
+
switch (key) {
|
|
675
|
+
case 'j':
|
|
676
|
+
e.preventDefault();
|
|
677
|
+
navigateHeading(1);
|
|
678
|
+
break;
|
|
679
|
+
|
|
680
|
+
case 'k':
|
|
681
|
+
e.preventDefault();
|
|
682
|
+
navigateHeading(-1);
|
|
683
|
+
break;
|
|
684
|
+
|
|
685
|
+
case 'h':
|
|
686
|
+
e.preventDefault();
|
|
687
|
+
navigatePage(-1);
|
|
688
|
+
break;
|
|
689
|
+
|
|
690
|
+
case 'l':
|
|
691
|
+
e.preventDefault();
|
|
692
|
+
navigatePage(1);
|
|
693
|
+
break;
|
|
694
|
+
|
|
695
|
+
case 'g':
|
|
696
|
+
if (lastKey === 'g' && now - lastKeyTime < 500) {
|
|
697
|
+
e.preventDefault();
|
|
698
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
699
|
+
}
|
|
700
|
+
break;
|
|
701
|
+
|
|
702
|
+
case 'G':
|
|
703
|
+
e.preventDefault();
|
|
704
|
+
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
|
705
|
+
break;
|
|
706
|
+
|
|
707
|
+
case 't':
|
|
708
|
+
document.querySelector('.theme-toggle')?.click();
|
|
709
|
+
break;
|
|
710
|
+
|
|
711
|
+
case '?':
|
|
712
|
+
e.preventDefault();
|
|
713
|
+
openKeyboardHelp();
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
lastKey = key;
|
|
718
|
+
lastKeyTime = now;
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function navigateHeading(direction) {
|
|
723
|
+
const headings = Array.from(document.querySelectorAll('.heading-anchor'));
|
|
724
|
+
if (headings.length === 0) return;
|
|
725
|
+
|
|
726
|
+
const scrollTop = window.scrollY + 100;
|
|
727
|
+
let targetIndex = -1;
|
|
728
|
+
|
|
729
|
+
if (direction > 0) {
|
|
730
|
+
// Find next heading below current scroll
|
|
731
|
+
targetIndex = headings.findIndex(h => h.offsetTop > scrollTop);
|
|
732
|
+
} else {
|
|
733
|
+
// Find previous heading above current scroll
|
|
734
|
+
for (let i = headings.length - 1; i >= 0; i--) {
|
|
735
|
+
if (headings[i].offsetTop < scrollTop - 10) {
|
|
736
|
+
targetIndex = i;
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (targetIndex >= 0 && targetIndex < headings.length) {
|
|
743
|
+
headings[targetIndex].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function navigatePage(direction) {
|
|
748
|
+
const navLinks = Array.from(document.querySelectorAll('.nav-link'));
|
|
749
|
+
const currentIndex = navLinks.findIndex(link =>
|
|
750
|
+
link.getAttribute('href').endsWith(config.currentPath)
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
const targetIndex = currentIndex + direction;
|
|
754
|
+
if (targetIndex >= 0 && targetIndex < navLinks.length) {
|
|
755
|
+
window.location.href = navLinks[targetIndex].href;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function openKeyboardHelp() {
|
|
760
|
+
document.querySelector('.keyboard-help-modal')?.classList.add('open');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function closeKeyboardHelp() {
|
|
764
|
+
document.querySelector('.keyboard-help-modal')?.classList.remove('open');
|
|
765
|
+
}
|
|
766
|
+
` : ''}
|
|
767
|
+
|
|
768
|
+
// ========================================
|
|
769
|
+
// Progress Tracking
|
|
770
|
+
// ========================================
|
|
771
|
+
${features.progressTracking ? `
|
|
772
|
+
function initProgressTracking() {
|
|
773
|
+
// Load read pages from storage
|
|
774
|
+
const saved = localStorage.getItem('wiki-read-pages');
|
|
775
|
+
if (saved) {
|
|
776
|
+
try {
|
|
777
|
+
state.readPages = new Set(JSON.parse(saved));
|
|
778
|
+
} catch (e) {}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Mark current page as read
|
|
782
|
+
state.readPages.add(config.currentPath);
|
|
783
|
+
saveReadPages();
|
|
784
|
+
|
|
785
|
+
// Update UI
|
|
786
|
+
updateProgressUI();
|
|
787
|
+
|
|
788
|
+
// Mark as read in sidebar
|
|
789
|
+
document.querySelectorAll('.nav-link').forEach(link => {
|
|
790
|
+
const href = link.getAttribute('href');
|
|
791
|
+
const path = href.replace(config.rootPath, '').replace(/^\\.?\\//, '');
|
|
792
|
+
if (state.readPages.has(path)) {
|
|
793
|
+
link.closest('.nav-item')?.classList.add('read');
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function saveReadPages() {
|
|
799
|
+
localStorage.setItem('wiki-read-pages', JSON.stringify([...state.readPages]));
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function updateProgressUI() {
|
|
803
|
+
const totalPages = state.manifest?.pages?.length || 1;
|
|
804
|
+
const readCount = state.readPages.size;
|
|
805
|
+
const percentage = Math.round((readCount / totalPages) * 100);
|
|
806
|
+
|
|
807
|
+
const fill = document.querySelector('.progress-fill');
|
|
808
|
+
const text = document.querySelector('.progress-text');
|
|
809
|
+
|
|
810
|
+
if (fill) fill.style.width = percentage + '%';
|
|
811
|
+
if (text) text.textContent = percentage + '% complete (' + readCount + '/' + totalPages + ')';
|
|
812
|
+
}
|
|
813
|
+
` : ''}
|
|
814
|
+
|
|
815
|
+
// ========================================
|
|
816
|
+
// Utility Functions
|
|
817
|
+
// ========================================
|
|
818
|
+
function isInputFocused() {
|
|
819
|
+
const active = document.activeElement;
|
|
820
|
+
return active && (
|
|
821
|
+
active.tagName === 'INPUT' ||
|
|
822
|
+
active.tagName === 'TEXTAREA' ||
|
|
823
|
+
active.contentEditable === 'true'
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function escapeHtml(text) {
|
|
828
|
+
const div = document.createElement('div');
|
|
829
|
+
div.textContent = text;
|
|
830
|
+
return div.innerHTML;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function escapeRegex(string) {
|
|
834
|
+
return string.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&');
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function showToast(message, type = 'info') {
|
|
838
|
+
const container = document.querySelector('.toast-container');
|
|
839
|
+
if (!container) return;
|
|
840
|
+
|
|
841
|
+
const toast = document.createElement('div');
|
|
842
|
+
toast.className = 'toast toast-' + type;
|
|
843
|
+
toast.textContent = message;
|
|
844
|
+
|
|
845
|
+
container.appendChild(toast);
|
|
846
|
+
|
|
847
|
+
setTimeout(() => {
|
|
848
|
+
toast.classList.add('toast-out');
|
|
849
|
+
setTimeout(() => toast.remove(), 300);
|
|
850
|
+
}, 3000);
|
|
851
|
+
}
|
|
852
|
+
})();
|
|
853
|
+
`;
|
|
854
|
+
}
|
|
855
|
+
//# sourceMappingURL=scripts.js.map
|