From 29349b6f976ae552a59a1d02f2ac5467bf17c4f8 Mon Sep 17 00:00:00 2001 From: calmplay <1354809038@qq.com> Date: Wed, 24 Jun 2026 20:43:51 +0800 Subject: [PATCH] Fix lazy-loaded image source extraction --- src/elements/images.ts | 20 ++++++++++++-------- src/removals/small-images.ts | 4 +++- tests/markdown.test.ts | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/elements/images.ts b/src/elements/images.ts index 57499009e..3044e9438 100644 --- a/src/elements/images.ts +++ b/src/elements/images.ts @@ -17,6 +17,7 @@ const urlPattern = /^([^\s]+)/; const absoluteUrlPattern = /^https?:\/\//; const filenamePattern = /^[\w\-\.\/\\]+\.(jpg|jpeg|png|gif|webp|svg)$/i; const datePattern = /^\d{4}-\d{2}-\d{2}$/; +const lazyImageSourceAttributes = ['data-src', 'data-original', 'data-lazy-src', 'data-actualsrc', 'data-backup']; export const imageRules = [ // Handle picture elements first to ensure we get the highest resolution @@ -142,7 +143,7 @@ export const imageRules = [ // Handle lazy-loaded images { - selector: 'img[data-src], img[data-srcset], img[loading="lazy"], img.lazy, img.lazyload, img[src^="data:image/svg+xml"]', + selector: 'img[data-src], img[data-original], img[data-lazy-src], img[data-actualsrc], img[data-backup], img[data-srcset], img[loading="lazy"], img.lazy, img.lazyload, img[src^="data:image/"]', element: 'img', transform: (el: Element, doc: Document): Element => { // Check for base64 placeholder images @@ -154,10 +155,13 @@ export const imageRules = [ el.removeAttribute('src'); } - // Handle data-src - const dataSrc = el.getAttribute('data-src'); - if (dataSrc && !el.getAttribute('src')) { - el.setAttribute('src', dataSrc); + // Handle common lazy image source attributes + for (const attrName of lazyImageSourceAttributes) { + const lazySrc = el.getAttribute(attrName); + if (lazySrc && !el.getAttribute('src')) { + el.setAttribute('src', lazySrc); + break; + } } // Handle data-srcset @@ -197,7 +201,7 @@ export const imageRules = [ // Remove lazy loading related classes and attributes el.classList.remove('lazy', 'lazyload'); el.removeAttribute('data-ll-status'); - el.removeAttribute('data-src'); + lazyImageSourceAttributes.forEach(attr => el.removeAttribute(attr)); el.removeAttribute('data-srcset'); el.removeAttribute('loading'); @@ -408,8 +412,8 @@ function isValidImageUrl(src: string): boolean { * Check if an element has better image sources than the current src */ function hasBetterImageSource(element: Element): boolean { - // Check for data-src or data-srcset - if (element.hasAttribute('data-src') || element.hasAttribute('data-srcset')) { + // Check for common lazy image source attributes + if (element.hasAttribute('data-srcset') || lazyImageSourceAttributes.some(attr => element.hasAttribute(attr))) { return true; } diff --git a/src/removals/small-images.ts b/src/removals/small-images.ts index 42f613484..5b7a08508 100644 --- a/src/removals/small-images.ts +++ b/src/removals/small-images.ts @@ -139,7 +139,9 @@ export function removeSmallImages(doc: Document, smallImages: Set, debug element.getAttribute('data-src') || element.getAttribute('data-srcset') || element.getAttribute('data-lazy-src') || - element.getAttribute('data-original'); + element.getAttribute('data-original') || + element.getAttribute('data-actualsrc') || + element.getAttribute('data-backup'); if (!src && !hasAltSrc) { element.remove(); removedCount++; diff --git a/tests/markdown.test.ts b/tests/markdown.test.ts index 9ac581bd1..ff4fe59da 100644 --- a/tests/markdown.test.ts +++ b/tests/markdown.test.ts @@ -92,6 +92,22 @@ describe('Markdown conversion', () => { }); }); + describe('lazy-loaded images', () => { + test.each([ + 'data-original', + 'data-lazy-src', + 'data-actualsrc', + 'data-backup', + ])('should promote %s when src is a placeholder', async (attribute) => { + const placeholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; + const html = `Test

Content

A
`; + const result = await Defuddle(parseDocument(html, 'https://example.com/articles/post'), 'https://example.com/articles/post', { separateMarkdown: true }); + + expect(result.contentMarkdown).toContain('![A](https://example.com/images/a.jpg)'); + expect(result.contentMarkdown).not.toContain('data:image'); + }); + }); + describe('wbr tag handling', () => { test('should remove wbr tags without adding spaces', async () => { const html = `Test

Supercalifragilistic

`;