mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(opds): send book descriptions as plain text
Descriptions are stored as HTML but the feed sent them escaped under type="text", so clients that honour the type, such as Foliate, showed the tags literally. Flatten them with htmlToPlainText, keeping line breaks. Fixes #1569
This commit is contained in:
@@ -522,4 +522,40 @@ describe('OpdsService', () => {
|
||||
expect(xml).toContain(''');
|
||||
});
|
||||
});
|
||||
|
||||
describe('book descriptions', () => {
|
||||
function feedFor(description: string | null): string {
|
||||
return makeService().generateAcquisitionFeed(
|
||||
'Catalog',
|
||||
'urn:bookorbit:catalog',
|
||||
[sampleBook({ description })],
|
||||
1,
|
||||
1,
|
||||
50,
|
||||
`${BASE}/catalog?page=1&size=50`,
|
||||
'test-token',
|
||||
);
|
||||
}
|
||||
|
||||
it('sends an HTML description as plain text with its paragraph and line breaks', () => {
|
||||
const xml = feedFor('<p>The <strong>first</strong> book & its sequel.</p><p>Line one<br>line two</p>');
|
||||
|
||||
expect(xml).toContain('<content type="text">The first book & its sequel.\nLine one\nline two</content>');
|
||||
expect(xml).not.toContain('<p>');
|
||||
expect(xml).not.toContain('<strong>');
|
||||
});
|
||||
|
||||
it('keeps a plain-text description and its line breaks unchanged', () => {
|
||||
const xml = feedFor('Part one.\n\nPart two.');
|
||||
|
||||
expect(xml).toContain('<content type="text">Part one.\n\nPart two.</content>');
|
||||
});
|
||||
|
||||
it.each([
|
||||
['no description', null],
|
||||
['a description holding only markup', '<p> </p><br>'],
|
||||
])('sends empty content for %s', (_label, description) => {
|
||||
expect(feedFor(description)).toContain('<content type="text"/>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable, Optional } from '@nestjs/common';
|
||||
|
||||
import { esc, fileMimeType, OPDS_MIME_ACQ, OPDS_MIME_ATOM, OPDS_MIME_NAV, OPDS_MIME_SEARCH, xmlEl, xmlLink } from './opds-xml.helpers';
|
||||
import { htmlToPlainText } from '../../common/utils/html-to-text.utils';
|
||||
import { DeliverySelectionService } from '../book/delivery-selection.service';
|
||||
import type { OpdsRequestUser } from './opds-auth.guard';
|
||||
import type { OpdsBookEntry } from './opds-book.service';
|
||||
@@ -172,8 +173,12 @@ export class OpdsService {
|
||||
lines.push(` <author>${xmlEl('name', author)}</author>`);
|
||||
}
|
||||
|
||||
if (book.description) {
|
||||
lines.push(` <content type="text">${esc(book.description)}</content>`);
|
||||
// Descriptions are stored as HTML. Clients that honour type="text" show markup literally,
|
||||
// and type="html" would hand unsanitized markup from book files and providers to clients
|
||||
// that render it, so the feed carries the text alone.
|
||||
const description = book.description ? htmlToPlainText(book.description, { preserveLineBreaks: true }) : '';
|
||||
if (description) {
|
||||
lines.push(` <content type="text">${esc(description)}</content>`);
|
||||
} else {
|
||||
lines.push(' <content type="text"/>');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user