forked from akai/readest
An OPDS entry with full metadata and a cover image but no acquisition link — e.g. a Calibre book whose file was removed but kept for tracking borrowed/loaned titles — was classified by foliate-js as a navigation item whose href fell back to the cover image link. Tapping it loaded the image, which is neither XML nor JSON, so the OPDS browser crashed with a JSON parse error. - Bump foliate-js to include the getFeed fix that classifies such metadata-only entries as publications instead of navigation. - PublicationView: show "No downloadable format available" when an entry has no acquisition or stream links. - loadOPDS: defense-in-depth — surface a clear message instead of a raw JSON.parse SyntaxError when a response is neither XML nor JSON. - Add tests covering the Calibre no-format entry and a regression guard that a true navigation entry still classifies as navigation; add the two new UI strings across all locales. Closes #4599 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1883,5 +1883,7 @@
|
||||
"Update": "تحديث",
|
||||
"Nightly Builds": "إصدارات ليلية",
|
||||
"Early daily builds": "إصدارات يومية مبكرة",
|
||||
"Privacy": "الخصوصية"
|
||||
"Privacy": "الخصوصية",
|
||||
"No downloadable format available": "لا يتوفر تنسيق قابل للتنزيل",
|
||||
"Content is neither valid XML nor JSON": "المحتوى ليس بصيغة XML ولا JSON صالحة"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "আপডেট",
|
||||
"Nightly Builds": "নাইটলি বিল্ড",
|
||||
"Early daily builds": "প্রাথমিক দৈনিক বিল্ড",
|
||||
"Privacy": "গোপনীয়তা"
|
||||
"Privacy": "গোপনীয়তা",
|
||||
"No downloadable format available": "ডাউনলোডযোগ্য কোনো ফরম্যাট নেই",
|
||||
"Content is neither valid XML nor JSON": "বিষয়বস্তু বৈধ XML বা JSON কোনোটিই নয়"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "གསར་སྒྱུར།",
|
||||
"Nightly Builds": "མཚན་མོའི་བཟོ་སྐྲུན།",
|
||||
"Early daily builds": "སྔ་མོའི་ཉིན་རེའི་བཟོ་སྐྲུན།",
|
||||
"Privacy": "གསང་དོན།"
|
||||
"Privacy": "གསང་དོན།",
|
||||
"No downloadable format available": "ཕབ་ལེན་བྱེད་ཆོག་པའི་རྣམ་པ་མི་འདུག",
|
||||
"Content is neither valid XML nor JSON": "ནང་དོན་ནི་ XML དང་ JSON གང་ཡང་ཚད་ལྡན་མིན།"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "Aktualisierung",
|
||||
"Nightly Builds": "Nightly-Builds",
|
||||
"Early daily builds": "Frühe tägliche Builds",
|
||||
"Privacy": "Datenschutz"
|
||||
"Privacy": "Datenschutz",
|
||||
"No downloadable format available": "Kein herunterladbares Format verfügbar",
|
||||
"Content is neither valid XML nor JSON": "Inhalt ist weder gültiges XML noch JSON"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "Ενημέρωση",
|
||||
"Nightly Builds": "Νυχτερινές εκδόσεις",
|
||||
"Early daily builds": "Πρώιμες καθημερινές εκδόσεις",
|
||||
"Privacy": "Απόρρητο"
|
||||
"Privacy": "Απόρρητο",
|
||||
"No downloadable format available": "Δεν υπάρχει διαθέσιμη μορφή για λήψη",
|
||||
"Content is neither valid XML nor JSON": "Το περιεχόμενο δεν είναι ούτε έγκυρο XML ούτε JSON"
|
||||
}
|
||||
|
||||
@@ -1784,5 +1784,7 @@
|
||||
"Update": "Actualización",
|
||||
"Nightly Builds": "Compilaciones nocturnas",
|
||||
"Early daily builds": "Compilaciones diarias anticipadas",
|
||||
"Privacy": "Privacidad"
|
||||
"Privacy": "Privacidad",
|
||||
"No downloadable format available": "No hay ningún formato descargable disponible",
|
||||
"Content is neither valid XML nor JSON": "El contenido no es XML ni JSON válido"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "بهروزرسانی",
|
||||
"Nightly Builds": "نسخههای شبانه",
|
||||
"Early daily builds": "نسخههای روزانهٔ آزمایشی",
|
||||
"Privacy": "حریم خصوصی"
|
||||
"Privacy": "حریم خصوصی",
|
||||
"No downloadable format available": "هیچ قالب قابل دانلودی موجود نیست",
|
||||
"Content is neither valid XML nor JSON": "محتوا نه XML معتبر است و نه JSON"
|
||||
}
|
||||
|
||||
@@ -1784,5 +1784,7 @@
|
||||
"Update": "Mise à jour",
|
||||
"Nightly Builds": "Versions nocturnes",
|
||||
"Early daily builds": "Versions quotidiennes anticipées",
|
||||
"Privacy": "Confidentialité"
|
||||
"Privacy": "Confidentialité",
|
||||
"No downloadable format available": "Aucun format téléchargeable disponible",
|
||||
"Content is neither valid XML nor JSON": "Le contenu n'est ni du XML ni du JSON valide"
|
||||
}
|
||||
|
||||
@@ -1784,5 +1784,7 @@
|
||||
"Update": "עדכון",
|
||||
"Nightly Builds": "גרסאות לילה",
|
||||
"Early daily builds": "גרסאות יומיות מוקדמות",
|
||||
"Privacy": "פרטיות"
|
||||
"Privacy": "פרטיות",
|
||||
"No downloadable format available": "אין פורמט להורדה זמין",
|
||||
"Content is neither valid XML nor JSON": "התוכן אינו XML או JSON תקין"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "अपडेट",
|
||||
"Nightly Builds": "नाइटली बिल्ड",
|
||||
"Early daily builds": "प्रारंभिक दैनिक बिल्ड",
|
||||
"Privacy": "गोपनीयता"
|
||||
"Privacy": "गोपनीयता",
|
||||
"No downloadable format available": "कोई डाउनलोड करने योग्य प्रारूप उपलब्ध नहीं है",
|
||||
"Content is neither valid XML nor JSON": "सामग्री न तो मान्य XML है और न ही JSON"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "Frissítés",
|
||||
"Nightly Builds": "Éjszakai buildek",
|
||||
"Early daily builds": "Korai napi buildek",
|
||||
"Privacy": "Adatvédelem"
|
||||
"Privacy": "Adatvédelem",
|
||||
"No downloadable format available": "Nincs letölthető formátum",
|
||||
"Content is neither valid XML nor JSON": "A tartalom sem érvényes XML, sem JSON"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "Pembaruan",
|
||||
"Nightly Builds": "Build Nightly",
|
||||
"Early daily builds": "Build harian awal",
|
||||
"Privacy": "Privasi"
|
||||
"Privacy": "Privasi",
|
||||
"No downloadable format available": "Tidak ada format yang dapat diunduh",
|
||||
"Content is neither valid XML nor JSON": "Konten bukan XML maupun JSON yang valid"
|
||||
}
|
||||
|
||||
@@ -1784,5 +1784,7 @@
|
||||
"Update": "Aggiornamento",
|
||||
"Nightly Builds": "Build notturne",
|
||||
"Early daily builds": "Build giornaliere anticipate",
|
||||
"Privacy": "Privacy"
|
||||
"Privacy": "Privacy",
|
||||
"No downloadable format available": "Nessun formato scaricabile disponibile",
|
||||
"Content is neither valid XML nor JSON": "Il contenuto non è né XML né JSON valido"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "アップデート",
|
||||
"Nightly Builds": "ナイトリービルド",
|
||||
"Early daily builds": "毎日の先行ビルド",
|
||||
"Privacy": "プライバシー"
|
||||
"Privacy": "プライバシー",
|
||||
"No downloadable format available": "ダウンロード可能な形式がありません",
|
||||
"Content is neither valid XML nor JSON": "コンテンツが有効な XML でも JSON でもありません"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "업데이트",
|
||||
"Nightly Builds": "야간 빌드",
|
||||
"Early daily builds": "매일 제공되는 미리 보기 빌드",
|
||||
"Privacy": "개인정보 보호"
|
||||
"Privacy": "개인정보 보호",
|
||||
"No downloadable format available": "다운로드 가능한 형식이 없습니다",
|
||||
"Content is neither valid XML nor JSON": "콘텐츠가 유효한 XML 또는 JSON이 아닙니다"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "Kemas kini",
|
||||
"Nightly Builds": "Binaan Nightly",
|
||||
"Early daily builds": "Binaan harian awal",
|
||||
"Privacy": "Privasi"
|
||||
"Privacy": "Privasi",
|
||||
"No downloadable format available": "Tiada format yang boleh dimuat turun",
|
||||
"Content is neither valid XML nor JSON": "Kandungan bukan XML mahupun JSON yang sah"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "Update",
|
||||
"Nightly Builds": "Nightly-builds",
|
||||
"Early daily builds": "Vroege dagelijkse builds",
|
||||
"Privacy": "Privacy"
|
||||
"Privacy": "Privacy",
|
||||
"No downloadable format available": "Geen downloadbaar formaat beschikbaar",
|
||||
"Content is neither valid XML nor JSON": "Inhoud is geen geldige XML of JSON"
|
||||
}
|
||||
|
||||
@@ -1817,5 +1817,7 @@
|
||||
"Update": "Aktualizacja",
|
||||
"Nightly Builds": "Kompilacje nocne",
|
||||
"Early daily builds": "Wczesne kompilacje dzienne",
|
||||
"Privacy": "Prywatność"
|
||||
"Privacy": "Prywatność",
|
||||
"No downloadable format available": "Brak formatu do pobrania",
|
||||
"Content is neither valid XML nor JSON": "Treść nie jest prawidłowym XML ani JSON"
|
||||
}
|
||||
|
||||
@@ -1784,5 +1784,7 @@
|
||||
"Update": "Atualização",
|
||||
"Nightly Builds": "Compilações noturnas",
|
||||
"Early daily builds": "Compilações diárias antecipadas",
|
||||
"Privacy": "Privacidade"
|
||||
"Privacy": "Privacidade",
|
||||
"No downloadable format available": "Nenhum formato disponível para download",
|
||||
"Content is neither valid XML nor JSON": "O conteúdo não é XML nem JSON válido"
|
||||
}
|
||||
|
||||
@@ -1784,5 +1784,7 @@
|
||||
"Update": "Atualização",
|
||||
"Nightly Builds": "Compilações noturnas",
|
||||
"Early daily builds": "Compilações diárias antecipadas",
|
||||
"Privacy": "Privacidade"
|
||||
"Privacy": "Privacidade",
|
||||
"No downloadable format available": "Nenhum formato disponível para download",
|
||||
"Content is neither valid XML nor JSON": "O conteúdo não é XML nem JSON válido"
|
||||
}
|
||||
|
||||
@@ -1784,5 +1784,7 @@
|
||||
"Update": "Actualizare",
|
||||
"Nightly Builds": "Versiuni nightly",
|
||||
"Early daily builds": "Versiuni zilnice timpurii",
|
||||
"Privacy": "Confidențialitate"
|
||||
"Privacy": "Confidențialitate",
|
||||
"No downloadable format available": "Niciun format descărcabil disponibil",
|
||||
"Content is neither valid XML nor JSON": "Conținutul nu este nici XML, nici JSON valid"
|
||||
}
|
||||
|
||||
@@ -1817,5 +1817,7 @@
|
||||
"Update": "Обновление",
|
||||
"Nightly Builds": "Ночные сборки",
|
||||
"Early daily builds": "Ранние ежедневные сборки",
|
||||
"Privacy": "Конфиденциальность"
|
||||
"Privacy": "Конфиденциальность",
|
||||
"No downloadable format available": "Нет доступного формата для загрузки",
|
||||
"Content is neither valid XML nor JSON": "Содержимое не является ни корректным XML, ни JSON"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "යාවත්කාලීන කිරීම",
|
||||
"Nightly Builds": "රාත්රී නිකුතු",
|
||||
"Early daily builds": "මුල් දෛනික නිකුතු",
|
||||
"Privacy": "පෞද්ගලිකත්වය"
|
||||
"Privacy": "පෞද්ගලිකත්වය",
|
||||
"No downloadable format available": "බාගත කළ හැකි ආකෘතියක් නොමැත",
|
||||
"Content is neither valid XML nor JSON": "අන්තර්ගතය වලංගු XML හෝ JSON නොවේ"
|
||||
}
|
||||
|
||||
@@ -1817,5 +1817,7 @@
|
||||
"Update": "Posodobitev",
|
||||
"Nightly Builds": "Nočne gradnje",
|
||||
"Early daily builds": "Zgodnje dnevne gradnje",
|
||||
"Privacy": "Zasebnost"
|
||||
"Privacy": "Zasebnost",
|
||||
"No downloadable format available": "Na voljo ni nobene oblike za prenos",
|
||||
"Content is neither valid XML nor JSON": "Vsebina ni veljaven XML niti JSON"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "Uppdatering",
|
||||
"Nightly Builds": "Nattliga versioner",
|
||||
"Early daily builds": "Tidiga dagliga versioner",
|
||||
"Privacy": "Integritet"
|
||||
"Privacy": "Integritet",
|
||||
"No downloadable format available": "Inget nedladdningsbart format tillgängligt",
|
||||
"Content is neither valid XML nor JSON": "Innehållet är varken giltig XML eller JSON"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "புதுப்பிப்பு",
|
||||
"Nightly Builds": "இரவு உருவாக்கங்கள்",
|
||||
"Early daily builds": "ஆரம்ப தினசரி உருவாக்கங்கள்",
|
||||
"Privacy": "தனியுரிமை"
|
||||
"Privacy": "தனியுரிமை",
|
||||
"No downloadable format available": "பதிவிறக்கம் செய்யக்கூடிய வடிவம் எதுவும் இல்லை",
|
||||
"Content is neither valid XML nor JSON": "உள்ளடக்கம் சரியான XML அல்லது JSON அல்ல"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "อัปเดต",
|
||||
"Nightly Builds": "บิลด์รายคืน",
|
||||
"Early daily builds": "บิลด์รายวันรุ่นแรก",
|
||||
"Privacy": "ความเป็นส่วนตัว"
|
||||
"Privacy": "ความเป็นส่วนตัว",
|
||||
"No downloadable format available": "ไม่มีรูปแบบที่ดาวน์โหลดได้",
|
||||
"Content is neither valid XML nor JSON": "เนื้อหาไม่ใช่ทั้ง XML และ JSON ที่ถูกต้อง"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "Güncelleme",
|
||||
"Nightly Builds": "Gecelik sürümler",
|
||||
"Early daily builds": "Erken günlük sürümler",
|
||||
"Privacy": "Gizlilik"
|
||||
"Privacy": "Gizlilik",
|
||||
"No downloadable format available": "İndirilebilir biçim yok",
|
||||
"Content is neither valid XML nor JSON": "İçerik geçerli XML veya JSON değil"
|
||||
}
|
||||
|
||||
@@ -1817,5 +1817,7 @@
|
||||
"Update": "Оновлення",
|
||||
"Nightly Builds": "Нічні збірки",
|
||||
"Early daily builds": "Ранні щоденні збірки",
|
||||
"Privacy": "Конфіденційність"
|
||||
"Privacy": "Конфіденційність",
|
||||
"No downloadable format available": "Немає доступного формату для завантаження",
|
||||
"Content is neither valid XML nor JSON": "Вміст не є дійсним XML чи JSON"
|
||||
}
|
||||
|
||||
@@ -1751,5 +1751,7 @@
|
||||
"Update": "Yangilanish",
|
||||
"Nightly Builds": "Tungi buildlar",
|
||||
"Early daily builds": "Erta kunlik buildlar",
|
||||
"Privacy": "Maxfiylik"
|
||||
"Privacy": "Maxfiylik",
|
||||
"No downloadable format available": "Yuklab olinadigan format mavjud emas",
|
||||
"Content is neither valid XML nor JSON": "Kontent yaroqli XML ham, JSON ham emas"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "Cập nhật",
|
||||
"Nightly Builds": "Bản dựng hằng đêm",
|
||||
"Early daily builds": "Bản dựng hằng ngày sớm",
|
||||
"Privacy": "Quyền riêng tư"
|
||||
"Privacy": "Quyền riêng tư",
|
||||
"No downloadable format available": "Không có định dạng nào để tải xuống",
|
||||
"Content is neither valid XML nor JSON": "Nội dung không phải XML hay JSON hợp lệ"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "更新",
|
||||
"Nightly Builds": "每夜构建版",
|
||||
"Early daily builds": "每日抢先构建版",
|
||||
"Privacy": "隐私"
|
||||
"Privacy": "隐私",
|
||||
"No downloadable format available": "没有可下载的格式",
|
||||
"Content is neither valid XML nor JSON": "内容既不是有效的 XML 也不是 JSON"
|
||||
}
|
||||
|
||||
@@ -1718,5 +1718,7 @@
|
||||
"Update": "更新",
|
||||
"Nightly Builds": "每夜建置版",
|
||||
"Early daily builds": "每日搶先建置版",
|
||||
"Privacy": "隱私"
|
||||
"Privacy": "隱私",
|
||||
"No downloadable format available": "沒有可下載的格式",
|
||||
"Content is neither valid XML nor JSON": "內容既不是有效的 XML 也不是 JSON"
|
||||
}
|
||||
|
||||
@@ -167,6 +167,76 @@ describe('OPDS feed parsing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('metadata-only entries without an acquisition link (Calibre, #4599)', () => {
|
||||
// Reproduces https://github.com/readest/readest/issues/4599
|
||||
// A Calibre book whose file was removed (e.g. a borrowed/loaned title kept
|
||||
// for tracking) still emits a full metadata entry with cover/thumbnail
|
||||
// links but no acquisition link. Such an entry was classified as a
|
||||
// navigation item whose href fell back to links[0] — the cover image — so
|
||||
// tapping it tried to load the image as a feed and crashed with a JSON
|
||||
// parse error. It should be treated as a publication (so its metadata is
|
||||
// shown) with no downloadable format.
|
||||
const calibreNoFormatFeed = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:opds="http://opds-spec.org/2010/catalog">
|
||||
<title>Calibre Library</title>
|
||||
<entry>
|
||||
<title>A Borrowed Book</title>
|
||||
<id>urn:uuid:1234</id>
|
||||
<author><name>Jane Author</name></author>
|
||||
<updated>2026-01-15T10:30:00Z</updated>
|
||||
<dc:language>eng</dc:language>
|
||||
<summary>A book I borrowed; the file is no longer available.</summary>
|
||||
<link type="image/jpeg" href="/get/cover/123/lib" rel="http://opds-spec.org/image"/>
|
||||
<link type="image/png" href="/get/thumb/123/lib" rel="http://opds-spec.org/image/thumbnail"/>
|
||||
</entry>
|
||||
</feed>`;
|
||||
|
||||
it('classifies a metadata+cover entry with no acquisition link as a publication', () => {
|
||||
const doc = parseXML(calibreNoFormatFeed);
|
||||
const feed = getFeed(doc) as OPDSFeed;
|
||||
|
||||
expect(feed.publications).toBeDefined();
|
||||
expect(feed.publications!).toHaveLength(1);
|
||||
expect(feed.publications![0]!.metadata.title).toBe('A Borrowed Book');
|
||||
// The crash path: the entry must NOT become a navigation item pointing at
|
||||
// the cover image.
|
||||
expect(feed.navigation).toBeUndefined();
|
||||
});
|
||||
|
||||
it('exposes no acquisition link for a format-less publication', () => {
|
||||
const doc = parseXML(calibreNoFormatFeed);
|
||||
const feed = getFeed(doc) as OPDSFeed;
|
||||
const pub = feed.publications![0]!;
|
||||
const hasAcquisition = pub.links.some((link) => {
|
||||
const rels = Array.isArray(link.rel) ? link.rel : [link.rel ?? ''];
|
||||
return rels.some((rel) => rel.startsWith('http://opds-spec.org/acquisition'));
|
||||
});
|
||||
expect(hasAcquisition).toBe(false);
|
||||
});
|
||||
|
||||
it('still classifies a true navigation entry (sub-catalog link, no image) as navigation', () => {
|
||||
const navFeed = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Root</title>
|
||||
<entry>
|
||||
<title>By Author</title>
|
||||
<link rel="subsection" href="author/?opds"
|
||||
type="application/atom+xml;profile=opds-catalog"/>
|
||||
<updated>2026-03-28T05:37:03Z</updated>
|
||||
</entry>
|
||||
</feed>`;
|
||||
const doc = parseXML(navFeed);
|
||||
const feed = getFeed(doc) as OPDSFeed;
|
||||
|
||||
expect(feed.publications).toBeUndefined();
|
||||
expect(feed.navigation).toBeDefined();
|
||||
expect(feed.navigation!).toHaveLength(1);
|
||||
expect(feed.navigation![0]!.title).toBe('By Author');
|
||||
});
|
||||
});
|
||||
|
||||
describe('entry id and updated parsing', () => {
|
||||
const shelfFeed = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
|
||||
@@ -223,6 +223,10 @@ export function PublicationView({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!downloadedBook && acquisitionLinks.length === 0 && streamLinks.length === 0 && (
|
||||
<p className='text-base-content/60 text-sm'>{_('No downloadable format available')}</p>
|
||||
)}
|
||||
|
||||
{(acquisitionLinks.length > 0 || streamLinks.length > 0) && (
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
{acquisitionLinks.map(({ rel, links }) => {
|
||||
|
||||
@@ -329,7 +329,16 @@ export default function BrowserPage() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const feed = JSON.parse(text);
|
||||
// Defense-in-depth: a non-feed response (e.g. an image returned for a
|
||||
// link that isn't actually a feed) is neither XML nor JSON. Surface a
|
||||
// clear message instead of letting a raw JSON.parse SyntaxError leak
|
||||
// into the error view (readest issue #4599).
|
||||
let feed;
|
||||
try {
|
||||
feed = JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error(_('Content is neither valid XML nor JSON'));
|
||||
}
|
||||
const newState = {
|
||||
feed,
|
||||
baseURL: responseURL,
|
||||
|
||||
+1
-1
Submodule packages/foliate-js updated: 1309db7e8c...03913b1575
Reference in New Issue
Block a user