mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
feat(reader): show book titles in reader tabs
Use library metadata so browser history and time tracking identify the book being read. Closes #1147
This commit is contained in:
@@ -7,6 +7,7 @@ import { useFoliate, type RelocateDetail } from './epub/composables/useFoliate'
|
||||
import type { SelectionDetail } from './epub/composables/useFoliateSelection'
|
||||
import { useReaderProgress } from './shared/composables/useReaderProgress'
|
||||
import { useReadingSession } from './shared/composables/useReadingSession'
|
||||
import { useReaderPageTitle } from './shared/composables/useReaderPageTitle'
|
||||
import { useReaderState } from './epub/composables/useReaderState'
|
||||
import { useReaderSettings } from './shared/composables/useReaderSettings'
|
||||
import { useCustomFonts } from './epub/composables/useCustomFonts'
|
||||
@@ -49,6 +50,7 @@ const router = useRouter()
|
||||
const bookId = Number(route.params.bookId)
|
||||
const fileId = Number(route.params.fileId)
|
||||
const fileFormat = (route.query.format as string) || 'epub'
|
||||
useReaderPageTitle(bookId)
|
||||
const isAudioFormat = getFormatGroup(fileFormat) === 'audio'
|
||||
const isPdfFormat = fileFormat === 'pdf'
|
||||
const isComicFormat = fileFormat === 'cbz' || fileFormat === 'cbr' || fileFormat === 'cb7'
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref, toValue, type MaybeRefOrGetter, type Ref } from 'vue'
|
||||
import type { BookDetail } from '@bookorbit/types'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
detail: null as unknown as Ref<BookDetail | null>,
|
||||
fetchBook: vi.fn<(bookId: number) => Promise<void>>(),
|
||||
pageTitle: null as MaybeRefOrGetter<string | null | undefined> | null,
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key: string, params?: Record<string, unknown>) => {
|
||||
if (key === 'views.bookDetail.titleWithId') return `Book #${String(params?.id)}`
|
||||
if (key === 'views.bookDetail.title') return 'Book'
|
||||
return key
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/features/book/composables/useBookDetail', () => ({
|
||||
useBookDetail: () => ({ detail: mocks.detail, fetch: mocks.fetchBook }),
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/usePageTitle', () => ({
|
||||
usePageTitle: (title: MaybeRefOrGetter<string | null | undefined>) => {
|
||||
mocks.pageTitle = title
|
||||
},
|
||||
}))
|
||||
|
||||
const { useReaderPageTitle } = await import('../useReaderPageTitle')
|
||||
|
||||
describe('useReaderPageTitle', () => {
|
||||
beforeEach(() => {
|
||||
mocks.detail = ref<BookDetail | null>(null)
|
||||
mocks.fetchBook.mockReset().mockResolvedValue()
|
||||
mocks.pageTitle = null
|
||||
})
|
||||
|
||||
it('loads the requested book and uses its title when available', () => {
|
||||
useReaderPageTitle(42)
|
||||
|
||||
expect(mocks.fetchBook).toHaveBeenCalledExactlyOnceWith(42)
|
||||
expect(toValue(mocks.pageTitle)).toBe('Book #42')
|
||||
|
||||
mocks.detail.value = { id: 42, title: ' The Left Hand of Darkness ' } as BookDetail
|
||||
|
||||
expect(toValue(mocks.pageTitle)).toBe('The Left Hand of Darkness')
|
||||
})
|
||||
|
||||
it('keeps the localized ID fallback when the book title is blank', () => {
|
||||
mocks.detail.value = { id: 42, title: ' ' } as BookDetail
|
||||
|
||||
useReaderPageTitle(42)
|
||||
|
||||
expect(toValue(mocks.pageTitle)).toBe('Book #42')
|
||||
})
|
||||
|
||||
it('uses the generic book fallback when the route ID is invalid', () => {
|
||||
useReaderPageTitle(Number.NaN)
|
||||
|
||||
expect(toValue(mocks.pageTitle)).toBe('Book')
|
||||
expect(mocks.fetchBook).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { usePageTitle } from '@/composables/usePageTitle'
|
||||
import { useBookDetail } from '@/features/book/composables/useBookDetail'
|
||||
|
||||
export function useReaderPageTitle(bookId: number): void {
|
||||
const { t } = useI18n()
|
||||
const { detail, fetch } = useBookDetail()
|
||||
const pageTitle = computed(() => {
|
||||
const title = detail.value?.title?.trim()
|
||||
const fallback = Number.isFinite(bookId) ? t('views.bookDetail.titleWithId', { id: bookId }) : t('views.bookDetail.title')
|
||||
return title || fallback
|
||||
})
|
||||
|
||||
usePageTitle(pageTitle)
|
||||
if (Number.isFinite(bookId)) void fetch(bookId)
|
||||
}
|
||||
Reference in New Issue
Block a user