From 442b58b0e7d02034583076150244be38a68e8ab7 Mon Sep 17 00:00:00 2001
From: Karan Hotchandani <33024356+karanh37@users.noreply.github.com>
Date: Tue, 11 Feb 2025 12:34:57 +0530
Subject: [PATCH] Markdown editor fix (#19732)
* fix block editor escaping html elements
* add unit tests
---
.../ui/src/utils/BlockEditorUtils.test.ts | 57 +++++++++++++++++++
.../ui/src/utils/BlockEditorUtils.ts | 32 ++++++++++-
2 files changed, 87 insertions(+), 2 deletions(-)
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;
}