Files
readest/apps/readest-app/src/utils/debounce.ts
T
Zeedif 595608bd62 feat: Implement KOReader Progress Synchronization (#1770)
* feat(sync): implement KOReader progress synchronization

This commit introduces a comprehensive feature to synchronize reading progress with a KOReader sync server. It includes a compatibility layer to handle discrepancies between Readest's CFI-based progress and KOReader's XPointer/page-based progress, primarily using the `percentage` field as a common ground.

Key additions include:
- A new settings panel under "KOReader Sync" for server configuration, authentication, and sync strategy management (e.g., prompt on conflict, always use latest).
- A conflict resolution dialog that appears when remote progress differs significantly from local progress, allowing the user to choose which version to keep.
- A client-side `useKOSync` hook to manage the entire synchronization lifecycle, including API calls, state management, and conflict resolution logic.
- A new API endpoint `/api/kosync` that acts as a secure proxy to the user-configured KOReader sync server, handling authentication and forwarding requests.
- Logic to differentiate between paginated (PDF/CBZ) and reflowable (EPUB) formats, using page numbers for paginated files where possible and falling back to percentage for reliability.
- Spanish translations for all UI elements related to the KOReader sync feature.
- Addition of `uuid` package to generate a unique `device_id` for sync purposes.

Refactor:
- The `debounce` utility has been improved to include `flush` and `cancel` methods, allowing for more precise control over debounced function execution, which is now used in the sync hook.

* fix(kosync): add support for converting between XPointer and CFI in progress synchronization

* fix(kosync): update navigation method to use select instead of goTo for paginated formats

* fix(kosync): refactor synchronization settings and improve conflict resolution handling

* fix(kosync): add event dispatcher for flushing KOReader synchronization

* fix(sync): handle xpointer in a different section, fix styling

* i18n: update translations

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-08-11 17:53:28 +02:00

68 lines
1.6 KiB
TypeScript

interface DebounceOptions {
emitLast?: boolean;
}
/**
* Debounces a function by waiting `delay` ms after the last call before executing it.
* If `emitLast` is false, it cancels the call instead of delaying it.
*
* @returns A debounced function with additional `flush` and `cancel` methods.
*/
export const debounce = <T extends (...args: Parameters<T>) => void | Promise<void>>(
func: T,
delay: number,
options: DebounceOptions = { emitLast: true },
): ((...args: Parameters<T>) => void) & { flush: () => void; cancel: () => void } => {
let timeout: ReturnType<typeof setTimeout> | null = null;
let lastArgs: Parameters<T> | null = null;
const debounced = (...args: Parameters<T>): void => {
lastArgs = args;
if (timeout) {
clearTimeout(timeout);
}
if (options.emitLast) {
timeout = setTimeout(() => {
if (lastArgs) {
func(...(lastArgs as Parameters<T>));
lastArgs = null;
}
timeout = null;
}, delay);
} else {
timeout = setTimeout(() => {
func(...args);
timeout = null;
}, delay);
}
};
/**
* Immediately executes the last pending debounced function call.
*/
debounced.flush = () => {
if (timeout) {
clearTimeout(timeout);
timeout = null;
if (lastArgs) {
func(...(lastArgs as Parameters<T>));
lastArgs = null;
}
}
};
/**
* Cancels the pending debounced function call.
*/
debounced.cancel = () => {
if (timeout) {
clearTimeout(timeout);
timeout = null;
lastArgs = null;
}
};
return debounced;
};