diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/BlockEditorUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/BlockEditorUtils.test.ts index 13c485d7a3a..0deadd071a7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/BlockEditorUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/BlockEditorUtils.test.ts @@ -14,6 +14,7 @@ import { formatValueBasedOnContent, getHtmlStringFromMarkdownString, getTextFromHtmlString, + isHTMLString, } from './BlockEditorUtils'; describe('getTextFromHtmlString', () => { @@ -132,3 +133,59 @@ describe('formatValueBasedOnContent', () => { expect(formatValueBasedOnContent(input)).toBe(''); }); }); + +describe('isHTMLString', () => { + it('should return true for simple HTML content', () => { + const htmlContent = '

This is a paragraph

'; + + expect(isHTMLString(htmlContent)).toBe(true); + }); + + it('should return true for complex HTML content', () => { + const htmlContent = ` +
+

Title

+

This is a bold paragraph with link

+ +
+ `; + + expect(isHTMLString(htmlContent)).toBe(true); + }); + + it('should return false for markdown content', () => { + const markdownContent = ` + *** +### Data Sharing Policies +If there is any question or concern regarding the data sharing policies, +please contact the support team . +*** + `; + + expect(isHTMLString(markdownContent)).toBe(false); + }); + + it('should return false for plain text', () => { + const plainText = 'This is just plain text without any formatting'; + + expect(isHTMLString(plainText)).toBe(false); + }); + + it('should return false for empty string', () => { + expect(isHTMLString('')).toBe(false); + }); + + it('should return false when content has both HTML and markdown', () => { + const mixedContent = ` +
+ # Markdown Header + * List item +
+ `; + + expect(isHTMLString(mixedContent)).toBe(true); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/BlockEditorUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/BlockEditorUtils.ts index 4603c72419e..26b747c00d1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/BlockEditorUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/BlockEditorUtils.ts @@ -122,12 +122,40 @@ export const formatValueBasedOnContent = (value: string) => value === '

' ? '' : value; export const isHTMLString = (content: string) => { + // Quick check for common HTML tags + const commonHtmlTags = + /<(p|div|span|a|ul|ol|li|h[1-6]|br|strong|em|code|pre)[>\s]/i; + + // If content doesn't have any HTML-like structure, return false early + if (!commonHtmlTags.test(content)) { + return false; + } + try { const parser = new DOMParser(); const parsedDocument = parser.parseFromString(content, 'text/html'); - // since text can be also counted as child node so we will check if length is greater than 1 - return parsedDocument.body.childNodes.length > 1; + // Check if there are any actual HTML elements (not just text nodes) + const hasHtmlElements = Array.from(parsedDocument.body.childNodes).some( + (node) => node.nodeType === Node.ELEMENT_NODE + ); + + // Check if the content has markdown-specific patterns + const markdownPatterns = [ + /^#{1,6}\s/, // Headers + /^\s*[-*+]\s/, // Lists + /^\s*\d+\.\s/, // Numbered lists + /^\s*>{1,}\s/, // Blockquotes + /^---|\*\*\*|___/, // Horizontal rules + /`{1,3}[^`]+`{1,3}/, // Code blocks + ]; + + const hasMarkdownSyntax = markdownPatterns.some((pattern) => + pattern.test(content) + ); + + // If it has markdown syntax but also parsed as HTML, prefer markdown interpretation + return hasHtmlElements && !hasMarkdownSyntax; } catch (e) { return false; }