Skip to content

Commit 70ceb5f

Browse files
committed
Refactor: extract shared utilities and remove dead code
1 parent 3e10173 commit 70ceb5f

8 files changed

Lines changed: 142 additions & 158 deletions

File tree

src/lib/components/common/MarkdownRenderer.svelte

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { marked } from 'marked';
1010
import { loadKatex, getKatexCssUrl } from '$lib/utils/katexLoader';
1111
import { loadCodeMirrorModules, createEditorExtensions, type CodeMirrorModules } from '$lib/utils/codemirror';
12+
import { detectLanguage } from '$lib/utils/codeUtils';
1213
import { theme } from '$lib/stores/themeStore';
1314
import { processCrossRefs, crossrefIndexStore } from '$lib/utils/crossref';
1415
import { searchTarget } from '$lib/stores/searchNavigation';
@@ -159,16 +160,6 @@
159160
container.innerHTML = innerHTML;
160161
}
161162
162-
/**
163-
* Detect language from code content
164-
*/
165-
function detectLanguage(code: string): 'python' | 'console' {
166-
if (code.includes('>>>') || code.includes('...')) {
167-
return 'console';
168-
}
169-
return 'python';
170-
}
171-
172163
/**
173164
* Render code blocks with CodeMirror
174165
*/

