forked from akai/readest
595608bd62
* 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>
68 lines
1.6 KiB
TypeScript
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;
|
|
};
|