Skip to content

Commit

Permalink
TypeScript Breathing Part2 πŸ—‘οΈπŸ—‘οΈ (#329)
Browse files Browse the repository at this point in the history
* δΌγƒŽεž‹

* ι™ΈγƒŽεž‹

* ζΌ†γƒŽεž‹

* ζŒγƒŽεž‹

* 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
CoralPink and coderabbitai[bot] authored Jan 23, 2025
1 parent e35a0ce commit e3fd5a1
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 243 deletions.
2 changes: 1 addition & 1 deletion js/book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { initThemeColor } from './theme-selector';
import initWasm, { attribute_external_links } from './wasm_book';

interface DataSet extends DOMStringMap {
pathtoroot?: string;
pathtoroot: string;
}

const initialize = (): void => {
Expand Down
2 changes: 1 addition & 1 deletion js/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Buffer } from 'bun:buffer';

import path from 'bun:path';

const ENTRY_POINTS = ['book.ts', 'hl-worker.ts', 'replace-dom.js', 'serviceworker.js'];
const ENTRY_POINTS = ['book.ts', 'hl-worker.ts', 'replace-dom.ts', 'serviceworker.js'];
const OUT_DIR = './dist';

const CLR_RESET = '\x1b[0m';
Expand Down
61 changes: 0 additions & 61 deletions js/finder.js

This file was deleted.

72 changes: 72 additions & 0 deletions js/finder.ts
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,
}));
}
}
34 changes: 0 additions & 34 deletions js/replace-dom.js

This file was deleted.

115 changes: 115 additions & 0 deletions js/replace-dom.ts
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();
};
};
Loading

0 comments on commit e3fd5a1

Please sign in to comment.