From 9f0bba1bee75f71e4d2b3936a0593f97d991ce31 Mon Sep 17 00:00:00 2001
From: rlazarini <remi.lazarini@cea.fr>
Date: Mon, 9 Dec 2024 09:36:09 +0100
Subject: [PATCH] [Ivette] markdown : refacto component

---
 ivette/src/dome/renderer/text/markdown.tsx | 127 +++++++--------------
 1 file changed, 43 insertions(+), 84 deletions(-)

diff --git a/ivette/src/dome/renderer/text/markdown.tsx b/ivette/src/dome/renderer/text/markdown.tsx
index ba06ca42828..ca97fcc4159 100644
--- a/ivette/src/dome/renderer/text/markdown.tsx
+++ b/ivette/src/dome/renderer/text/markdown.tsx
@@ -21,7 +21,7 @@
 /* ************************************************************************ */
 
 import React from 'react';
-import ReactMarkdown, { Components, Options } from 'react-markdown';
+import ReactMarkdown, { Options } from 'react-markdown';
 
 import { classes } from 'dome/misc/utils';
 import { Icon } from 'dome/controls/icons';
@@ -38,82 +38,67 @@ export const iconTag: Pattern = {
   }
 };
 
-class Counter {
-  private val: number = 0;
-  increment(): number { return this.val++; }
-}
-
 // --------------------------------------------------------------------------
 // --- Replacement function
 // --------------------------------------------------------------------------
+
 /**
- * Replace all tag in the text.
+ * Replace all tag in children.
  * This function doesn't replace any tags added by a previous replacement.
  */
-function replaceTagsByElement(
-  text: string,
-  counter: Counter,
-  patterns?: Pattern[],
-): (string | JSX.Element | null)[] {
-  if(!patterns || patterns.length < 1) return [text];
-  type Content = string | JSX.Element | null;
-  let newContent: (Content|Content[])[] = [text];
-  let match;
-  let lastIndex: number;
-
-  patterns.forEach(({ pattern, replace }) => {
-    newContent = newContent.flat();
-    newContent.slice().forEach((content, i) => {
-      if(typeof content === "string") {
-        const contentTab: (string | JSX.Element | null)[] = [];
-        lastIndex = 0;
-        while ((match = pattern.exec(content)) !== null) {
-          if (match.index > lastIndex) {
-            contentTab.push(content.slice(lastIndex, match.index));
-          }
-          contentTab.push(replace(counter.increment(), match));
-          lastIndex = pattern.lastIndex;
-        }
-        if (lastIndex < content.length) {
-          contentTab.push(content.slice(lastIndex));
-        }
-        newContent.splice(i, 1, contentTab);
-      }
-    });
-  });
-
-  return newContent.flat();
-}
-
 function replaceTags(
   children: React.ReactNode,
   patterns: Pattern[],
-  counter: Counter
 ): React.ReactNode {
+  if(patterns.length < 1) return children;
+
+  const buffer: React.ReactNode[] = [];
+  let counter = 0;
   const childrenTab = React.Children.toArray(children);
 
-  const newContent = childrenTab.map((child) => {
+  const makeReplace = (text: string, index: number): void => {
+    if(index >= patterns.length) {
+      buffer.push(text);
+      return;
+    }
+    const { pattern, replace } = patterns[index];
+
+    let match;
+    let lastIndex = 0;
+    while ((match = pattern.exec(text)) !== null) {
+      if (match.index > lastIndex) {
+        makeReplace(text.slice(lastIndex, match.index), index+1);
+      }
+      buffer.push(replace(counter++, match));
+      lastIndex = pattern.lastIndex;
+    }
+    if (lastIndex < text.length) {
+      makeReplace(text.slice(lastIndex), index+1);
+    }
+  };
+
+  /** makeReplace is applied if child is a string,
+   * otherwise it is pushed into the buffer
+   */
+  childrenTab.forEach((child) => {
     if (typeof child === 'string') {
-      return replaceTagsByElement(child, counter, patterns.slice());
+      return makeReplace(child, 0);
     }
-    return child;
+    return buffer.push(child);
   });
 
-  return newContent;
+  return buffer;
 }
 
 // --------------------------------------------------------------------------
 // --- Markdown component
 // --------------------------------------------------------------------------
-type tagHtmlList = [ k: keyof Components, v: keyof Components ][]
 
 interface MarkdownProps {
   /** classes for Markdown component */
   className?: string;
-  /** html tag of the markdown to be processed and possible replacement */
-  htmlTag?: tagHtmlList;
-  /** Tab of tag replacement */
-  pattern?: Pattern[];
+  /** Tab of patterns */
+  patterns?: Pattern[];
   /** Children */
   children?: string | null;
 }
@@ -121,41 +106,15 @@ interface MarkdownProps {
 export function Markdown(
   props: MarkdownProps
 ): JSX.Element {
-  const { className, pattern, htmlTag, children } = props;
+  const { className, patterns, children } = props;
   const markdownClasses = classes(
-    "dome-xMarkdown", "dome-pages",
-    className,
+    "dome-xMarkdown", "dome-pages", className
   );
 
-  const counter = new Counter();
-  const transformChildren = (c: React.ReactNode): React.ReactNode => {
-    return !pattern ? c : replaceTags(c, pattern, counter);
-  };
-
-  const getComponentsOption = (): Components | null => {
-    if(!htmlTag) return null;
-
-    const getDynamicElement = (
-      tagName: keyof JSX.IntrinsicElements,
-      children: React.ReactNode
-    ): JSX.Element => {
-      const Tag = tagName;
-      return <Tag>{children}</Tag>;
-    };
-
-    const component = [];
-    for (const [key, val] of htmlTag) {
-      component.push([key, ({ children }: {
-        children: React.ReactNode;
-      }) => getDynamicElement(val, transformChildren(children))]);
-    }
-
-    return Object.fromEntries(component);
-  };
-
-  const options: Options = {
-    className: markdownClasses,
-    components: getComponentsOption()
+  const options: Options = { className: markdownClasses };
+  if(patterns && patterns.length > 0) options.components = {
+    p: ({ children }) => <div>{replaceTags(children, patterns)}</div>,
+    li: ({ children }) => <li>{replaceTags(children, patterns)}</li>
   };
 
   return <ReactMarkdown {...options}>{ children }</ReactMarkdown>;
-- 
GitLab