From f4d22b4ef92e39501ad7e21870a81aedb086d571 Mon Sep 17 00:00:00 2001 From: rlazarini <remi.lazarini@cea.fr> Date: Wed, 2 Oct 2024 14:50:13 +0200 Subject: [PATCH] [Ivette] addmarkdown component --- ivette/package.json | 1 + ivette/src/dome/renderer/text/markdown.tsx | 151 +++++++++++++++++++++ ivette/src/dome/renderer/text/style.css | 8 ++ ivette/src/dome/template/makefile.packages | 3 +- 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 ivette/src/dome/renderer/text/markdown.tsx diff --git a/ivette/package.json b/ivette/package.json index 369ffc11001..6978f96fcb8 100644 --- a/ivette/package.json +++ b/ivette/package.json @@ -52,6 +52,7 @@ "react-force-graph-2d": "^1.25.4", "react-force-graph-3d": "^1.24.2", "react-infinite-scroller": "^1.2.6", + "react-markdown": "9.0.1", "react-pivottable": "^0.11.0", "react-virtualized": "9.22.5", "react-virtualized-auto-sizer": "^1.0.22", diff --git a/ivette/src/dome/renderer/text/markdown.tsx b/ivette/src/dome/renderer/text/markdown.tsx new file mode 100644 index 00000000000..930f2a63fce --- /dev/null +++ b/ivette/src/dome/renderer/text/markdown.tsx @@ -0,0 +1,151 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2024 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import React from 'react'; +import ReactMarkdown, { Components, Options } from 'react-markdown'; + +import { classes } from 'dome/misc/utils'; +import { Icon } from 'dome/controls/icons'; + +export interface IReplacement { + regex: RegExp, + transform: (key: number, match?: RegExpExecArray) => JSX.Element | null +} + +export const iconTagReplacement: IReplacement = { + regex: /\[icon-([^\]]+)\]/g, + transform: (key: number, match?: RegExpExecArray) => { + return match ? <Icon key={key} id={match[1]}/> : null; + } +}; + +// -------------------------------------------------------------------------- +// --- Replacement function +// -------------------------------------------------------------------------- +/** + * Replace all tag in the text. + * This function doesn't replace any tags added by a previous replacement. + */ +function replaceTagsByElement( + text: string, + replacement?: IReplacement[] +): (string | JSX.Element | null)[] { + if(!replacement || replacement.length < 1) return [text]; + + const { regex, transform } = replacement[0]; + replacement.shift(); + + const newContent = []; + let match; + let lastIndex = 0; + while ((match = regex.exec(text)) !== null) { + if (match.index > lastIndex) { + const before = replaceTagsByElement( + text.slice(lastIndex, match.index), replacement + ); + before.forEach((elt) => newContent.push(elt)); + } + newContent.push(transform(Math.random(), match)); + lastIndex = regex.lastIndex; + } + if (lastIndex < text.length) { + const after = replaceTagsByElement(text.slice(lastIndex), replacement); + after.forEach((elt) => newContent.push(elt)); + } + return newContent; +} + +function replaceTags( + children: React.ReactNode, + replacement: IReplacement[] +): React.ReactNode { + const childrenTab = React.Children.toArray(children); + + const newContent = childrenTab.map((child) => { + if (typeof child === 'string') { + return replaceTagsByElement(child, replacement.slice()); + } + return child; + }); + + return newContent; +} + +// -------------------------------------------------------------------------- +// --- 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 */ + replacement?: IReplacement[]; + /** Children */ + children?: string | null; +} + +export function Markdown( + props: MarkdownProps +): JSX.Element { + const { className, replacement, htmlTag, children } = props; + const markdownClasses = classes( + "dome-xMarkdown", "dome-pages", + className, + ); + + const transformChildren = (c: React.ReactNode): React.ReactNode => { + return !replacement ? c : replaceTags(c, replacement); + }; + + 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() + }; + + return <ReactMarkdown {...options}>{ children }</ReactMarkdown>; +} + +/* -------------------------------------------------------------------------- */ diff --git a/ivette/src/dome/renderer/text/style.css b/ivette/src/dome/renderer/text/style.css index ded51521f57..b3a5e8e8d93 100644 --- a/ivette/src/dome/renderer/text/style.css +++ b/ivette/src/dome/renderer/text/style.css @@ -213,3 +213,11 @@ .cm-line.cm-activeLine, .cm-active-line { background-color: var(--background); } /* -------------------------------------------------------------------------- */ +/* --- Markdown --- */ +/* -------------------------------------------------------------------------- */ + +.dome-xMarkdown { + &>* { padding: 1px; } +} + +/* -------------------------------------------------------------------------- */ diff --git a/ivette/src/dome/template/makefile.packages b/ivette/src/dome/template/makefile.packages index ba3fa7df3f6..5e9e6750694 100644 --- a/ivette/src/dome/template/makefile.packages +++ b/ivette/src/dome/template/makefile.packages @@ -58,6 +58,7 @@ DOME_APP_PACKAGES= \ react-force-graph-2d@^1.25.4 \ react-force-graph-3d@^1.24.2 \ d3-selection@^3 \ - d3-graphviz@^5 + d3-graphviz@^5 \ + react-markdown@9.0.1 \ # -------------------------------------------------------------------------- -- GitLab