forked from akai/readest
fix(reader): remember last read position for markdown files (#4871)
Markdown sections were created with `cfi: ''`, but foliate-js builds a location CFI as `section.cfi ?? CFI.fake.fromIndex(index)`. Nullish coalescing does not fall back for an empty string, so every saved position collapsed to a section-less CFI that resolves to no section. Reopening a `.md` book then fell back to the start even though the library still showed the correct read percentage. Set each section's `cfi` to `CFI.fake.fromIndex(index)`, the same fake spine CFI foliate synthesizes for single-file formats that omit it (e.g. fb2), so positions round-trip across reopens. Fixes #4862 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { makeMarkdownBook } from '@/utils/md';
|
||||
import type { BookDoc } from '@/libs/document';
|
||||
import { CFI } from '@/libs/document';
|
||||
import { getIndexFromCfi } from '@/utils/cfi';
|
||||
|
||||
// makeMarkdownBook returns a foliate book with a few methods (resolveHref,
|
||||
// isExternal, destroy) and a per-section load() that the BookDoc type does not
|
||||
@@ -80,6 +82,20 @@ describe('makeMarkdownBook', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('gives each section a CFI base that round-trips to its index (resume position)', async () => {
|
||||
// foliate-js view.getCFI does `section.cfi ?? CFI.fake.fromIndex(index)`,
|
||||
// so an empty-string cfi defeats the fallback and the generated location
|
||||
// CFI loses its spine step. Reopening then resolves to no section and the
|
||||
// reader falls back to the start. Verify each section carries a base CFI
|
||||
// that survives the getCFI -> resolveCFI round-trip.
|
||||
const book = await make('# One\n\na\n\n# Two\n\nb\n\n# Three\n\nc\n');
|
||||
book.sections.forEach((section, index) => {
|
||||
const baseCFI = section.cfi ?? CFI.fake.fromIndex(index);
|
||||
const locationCFI = CFI.joinIndir(baseCFI, '/4/2:3');
|
||||
expect(getIndexFromCfi(locationCFI)).toBe(index);
|
||||
});
|
||||
});
|
||||
|
||||
it('produces XHTML that parses without errors despite void tags', async () => {
|
||||
const book = await make('# H\n\nline one \nline two\n\n---\n\n\n');
|
||||
const doc = await book.sections[0]!.createDocument();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { marked } from 'marked';
|
||||
|
||||
import type { BookDoc, SectionItem } from '@/libs/document';
|
||||
import { CFI } from '@/libs/document';
|
||||
import { sanitizeHtml } from './sanitize';
|
||||
|
||||
// Render a standalone Markdown (.md) file into an in-memory foliate-js book at
|
||||
@@ -158,7 +159,12 @@ export async function makeMarkdownBook(file: File): Promise<BookDoc> {
|
||||
const urls: (string | undefined)[] = new Array(xhtml.length).fill(undefined);
|
||||
const sections: MarkdownSection[] = xhtml.map((str, index) => ({
|
||||
id: String(index),
|
||||
cfi: '',
|
||||
// A per-section spine CFI base. foliate-js builds location CFIs as
|
||||
// `section.cfi ?? CFI.fake.fromIndex(index)`; an empty string defeats that
|
||||
// `??` fallback, so every saved position would collapse to a section-less
|
||||
// CFI and reopening would resume from the start. Set the same fake spine
|
||||
// CFI foliate would synthesize so positions round-trip across reopens.
|
||||
cfi: CFI.fake.fromIndex(index),
|
||||
size: new TextEncoder().encode(str).length,
|
||||
linear: 'yes',
|
||||
load: () => {
|
||||
|
||||
Reference in New Issue
Block a user