src/lib/components/common/RstRenderer.svelte

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { goto } from '$app/navigation';
1010
import { loadKatex, getKatexCssUrl } from '$lib/utils/katexLoader';
1111
import { loadCodeMirrorModules, createEditorExtensions, type CodeMirrorModules } from '$lib/utils/codemirror';
12+
import { detectLanguage } from '$lib/utils/codeUtils';
1213
import { theme } from '$lib/stores/themeStore';
1314
import { processCrossRefs, crossrefIndexStore } from '$lib/utils/crossref';
1415
import { searchTarget } from '$lib/stores/searchNavigation';
@@ -68,8 +69,7 @@
6869
| CodeBlockDirective
6970
| MathDirective
7071
| SectionHeader
71-
| { type: 'paragraph'; content: string }
72-
| { type: 'hidden' };
72+
| { type: 'paragraph'; content: string };
7373
7474
/**
7575
* Parse RST content into blocks
@@ -345,16 +345,6 @@
345345
}
346346
}
347347
348-
/**
349-
* Detect language from code content
350-
*/
351-
function detectLanguage(code: string): 'python' | 'console' {
352-
if (code.includes('>>>') || code.includes('...')) {
353-
return 'console';
354-
}
355-
return 'python';
356-
}
357-
358348
/**
359349
* Render code blocks with CodeMirror
360350
*/
@@ -497,11 +487,12 @@
497487
{:else if block.type === 'paragraph'}
498488
<p>{@html block.content}</p>
499489
{:else if block.type === 'image'}
500-
<div class="image-block" style:text-align={block.align || 'center'}>
490+
<div class="image-block align-{block.align || 'center'}">
501491
<img
502492
src={resolveImagePath(block.src)}
503493
alt={block.alt || 'Image'}
504-
style:max-width={block.width ? `min(${block.width}px, 100%)` : '100%'}
494+
class={block.width ? 'has-width' : ''}
495+
style:--img-width="{block.width}px"
505496
/>
506497
</div>
507498
{:else if block.type === 'admonition'}
@@ -555,12 +546,20 @@
555546
margin: var(--space-md) 0;
556547
}
557548
549+
.image-block.align-left { text-align: left; }
550+
.image-block.align-center { text-align: center; }
551+
.image-block.align-right { text-align: right; }
552+
558553
.image-block img {
559554
max-width: 100%;
560555
height: auto;
561556
border-radius: var(--radius-md);
562557
}
563558
559+
.image-block img.has-width {
560+
max-width: min(var(--img-width, 100%), 100%);
561+
}
562+
564563
/* Admonitions - styled like DocstringRenderer */
565564
.admonition {
566565
margin: var(--space-md) 0;

src/lib/components/layout/Sidebar.svelte

Lines changed: 14 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
import { apiModulesStore } from '$lib/stores/apiContext';
1010
import { ApiToc } from '$lib/components/api';
1111
import ExamplesToc from '$lib/components/examples/ExamplesToc.svelte';
12-
import { search, hybridSearch, type SearchResult } from '$lib/utils/search';
12+
import type { SearchResult } from '$lib/utils/search';
13+
import { createDebouncedSearch } from '$lib/stores/searchHook';
1314
import { searchTarget } from '$lib/stores/searchNavigation';
1415
import { exampleGroupsStore } from '$lib/stores/examplesContext';
1516
import type { PackageManifest } from '$lib/api/versions';
@@ -35,56 +36,9 @@
3536
(item) => item.title !== 'Examples' || hasExamples
3637
)
3738
);
38-
let searchQuery = $state('');
39-
let debouncedQuery = $state('');
40-
let searchResults = $state<SearchResult[]>([]);
41-
let isSearching = $state(false);
42-
let showResults = $derived(searchQuery.length > 0);
4339
44-
// Debounce search query (150ms delay)
45-
$effect(() => {
46-
const query = searchQuery;
47-
const timeout = setTimeout(() => {
48-
debouncedQuery = query;
49-
}, 150);
50-
return () => clearTimeout(timeout);
51-
});
52-
53-
// Run hybrid search when debounced query changes
54-
$effect(() => {
55-
const query = debouncedQuery;
56-
if (!query) {
57-
searchResults = [];
58-
isSearching = false;
59-
return;
60-
}
61-
62-
// Track if this search is still relevant
63-
let cancelled = false;
64-
65-
// Run hybrid search with semantic fallback
66-
hybridSearch(query, 15, () => {
67-
// Callback when semantic search starts
68-
if (!cancelled) {
69-
isSearching = true;
70-
}
71-
}).then((result) => {
72-
if (!cancelled) {
73-
searchResults = result.results;
74-
isSearching = false;
75-
}
76-
}).catch(() => {
77-
if (!cancelled) {
78-
// Fallback to keyword search on error
79-
searchResults = search(query, 15);
80-
isSearching = false;
81-
}
82-
});
83-
84-
return () => {
85-
cancelled = true;
86-
};
87-
});
40+
// Use shared search hook
41+
const searchState = createDebouncedSearch({ limit: 15 });
8842
let searchInput = $state<HTMLInputElement | null>(null);
8943
9044
function handleGlobalKeydown(event: KeyboardEvent) {
@@ -115,8 +69,8 @@
11569
11670
function handleSearchKeydown(event: KeyboardEvent) {
11771
if (event.key === 'Escape') {
118-
if (searchQuery) {
119-
searchQuery = '';
72+
if (searchState.query) {
73+
searchState.clear();
12074
} else {
12175
searchInput?.blur();
12276
}
@@ -125,7 +79,7 @@
12579
}
12680
12781
function handleResultClick(result: SearchResult) {
128-
searchQuery = '';
82+
searchState.clear();
12983
// Set the target element to expand/scroll to
13084
searchTarget.set({
13185
name: result.name,
@@ -141,17 +95,17 @@
14195
<div class="sidebar-fixed">
14296
<div class="search-container">
14397
<SearchInput
144-
bind:value={searchQuery}
98+
bind:value={searchState.query}
14599
bind:inputRef={searchInput}
146-
{isSearching}
100+
isSearching={searchState.isSearching}
147101
onkeydown={handleSearchKeydown}
148102
/>
149103
</div>
150104

151-
{#if showResults}
105+
{#if searchState.showResults}
152106
<nav class="sidebar-nav search-results">
153-
{#if searchResults.length > 0}
154-
{#each searchResults as result}
107+
{#if searchState.results.length > 0}
108+
{#each searchState.results as result}
155109
<SearchResultComponent {result} variant="list" onclick={() => handleResultClick(result)} />
156110
{/each}
157111
{:else}
@@ -178,11 +132,11 @@
178132

179133
</div>
180134

181-
{#if !showResults && isApiPage && $apiModulesStore.length > 0}
135+
{#if !searchState.showResults && isApiPage && $apiModulesStore.length > 0}
182136
<div class="sidebar-scrollable with-separator">
183137
<ApiToc modules={$apiModulesStore} />
184138
</div>
185-
{:else if !showResults && isExamplesPage && $exampleGroupsStore.length > 0}
139+
{:else if !searchState.showResults && isExamplesPage && $exampleGroupsStore.length > 0}
186140
<div class="sidebar-scrollable with-separator">
187141
<ExamplesToc groups={$exampleGroupsStore} {packageId} currentTag={currentTag} />
188142
</div>

src/lib/config/timing.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export const TOOLTIP_SHOW_DELAY = 300;
99
/** Delay before hiding tooltip (ms) */
1010
export const TOOLTIP_HIDE_DELAY = 100;
1111

12+
/** Debounce delay for search input (ms) */
13+
export const SEARCH_DEBOUNCE_MS = 150;
14+
1215
/** Animation durations matching CSS variables */
1316
export const TRANSITION = {
1417
fast: 100,

src/lib/stores/searchHook.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Shared debounced search hook for PathSim Documentation
3+
* Consolidates duplicate search logic from Sidebar and home page
4+
*/
5+
6+
import { search, hybridSearch, type SearchResult } from '$lib/utils/search';
7+
import { SEARCH_DEBOUNCE_MS } from '$lib/config/timing';
8+
9+
export interface SearchOptions {
10+
/** Maximum number of results to return */
11+
limit?: number;
12+
/** Debounce delay in milliseconds */
13+
delay?: number;
14+
}
15+
16+
export interface SearchState {
17+
query: string;
18+
results: SearchResult[];
19+
isSearching: boolean;
20+
}
21+
22+
/**
23+
* Creates a debounced search state with hybrid search
24+
* Returns reactive state that can be used in Svelte components
25+
*/
26+
export function createDebouncedSearch(options: SearchOptions = {}) {
27+
const { limit = 15, delay = SEARCH_DEBOUNCE_MS } = options;
28+
29+
let searchQuery = $state('');
30+
let debouncedQuery = $state('');
31+
let searchResults = $state<SearchResult[]>([]);
32+
let isSearching = $state(false);
33+
let showResults = $derived(searchQuery.length > 0);
34+
35+
// Debounce search query
36+
$effect(() => {
37+
const query = searchQuery;
38+
const timeout = setTimeout(() => {
39+
debouncedQuery = query;
40+
}, delay);
41+
return () => clearTimeout(timeout);
42+
});
43+
44+
// Run hybrid search when debounced query changes
45+
$effect(() => {
46+
const query = debouncedQuery;
47+
if (!query) {
48+
searchResults = [];
49+
isSearching = false;
50+
return;
51+
}
52+
53+
let cancelled = false;
54+
55+
hybridSearch(query, limit, () => {
56+
if (!cancelled) isSearching = true;
57+
}).then((result) => {
58+
if (!cancelled) {
59+
searchResults = result.results;
60+
isSearching = false;
61+
}
62+
}).catch(() => {
63+
if (!cancelled) {
64+
searchResults = search(query, limit);
65+
isSearching = false;
66+
}
67+
});
68+
69+
return () => {
70+
cancelled = true;
71+
};
72+
});
73+
74+
return {
75+
get query() { return searchQuery; },
76+
set query(value: string) { searchQuery = value; },
77+
get results() { return searchResults; },
78+
get isSearching() { return isSearching; },
79+
get showResults() { return showResults; },
80+
clear() { searchQuery = ''; }
81+
};
82+
}

src/lib/utils/codeUtils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Shared code utilities for PathSim Documentation
3+
*/
4+
5+
/**
6+
* Detect language from code content
7+
* Returns 'console' for code with Python REPL markers, 'python' otherwise
8+
*/
9+
export function detectLanguage(code: string): 'python' | 'console' {
10+
if (code.includes('>>>') || code.includes('...')) {
11+
return 'console';
12+
}
13+
return 'python';
14+
}

src/lib/utils/colors.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33
* Matches PathView design system
44
*/
55

6-
// PathSim brand color
7-
export const PATHSIM_BLUE = '#0070C0';
8-
9-
// Default accent color
10-
export const DEFAULT_ACCENT = PATHSIM_BLUE;
11-
126
// Syntax highlighting colors - aligned with design system
137
export const SYNTAX_COLORS = {
148
keyword: '#E57373', // Red - control flow, imports
@@ -22,17 +16,3 @@ export const SYNTAX_COLORS = {
2216
variable: { dark: '#e0e0e0', light: '#383a42' },
2317
punctuation: { dark: '#abb2bf', light: '#505050' }
2418
} as const;
25-
26-
// Status colors
27-
export const STATUS_COLORS = {
28-
success: '#22c55e',
29-
warning: '#eab308',
30-
error: '#ef4444'
31-
} as const;
32-
33-
// Package colors for visual distinction
34-
export const PACKAGE_COLORS = {
35-
pathsim: PATHSIM_BLUE,
36-
chem: '#81C784', // Green
37-
vehicle: '#FFB74D' // Orange
38-
} as const;

0 commit comments

Comments
 (0)