From 708e06a46e531bc9e7d80e8d7e1c31702b53053d Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Fri, 15 May 2026 01:23:32 +0800 Subject: [PATCH] fix(opds): show summary as book description, closes #4156 (#4162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TypeScript types in `src/types/opds.ts` declared fresh `Symbol('content')` / `Symbol('summary')` instances. foliate-js's `opds.js` declared its own distinct ones, and since Symbols are unique per call, `metadata[SYMBOL.CONTENT]` always returned undefined — even though the parser had written the value under a same-named Symbol. This broke silently in 0.11.1 after foliate-js #14 stopped also setting a plain `content: string` fallback. For OPDS 1.x feeds (e.g. CWA) the book description lives in ``, which foliate-js exposes only via `[SYMBOL.CONTENT]` — so the description vanished. Re-export the SYMBOL from foliate-js so consumers read the same Symbol identities the parser writes. --- .../src/__tests__/utils/opds-feed.test.ts | 29 ++++++++++++++++++- apps/readest-app/src/types/opds.ts | 15 +++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/readest-app/src/__tests__/utils/opds-feed.test.ts b/apps/readest-app/src/__tests__/utils/opds-feed.test.ts index 7e7938fd..c28eb857 100644 --- a/apps/readest-app/src/__tests__/utils/opds-feed.test.ts +++ b/apps/readest-app/src/__tests__/utils/opds-feed.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { getFeed, getPublication } from 'foliate-js/opds.js'; -import type { OPDSFeed, OPDSPublication } from '@/types/opds'; +import { SYMBOL, type OPDSFeed, type OPDSPublication } from '@/types/opds'; const MIME_XML = 'application/xml'; @@ -222,6 +222,33 @@ describe('OPDS feed parsing', () => { expect(pub.metadata.updated).toBe('2026-01-15T10:30:00.000Z'); }); + // Regression test for https://github.com/readest/readest/issues/4156 + // CWA (and other OPDS 1.x servers) place the book description in + // . foliate-js attaches it under SYMBOL.CONTENT, so the + // SYMBOL exported from @/types/opds must be the same Symbol instance the + // parser writes — otherwise PublicationView reads undefined and the + // description disappears from the book details page. + it('should expose via SYMBOL.CONTENT for OPDS 1.x feeds', () => { + const opds1Feed = ` + + CWA Library + + A Book With A Summary + A short blurb describing the book, set by the OPDS server in <summary>. + + +`; + const doc = parseXML(opds1Feed); + const feed = getFeed(doc) as OPDSFeed; + + expect(feed.publications).toHaveLength(1); + const metadata = feed.publications![0]!.metadata; + const content = metadata[SYMBOL.CONTENT]; + expect(content).toBeDefined(); + expect(content!.value).toContain('A short blurb describing the book'); + }); + it('should handle entries without id or updated gracefully', () => { const minimalFeed = ` diff --git a/apps/readest-app/src/types/opds.ts b/apps/readest-app/src/types/opds.ts index e57ebffa..c9e78312 100644 --- a/apps/readest-app/src/types/opds.ts +++ b/apps/readest-app/src/types/opds.ts @@ -1,3 +1,10 @@ +// SYMBOL must be re-exported from foliate-js so consumers read the same Symbol +// instances that the parser writes onto publication metadata. Declaring fresh +// `Symbol('content')` calls here would produce different identities, and +// `metadata[SYMBOL.CONTENT]` would silently return undefined — losing the book +// description for OPDS 1.x feeds where it lives in . +import { SYMBOL as FOLIATE_SYMBOL } from 'foliate-js/opds.js'; + export const REL = { ACQ: 'http://opds-spec.org/acquisition', FACET: 'http://opds-spec.org/facet', @@ -11,13 +18,7 @@ export const REL = { STREAM: 'http://vaemendis.net/opds-pse/stream', } as const; -const SUMMARY = Symbol('summary'); -const CONTENT = Symbol('content'); - -export const SYMBOL = { - SUMMARY, - CONTENT, -} as const; +export const SYMBOL = FOLIATE_SYMBOL as { SUMMARY: symbol; CONTENT: symbol }; export interface OPDSCatalog { id: string;