feat(rsvp): sync reading position to cloud via book_configs (#3801)

This commit is contained in:
Lex Moulton
2026-04-09 09:52:49 -04:00
committed by GitHub
parent 1e259e87b2
commit baee85e7c8
8 changed files with 120 additions and 6 deletions
@@ -4,9 +4,11 @@ import React, { useState, useRef, useEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { useReaderStore } from '@/store/readerStore';
import { useBookDataStore } from '@/store/bookDataStore';
import { useSettingsStore } from '@/store/settingsStore';
import { useThemeStore } from '@/store/themeStore';
import { RSVPController, RsvpStartChoice, RsvpStopPosition } from '@/services/rsvp';
import { eventDispatcher } from '@/utils/event';
import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
import { BookNote } from '@/types/book';
import { Insets } from '@/types/misc';
@@ -103,8 +105,10 @@ const expandRangeToSentence = (range: Range, doc: Document): Range => {
const RSVPControl: React.FC<RSVPControlProps> = ({ bookKey, gridInsets }) => {
const _ = useTranslation();
const { envConfig } = useEnv();
const { settings } = useSettingsStore();
const { getView, getProgress } = useReaderStore();
const { getBookData } = useBookDataStore();
const { getBookData, getConfig, setConfig, saveConfig } = useBookDataStore();
const { themeCode } = useThemeStore();
const [isActive, setIsActive] = useState(false);
@@ -198,6 +202,13 @@ const RSVPControl: React.FC<RSVPControlProps> = ({ bookKey, gridInsets }) => {
const controller = controllerRef.current;
// Seed localStorage from cloud-synced BookConfig when this device has no local position.
// This restores RSVP progress saved on another device after a sync.
const configPos = getConfig(bookKey)?.rsvpPosition;
if (configPos) {
controller.seedPosition(configPos);
}
// Set current CFI for position tracking
if (progress?.location) {
controller.setCurrentCfi(progress.location);
@@ -226,7 +237,7 @@ const RSVPControl: React.FC<RSVPControlProps> = ({ bookKey, gridInsets }) => {
controller.removeEventListener('rsvp-start-choice', handleStartChoice);
}, 100);
},
[_, bookKey, getBookData, getProgress, getView, removeRsvpHighlight],
[_, bookKey, getBookData, getConfig, getProgress, getView, removeRsvpHighlight],
);
const handleStartDialogSelect = useCallback(
@@ -374,9 +385,29 @@ const RSVPControl: React.FC<RSVPControlProps> = ({ bookKey, gridInsets }) => {
controller.stop();
}
// Persist RSVP position to BookConfig so it syncs to the cloud
const rsvpPosition = controller?.getStoredPosition();
if (rsvpPosition) {
const config = getConfig(bookKey);
if (config) {
setConfig(bookKey, { rsvpPosition });
saveConfig(envConfig, bookKey, { ...config, rsvpPosition }, settings);
}
}
setIsActive(false);
setShowStartDialog(false);
}, [bookKey, getView, removeRsvpHighlight, themeCode.primary]);
}, [
bookKey,
envConfig,
getConfig,
getView,
removeRsvpHighlight,
saveConfig,
setConfig,
settings,
themeCode.primary,
]);
const handleChapterSelect = useCallback(
(href: string) => {