From 6ce63910af9527abc685f4cc89c07a3c9c5d864c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jasikowski?= Date: Wed, 25 Sep 2024 14:03:29 +0200 Subject: [PATCH] Added attribute caching to img --- __tests__/ExpensiMark-HTML-test.js | 2 +- __tests__/ExpensiMark-Markdown-test.js | 18 ++++++++-- lib/ExpensiMark.ts | 48 +++++++++++++++++++------- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index 1417cc5a..488088d4 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -2102,7 +2102,7 @@ describe('Video markdown conversion to html tag', () => { const resultString = ''; expect(parser.replace(testString, { extras: { - videoAttributeCache: { + mediaAttributeCache: { 'https://example.com/video.mp4': 'data-expensify-height="100" data-expensify-width="100"' } } diff --git a/__tests__/ExpensiMark-Markdown-test.js b/__tests__/ExpensiMark-Markdown-test.js index 1ebfd21a..9199ca7d 100644 --- a/__tests__/ExpensiMark-Markdown-test.js +++ b/__tests__/ExpensiMark-Markdown-test.js @@ -867,6 +867,18 @@ describe('Image tag conversion to markdown', () => { const resultString = '![```code```](https://example.com/image.png)'; expect(parser.htmlToMarkdown(testString)).toBe(resultString); }); + + test('Cache extra attributes for img', () => { + const cacheMediaAttributes = jest.fn(); + const testString = 'altText'; + const resultString = '![altText](https://example.com/image.png)'; + const extras = { + cacheMediaAttributes, + }; + expect(parser.htmlToMarkdown(testString, extras)).toBe(resultString); + expect(cacheMediaAttributes).toHaveBeenCalledWith("https://example.com/image.png", 'data-expensify-width="100" data-expensify-height="500" data-name="newName" data-expensify-source="expensify-source"') + }); + }); describe('Video tag conversion to markdown', () => { @@ -883,14 +895,14 @@ describe('Video tag conversion to markdown', () => { }) test('While convert video, cache some extra attributes from the video tag', () => { - const cacheVideoAttributes = jest.fn(); + const cacheMediaAttributes = jest.fn(); const testString = ''; const resultString = '![video](https://example.com/video.mp4)'; const extras = { - cacheVideoAttributes, + cacheMediaAttributes, }; expect(parser.htmlToMarkdown(testString, extras)).toBe(resultString); - expect(cacheVideoAttributes).toHaveBeenCalledWith("https://example.com/video.mp4", ' data-expensify-width="100" data-expensify-height="500" data-expensify-thumbnail-url="https://image.com/img.jpg"') + expect(cacheMediaAttributes).toHaveBeenCalledWith("https://example.com/video.mp4", ' data-expensify-width="100" data-expensify-height="500" data-expensify-thumbnail-url="https://image.com/img.jpg"') }) }) diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index 0a17a763..b125b578 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -9,8 +9,8 @@ import * as Utils from './utils'; type Extras = { reportIDToName?: Record; accountIDToName?: Record; - cacheVideoAttributes?: (vidSource: string, attrs: string) => void; - videoAttributeCache?: Record; + cacheMediaAttributes?: (mediaSource: string, attrs: string) => void; + mediaAttributeCache?: Record; }; const EXTRAS_DEFAULT = {}; @@ -171,11 +171,11 @@ export default class ExpensiMark { * @return Returns the HTML video tag */ replacement: (extras, _match, videoName, videoSource) => { - const extraAttrs = extras && extras.videoAttributeCache && extras.videoAttributeCache[videoSource]; + const extraAttrs = extras && extras.mediaAttributeCache && extras.mediaAttributeCache[videoSource]; return ``; }, rawInputReplacement: (extras, _match, videoName, videoSource) => { - const extraAttrs = extras && extras.videoAttributeCache && extras.videoAttributeCache[videoSource]; + const extraAttrs = extras && extras.mediaAttributeCache && extras.mediaAttributeCache[videoSource]; return ``; }, }, @@ -658,13 +658,37 @@ export default class ExpensiMark { { name: 'image', - regex: /<]*src\s*=\s*(['"])(.*?)\1(?:[^><]*alt\s*=\s*(['"])(.*?)\3)?[^><]*>*(?![^<][\s\S]*?(<\/pre>|<\/code>))/gi, - replacement: (_extras, _match, _g1, g2, _g3, g4) => { - if (g4) { - return `![${g4}](${g2})`; + regex: /<]*src\s*=\s*(['"])(.*?)\1(.*?)>(?![^<][\s\S]*?(<\/pre>|<\/code>))/gi, + /** + * @param extras - The extras object + * @param match - The full match + * @param _g1 - The first capture group (the quote) + * @param imgSource - The second capture group - src attribute value + * @param imgAttrs - The third capture group - any attributes after src + * @returns The markdown image tag + */ + replacement: (extras, _match, _g1, imgSource, imgAttrs) => { + // Extract alt attribute from imgAttrs + let altText = ''; + const altRegex = /alt\s*=\s*(['"])(.*?)\1/i; + const altMatch = imgAttrs.match(altRegex); + let attributes = ''; + if (altMatch) { + altText = altMatch[2]; + // Remove the alt attribute from imgAttrs + attributes = imgAttrs.replace(altRegex, ''); } - - return `!(${g2})`; + // Remove trailing slash and extra whitespace + attributes = attributes.replace(/\s*\/\s*$/, '').trim(); + // Cache attributes without alt and trailing slash + if (imgAttrs && extras && extras.cacheMediaAttributes && typeof extras.cacheMediaAttributes === 'function') { + extras.cacheMediaAttributes(imgSource, attributes); + } + // Return the markdown image tag + if (altText) { + return `![${altText}](${imgSource})`; + } + return `!(${imgSource})`; }, }, @@ -681,8 +705,8 @@ export default class ExpensiMark { * @returns The markdown video tag */ replacement: (extras, _match, _g1, videoSource, videoAttrs, videoName) => { - if (videoAttrs && extras && extras.cacheVideoAttributes && typeof extras.cacheVideoAttributes === 'function') { - extras.cacheVideoAttributes(videoSource, videoAttrs); + if (videoAttrs && extras && extras.cacheMediaAttributes && typeof extras.cacheMediaAttributes === 'function') { + extras.cacheMediaAttributes(videoSource, videoAttrs); } if (videoName) { return `![${videoName}](${videoSource})`;