feat(w3): add continuous read mode and Wave 3 plan

Introduce ReadModeOverlay with device width and font theme persistence, sidebar and volume menu entry, plus E2E-READ-01 through 03.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 16:38:54 +08:00
parent fe6e411d2c
commit c10be87d18
13 changed files with 653 additions and 2 deletions
@@ -28,6 +28,7 @@ import { ChapterBridgeModal } from '@renderer/components/bridge/ChapterBridgeMod
import { TemplateQuickCreateModal } from '@renderer/components/chapter/TemplateQuickCreateModal'
import { ChapterMetaPanel } from '@renderer/components/chapter/ChapterMetaPanel'
import { VolumeContextMenu } from '@renderer/components/volume/VolumeContextMenu'
import { useReadModeStore } from '@renderer/stores/useReadModeStore'
import { ExportModal } from '@renderer/components/export/ExportModal'
import { useExportStore } from '@renderer/stores/useExportStore'
import {
@@ -124,6 +125,7 @@ export function EditorLayout(): React.JSX.Element {
const [bridgeOpen, setBridgeOpen] = useState(false)
const [templateModalOpen, setTemplateModalOpen] = useState(false)
const [chapterMetaOpen, setChapterMetaOpen] = useState(false)
const openReadMode = useReadModeStore((s) => s.openForVolume)
const [volumeMenu, setVolumeMenu] = useState<{ volumeId: string; x: number; y: number } | null>(
null
)
@@ -497,6 +499,22 @@ export function EditorLayout(): React.JSX.Element {
>
+ {t('template.quickNew')}
</button>
<button
type="button"
data-testid="chapter-read-mode"
style={{ marginTop: 6 }}
disabled={!activeVolumeId}
onClick={() => {
if (!activeVolumeId) return
const idx = chapters
.filter((c) => c.volumeId === activeVolumeId)
.sort((a, b) => a.sortOrder - b.sortOrder)
.findIndex((c) => c.id === selectedChapterId)
openReadMode(activeVolumeId, Math.max(0, idx))
}}
>
👁 {t('readMode.open')}
</button>
<button type="button" style={{ marginTop: 6 }} onClick={() => void handleNewVolume()}>
+ {t('editor.newVolume')}
</button>
@@ -710,6 +728,7 @@ export function EditorLayout(): React.JSX.Element {
hasChapters={chapters.some((c) => c.volumeId === volumeMenuVolume.id)}
x={volumeMenu.x}
y={volumeMenu.y}
onOpenReadMode={() => openReadMode(volumeMenuVolume.id, 0)}
onClose={() => setVolumeMenu(null)}
onChanged={refreshChapters}
/>