-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TypeScript Breathing Part2 π‘οΈπ‘οΈ (#329)
* δΌγε * ιΈγε * ζΌγε * ζγε * The Apprentice π * Associate titles directly with their keys to avoid indexing issues Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Fix the selector function to use the correct title mapping * Replace type assertion with type guard for better type safety. * Add proper cleanup and error handling in tocReset. Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Improve error handling and type safety in DOM manipulation. Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Add cleanup mechanism and debounce observer callbacks. * Add error handling for waitForStyle. Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Add error handling to the toc. * from "Nitpick comments" on `replace-dom.ts` * from "Nitpick comments" on `replace-dom.ts` part2 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
- Loading branch information
1 parent
e35a0ce
commit e3fd5a1
Showing
9 changed files
with
400 additions
and
243 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { Fzf, byStartAsc } from 'fzf'; | ||
import type { FzfResultItem, FzfOptions } from 'fzf'; | ||
|
||
const LOWER_LIMIT_SCORE = 56; | ||
|
||
interface StoreDoc { | ||
id: string; | ||
title: string; | ||
body: string; | ||
key: string; | ||
} | ||
|
||
interface SearchResult { | ||
doc: Omit<StoreDoc, 'id' | 'title'>; | ||
key: string; | ||
score: number; | ||
} | ||
|
||
export default class Finder { | ||
private fzf: Fzf<string[]>; | ||
|
||
private docs: Record<string, Omit<StoreDoc, 'id' | 'title'>> = {}; | ||
private titlesByKey: Record<string, string> = {}; | ||
|
||
constructor(storeDocs: Record<string, StoreDoc>, limit: number) { | ||
for (const [key, { id, title, ...rest }] of Object.entries(storeDocs)) { | ||
this.titlesByKey[key] = title; | ||
this.docs[key] = rest; | ||
} | ||
|
||
const options: FzfOptions<string> = { | ||
limit, | ||
selector: x => `${this.titlesByKey[x]} ${this.docs[x].body}`, | ||
tiebreakers: [byStartAsc], | ||
}; | ||
|
||
this.fzf = new Fzf(Object.keys(this.docs), options); | ||
} | ||
|
||
private filterSatisfactory(array: FzfResultItem<string>[]): FzfResultItem<string>[] { | ||
if (array.length === 0 || array[0].score < LOWER_LIMIT_SCORE) { | ||
return array.filter(x => x.score >= 0); | ||
} | ||
|
||
let low = 1; // '0' is already checked, so start with '1' | ||
let high = array.length - 1; | ||
|
||
let idx = -1; | ||
|
||
while (low <= high) { | ||
const mid = Math.floor((low + high) / 2); | ||
|
||
if (array[mid].score < LOWER_LIMIT_SCORE) { | ||
idx = mid; | ||
high = mid - 1; | ||
} else { | ||
low = mid + 1; | ||
} | ||
} | ||
|
||
return idx >= 0 ? array.slice(0, idx) : array; | ||
} | ||
|
||
public search(term: string): SearchResult[] { | ||
const results = this.filterSatisfactory(this.fzf.find(term)); | ||
return results.map(x => ({ | ||
doc: this.docs[x.item], | ||
key: x.item, | ||
score: x.score, | ||
})); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { getRootVariable } from './css-loader'; | ||
|
||
const INTERVAL_MS = 50; | ||
const TIMEOUT_MS = 1000; | ||
|
||
const BATCH_SIZE = 2; | ||
|
||
interface BaseObject { | ||
id: string; | ||
} | ||
|
||
interface ImageSrc { | ||
light: string; | ||
dark: string; | ||
} | ||
|
||
interface ImageObject extends BaseObject { | ||
src: ImageSrc; | ||
alt: string; | ||
} | ||
|
||
const waitForStyle = (property: string): Promise<string> => { | ||
const start = Date.now(); | ||
|
||
return new Promise((resolve, reject) => { | ||
const checkStyle = (): void => { | ||
const value = getRootVariable(property); | ||
|
||
if (value !== '') { | ||
resolve(value); | ||
return; | ||
} | ||
if (Date.now() - start >= TIMEOUT_MS) { | ||
reject(new Error(`Timeout waiting for CSS variable "${property}" after ${TIMEOUT_MS}ms`)); | ||
return; | ||
} | ||
setTimeout(checkStyle, INTERVAL_MS); | ||
}; | ||
|
||
checkStyle(); | ||
}); | ||
}; | ||
|
||
const debounce = <T extends BaseObject>(fn: (...args: T[]) => void, delay: number): ((...args: T[]) => void) => { | ||
let timeoutId: ReturnType<typeof setTimeout>; | ||
|
||
return (...args: T[]) => { | ||
clearTimeout(timeoutId); | ||
timeoutId = setTimeout(() => fn(...args), delay); | ||
}; | ||
}; | ||
|
||
const processBatch = (batch: ImageObject[], scheme: string) => { | ||
for (const x of batch) { | ||
const elm = document.getElementById(x.id); | ||
|
||
if (elm === null) { | ||
console.warn(`id: ${x.id} element does not exist`); | ||
continue; | ||
} | ||
const img = document.createElement('img'); | ||
|
||
img.setAttribute('id', x.id); | ||
img.setAttribute('src', scheme === 'light' ? x.src.light : x.src.dark); | ||
img.setAttribute('alt', x.alt); | ||
img.setAttribute('loading', 'lazy'); | ||
|
||
img.onerror = () => { | ||
console.error(`Failed to load image for id: ${x.id}`); | ||
}; | ||
|
||
elm.replaceWith(img); | ||
} | ||
}; | ||
|
||
const replaceProc = async (imageObjectArray: ImageObject[]): Promise<void> => { | ||
let scheme: string; | ||
|
||
try { | ||
scheme = await waitForStyle('--color-scheme'); | ||
} catch (error) { | ||
console.error('Failed to get color scheme:', error); | ||
return; | ||
} | ||
|
||
for (let i = 0; i < imageObjectArray.length; i += BATCH_SIZE) { | ||
requestAnimationFrame(() => processBatch(imageObjectArray.slice(i, i + BATCH_SIZE), scheme)); | ||
} | ||
}; | ||
|
||
/** | ||
* Replaces images based on color scheme and observes class changes. | ||
* @param imageObjectArray - Array of image objects to process | ||
* @returns A cleanup function that disconnects the observer when called | ||
*/ | ||
export const replaceId = (imageObjectArray: ImageObject[]): (() => void) => { | ||
replaceProc(imageObjectArray); | ||
|
||
const debouncedReplace = debounce(() => replaceProc(imageObjectArray), 150); | ||
|
||
const observer = new MutationObserver(mutations => { | ||
for (const x of mutations) { | ||
if (x.attributeName === 'class') { | ||
debouncedReplace(); | ||
} | ||
} | ||
}); | ||
|
||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); | ||
|
||
// Cleanup function that can be called when needed | ||
return () => { | ||
observer.disconnect(); | ||
}; | ||
}; |
Oops, something went wrong.