mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
- npm-run-all → npm-run-all2 (dev script uses run-p) - lint-staged → nano-staged (config block + husky hook) - lodash.debounce → local src/utils/debounce.ts with cancel/flush/leading/trailing - dotenv → process.loadEnvFile in integration test bootstrap - eslint-plugin-react → @eslint-react/eslint-plugin (flat-config migration) - js-yaml → yaml (mock + jest CJS moduleNameMapper for jsdom) - crypto-js → pure-JS md5/sha256 in src/utils/hash.ts producing byte-identical digests so all on-disk cache keys (PDF, file, project, search index) remain valid across the upgrade — no migration or re-parse cost Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
89 lines
2.3 KiB
TypeScript
89 lines
2.3 KiB
TypeScript
/**
|
|
* Returned by {@link debounce}. Calling it schedules the underlying function,
|
|
* with `cancel` and `flush` controls modeled after `lodash.debounce`.
|
|
*/
|
|
export interface DebouncedFunction<T extends (...args: any[]) => any> {
|
|
(...args: Parameters<T>): ReturnType<T> | undefined;
|
|
cancel(): void;
|
|
flush(): ReturnType<T> | undefined;
|
|
}
|
|
|
|
export interface DebounceOptions {
|
|
leading?: boolean;
|
|
trailing?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Delay-invoking `func` until `wait` ms have elapsed since the last call.
|
|
* Supports the `leading` / `trailing` options and `cancel` / `flush` methods
|
|
* used by the `lodash.debounce` API the codebase relied on previously.
|
|
*/
|
|
export function debounce<T extends (...args: any[]) => any>(
|
|
func: T,
|
|
wait: number,
|
|
options: DebounceOptions = {}
|
|
): DebouncedFunction<T> {
|
|
const leading = options.leading ?? false;
|
|
const trailing = options.trailing ?? true;
|
|
|
|
let timeoutId: number | undefined;
|
|
let lastArgs: Parameters<T> | undefined;
|
|
let lastResult: ReturnType<T> | undefined;
|
|
let hasPendingTrailingCall = false;
|
|
|
|
const invoke = (): ReturnType<T> | undefined => {
|
|
const args = lastArgs;
|
|
lastArgs = undefined;
|
|
hasPendingTrailingCall = false;
|
|
if (!args) return lastResult;
|
|
lastResult = func(...args) as ReturnType<T>;
|
|
return lastResult;
|
|
};
|
|
|
|
const startTimer = () => {
|
|
timeoutId = window.setTimeout(() => {
|
|
timeoutId = undefined;
|
|
if (trailing && hasPendingTrailingCall) {
|
|
invoke();
|
|
}
|
|
}, wait);
|
|
};
|
|
|
|
const debounced = ((...args: Parameters<T>) => {
|
|
lastArgs = args;
|
|
hasPendingTrailingCall = true;
|
|
|
|
const isFirstCallInWindow = timeoutId === undefined;
|
|
if (timeoutId !== undefined) {
|
|
window.clearTimeout(timeoutId);
|
|
}
|
|
|
|
if (isFirstCallInWindow && leading) {
|
|
invoke();
|
|
}
|
|
|
|
startTimer();
|
|
return lastResult;
|
|
}) as DebouncedFunction<T>;
|
|
|
|
debounced.cancel = () => {
|
|
if (timeoutId !== undefined) {
|
|
window.clearTimeout(timeoutId);
|
|
timeoutId = undefined;
|
|
}
|
|
lastArgs = undefined;
|
|
hasPendingTrailingCall = false;
|
|
};
|
|
|
|
debounced.flush = () => {
|
|
if (timeoutId === undefined) return lastResult;
|
|
window.clearTimeout(timeoutId);
|
|
timeoutId = undefined;
|
|
if (hasPendingTrailingCall) {
|
|
return invoke();
|
|
}
|
|
return lastResult;
|
|
};
|
|
|
|
return debounced;
|
|
}
|