Merge pull request #92 from kuyua9/fix/reading-bookmark-route-save-kuyua9

fix: keep reading bookmarks bound to route
This commit is contained in:
Sanbu 散步
2026-05-14 09:06:15 +08:00
committed by GitHub
3 changed files with 93 additions and 22 deletions
@@ -43,7 +43,7 @@
import { computed, nextTick, ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vitepress'
import {
createReadingBookmark,
createReadingBookmarkSnapshot,
readReadingBookmark,
writeReadingBookmark
} from '../utils/readingBookmark.js'
@@ -125,24 +125,40 @@ const progressTitle = computed(() =>
: `${bookmarkTitle.value} · 阅读进度 ${progress.value}%`
)
const saveBookmark = () => {
const clearBookmarkSaveTimer = () => {
if (saveTimer) {
window.clearTimeout(saveTimer)
saveTimer = null
}
}
const clearClickSaveTimer = () => {
if (clickSaveTimer) {
window.clearTimeout(clickSaveTimer)
clickSaveTimer = null
}
}
const saveBookmark = (path = currentPath()) => {
writeReadingBookmark(
getClientStorage(),
createReadingBookmark({
path: currentPath(),
title: articleTitle.value,
section: activeSection.value,
scrollY: window.scrollY,
progress: progress.value
createReadingBookmarkSnapshot({
path,
getTitle: () => articleTitle.value,
getSection: () => activeSection.value,
getScrollY: () => window.scrollY,
getProgress: () => progress.value
})
)
}
const scheduleBookmarkSave = () => {
if (saveTimer) {
window.clearTimeout(saveTimer)
}
saveTimer = window.setTimeout(saveBookmark, 180)
const path = currentPath()
clearBookmarkSaveTimer()
saveTimer = window.setTimeout(() => {
saveTimer = null
saveBookmark(path)
}, 180)
}
const updateProgress = () => {
@@ -325,12 +341,12 @@ const handleClick = () => {
behavior: 'smooth'
})
if (clickSaveTimer) {
window.clearTimeout(clickSaveTimer)
}
const path = currentPath()
clearClickSaveTimer()
clickSaveTimer = window.setTimeout(() => {
clickSaveTimer = null
updateProgress()
saveBookmark()
saveBookmark(path)
}, 400)
}
@@ -344,15 +360,11 @@ onUnmounted(() => {
if (scrollTimer) {
clearTimeout(scrollTimer)
}
if (saveTimer) {
clearTimeout(saveTimer)
}
clearBookmarkSaveTimer()
if (restoreTimer) {
clearTimeout(restoreTimer)
}
if (clickSaveTimer) {
clearTimeout(clickSaveTimer)
}
clearClickSaveTimer()
// 清理拖拽事件
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', endDrag)
@@ -366,6 +378,8 @@ onUnmounted(() => {
watch(
() => route.path,
() => {
clearBookmarkSaveTimer()
clearClickSaveTimer()
resetRouteState()
restoreBookmark()
}
@@ -27,6 +27,24 @@ export const createReadingBookmark = ({
updatedAt: now()
})
export const createReadingBookmarkSnapshot = ({
path,
getTitle = () => '',
getSection = () => '',
getScrollY = () => 0,
getProgress = () => 0,
now = () => Date.now()
}) =>
createReadingBookmark({
path,
title: getTitle(),
section: getSection(),
scrollY: getScrollY(),
progress: getProgress(),
now
})
const normalizeBookmark = (
value,
expectedPath,
@@ -3,6 +3,7 @@ import { describe, it } from 'node:test'
import {
createReadingBookmark,
createReadingBookmarkSnapshot,
getReadingBookmarkKey,
readReadingBookmark,
writeReadingBookmark
@@ -62,6 +63,44 @@ describe('reading bookmarks', () => {
)
})
it('keeps delayed saves bound to the path captured before navigation', () => {
const storage = createStorage()
let currentPath = '/easy-vibe/zh-cn/page-a/'
const scheduledPath = currentPath
currentPath = '/easy-vibe/zh-cn/page-b/'
writeReadingBookmark(
storage,
createReadingBookmarkSnapshot({
path: scheduledPath,
getTitle: () => '页面 A',
getSection: () => '小节 A',
getScrollY: () => 240,
getProgress: () => 32,
now: () => 777
})
)
assert.equal(
readReadingBookmark(storage, currentPath, 1000),
null
)
assert.deepEqual(
readReadingBookmark(storage, scheduledPath, 1000),
{
version: 1,
path: scheduledPath,
title: '页面 A',
section: '小节 A',
scrollY: 240,
progress: 32,
updatedAt: 777
}
)
})
it('normalizes invalid numeric values', () => {
assert.deepEqual(
createReadingBookmark({