From 15cc6bddc66c1f59c654fd73dc41328c88d6bd79 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 27 Mar 2025 00:51:40 +0530 Subject: [PATCH 1/4] Markdown column added in table --- .../ColumnManager/PropertiesTabElements.jsx | 1 + .../Table/ColumnManager/StylesTabElements.jsx | 1 + .../Inspector/Components/Table/Table.jsx | 2 + .../src/AppBuilder/Widgets/Table/Markdown.jsx | 166 ++++++++++++++++++ .../Widgets/Table/columns/index.jsx | 20 +++ 5 files changed, 190 insertions(+) create mode 100644 frontend/src/AppBuilder/Widgets/Table/Markdown.jsx diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx index 2e975109d2..0568b85ccd 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx @@ -53,6 +53,7 @@ export const PropertiesTabElements = ({ { label: 'Image', value: 'image' }, { label: 'Link', value: 'link' }, { label: 'JSON', value: 'json' }, + { label: 'Markdown', value: 'markdown' }, // Following column types are deprecated { label: 'Default', value: 'default' }, { label: 'Dropdown', value: 'dropdown' }, diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx index 5091e56c6c..2b91a8dd15 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx @@ -128,6 +128,7 @@ export const StylesTabElements = ({ undefined, 'number', 'json', + 'markdown', 'boolean', 'select', 'text', diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx index 3aca83b046..bb6c1d94bb 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx @@ -631,6 +631,8 @@ class TableComponent extends React.Component { return 'Multiselect'; case 'json': return 'JSON'; + case 'markdown': + return 'Markdown'; default: capitalize(text ?? ''); } diff --git a/frontend/src/AppBuilder/Widgets/Table/Markdown.jsx b/frontend/src/AppBuilder/Widgets/Table/Markdown.jsx new file mode 100644 index 0000000000..abd121182a --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/Table/Markdown.jsx @@ -0,0 +1,166 @@ +import React, { useState, useEffect } from 'react'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; +import { determineJustifyContentValue } from '@/_helpers/utils'; +import { default as ReactMarkdown } from 'react-markdown'; +import DOMPurify from 'dompurify'; + +const Markdown = ({ + isEditable, + darkMode, + handleCellValueChange, + cellTextColor, + cellValue, + column, + containerWidth, + cell, + horizontalAlignment, + isMaxRowHeightAuto, + cellSize, + maxRowHeightValue, +}) => { + const ref = React.useRef(null); + + const [hovered, setHovered] = useState(false); + const [isEditing, setIsEditing] = useState(false); + + const cellStyles = { + color: cellTextColor ?? 'inherit', + }; + + const getCellValue = (value) => { + return DOMPurify.sanitize(value); + }; + + useEffect(() => { + if (!isEditable && isEditing) { + setIsEditing(false); + } + }, [isEditable]); + + const getOverlay = () => { + return ( +
setHovered(true)} + onMouseLeave={() => setHovered(false)} + style={{ color: 'var(--text-primary)' }} + > + + {getCellValue(cellValue)} + +
+ ); + }; + + const renderEditable = () => { + const onChange = (e) => { + if (cellValue !== e.target.textContent) { + const value = e.target.textContent.replace(/\n/g, ''); + handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original); + } + }; + + return ( +
{ + setIsEditing(false); + onChange(e); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + ref.current.blur(); + onChange(e); + } + }} + onFocus={(e) => { + setIsEditing(true); + e.stopPropagation(); + }} + > + {isEditing ? cellValue : {getCellValue(cellValue)}} +
+ ); + }; + + const _showOverlay = + ref?.current && + (ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth || + ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight); + + return ( + <> + } + trigger={_showOverlay && ['hover', 'focus']} + rootClose={true} + show={_showOverlay && hovered && !isEditing} + > + {!isEditable ? ( +
{ + if (!hovered) setHovered(true); + }} + onMouseLeave={() => { + setHovered(false); + }} + ref={ref} + > + + {getCellValue(cellValue)} + +
+ ) : ( +
+
{ + if (!hovered) setHovered(true); + }} + onMouseLeave={() => setHovered(false)} + className={`${isEditing ? 'h-100 content-editing' : ''} h-100`} + > + {renderEditable()} +
+
+ )} +
+ + ); +}; + +export default Markdown; diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx index 257de7c4ae..6e56e6e6ef 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx @@ -15,6 +15,7 @@ import SolidIcon from '@/_ui/Icon/SolidIcons'; import Text from '../Text'; import StringColumn from '../String'; import Json from '../Json'; +import Markdown from '../Markdown'; export default function generateColumnsData({ columnProperties, @@ -736,6 +737,25 @@ export default function generateColumnsData({ /> ); } + case 'markdown': { + return ( + + ); + } } return cellValue || ''; }, From 6add2b622ad625c8c6ebafa8f00b6d0f88ec57f0 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 3 Apr 2025 00:56:00 +0530 Subject: [PATCH 2/4] Moved to modularisation along with fixes for new line --- frontend/ee | 2 +- .../_components/DataTypes}/Markdown.jsx | 53 +++++++++++-------- .../NewTable/_components/DataTypes/index.js | 1 + .../NewTable/_utils/generateColumnsData.js | 18 +++++++ .../Widgets/Table/columns/index.jsx | 20 ------- server/ee | 2 +- 6 files changed, 53 insertions(+), 43 deletions(-) rename frontend/src/AppBuilder/Widgets/{Table => NewTable/_components/DataTypes}/Markdown.jsx (72%) diff --git a/frontend/ee b/frontend/ee index 715a830c7a..4b950ed3d0 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 715a830c7a8d75efc7f77106292d9e4499005b69 +Subproject commit 4b950ed3d0ba15edddf217936e9c9ae1ca3cf11a diff --git a/frontend/src/AppBuilder/Widgets/Table/Markdown.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx similarity index 72% rename from frontend/src/AppBuilder/Widgets/Table/Markdown.jsx rename to frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx index abd121182a..fff48c5160 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Markdown.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx @@ -2,24 +2,30 @@ import React, { useState, useEffect } from 'react'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import { determineJustifyContentValue } from '@/_helpers/utils'; import { default as ReactMarkdown } from 'react-markdown'; +import useTextColor from '../DataTypes/_hooks/useTextColor'; +import useTableStore from '../../_stores/tableStore'; +import { getMaxHeight } from '../../_utils/helper'; +import { shallow } from 'zustand/shallow'; +import remarkBreaks from 'remark-breaks'; import DOMPurify from 'dompurify'; -const Markdown = ({ +export const MarkdownColumn = ({ + id, isEditable, darkMode, handleCellValueChange, - cellTextColor, + textColor, cellValue, column, containerWidth, cell, horizontalAlignment, - isMaxRowHeightAuto, cellSize, - maxRowHeightValue, }) => { const ref = React.useRef(null); - + const cellTextColor = useTextColor(id, textColor, 'json'); + const isMaxRowHeightAuto = useTableStore((state) => state.getTableStyles(id)?.isMaxRowHeightAuto, shallow); + const maxRowHeightValue = useTableStore((state) => state.getTableStyles(id)?.maxRowHeightValue, shallow); const [hovered, setHovered] = useState(false); const [isEditing, setIsEditing] = useState(false); @@ -28,7 +34,15 @@ const Markdown = ({ }; const getCellValue = (value) => { - return DOMPurify.sanitize(value); + let transformedValue = value; + if (typeof value !== 'string') { + try { + transformedValue = String(value); + } catch (e) { + transformedValue = ''; + } + } + return DOMPurify.sanitize(transformedValue.trim()); }; useEffect(() => { @@ -51,7 +65,7 @@ const Markdown = ({ whiteSpace: 'pre-wrap', }} > - {getCellValue(cellValue)} + {getCellValue(cellValue)} ); @@ -60,7 +74,7 @@ const Markdown = ({ const renderEditable = () => { const onChange = (e) => { if (cellValue !== e.target.textContent) { - const value = e.target.textContent.replace(/\n/g, ''); + const value = e.target.textContent; handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original); } }; @@ -69,7 +83,7 @@ const Markdown = ({
{ @@ -87,7 +102,7 @@ const Markdown = ({ onChange(e); }} onKeyDown={(e) => { - if (e.key === 'Enter') { + if (e.key === 'Enter' && !e.shiftKey) { ref.current.blur(); onChange(e); } @@ -97,7 +112,11 @@ const Markdown = ({ e.stopPropagation(); }} > - {isEditing ? cellValue : {getCellValue(cellValue)}} + {isEditing ? ( + cellValue + ) : ( + {getCellValue(cellValue)} + )}
); }; @@ -132,17 +151,11 @@ const Markdown = ({ > - {getCellValue(cellValue)} + {getCellValue(cellValue)} ) : ( @@ -162,5 +175,3 @@ const Markdown = ({ ); }; - -export default Markdown; diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/index.js b/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/index.js index 0801f2a2dc..ee2fb701a0 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/index.js +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/index.js @@ -13,3 +13,4 @@ export { CustomDropdownColumn } from './CustomDropdown'; // export { SelectColumn } from './Select'; export { TextColumn } from './Text'; export { JsonColumn } from './JSON'; +export { MarkdownColumn } from './Markdown'; diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateColumnsData.js b/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateColumnsData.js index 45e4b8be15..817493a4d7 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateColumnsData.js +++ b/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateColumnsData.js @@ -19,6 +19,7 @@ import { CustomDropdownColumn, TextColumn, JsonColumn, + MarkdownColumn, } from '../_components/DataTypes'; import useTableStore from '../_stores/tableStore'; @@ -339,6 +340,23 @@ export default function generateColumnsData({ /> ); + case 'markdown': { + return ( + + ); + } + default: return cellValue || ''; } diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx index 6e56e6e6ef..257de7c4ae 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx @@ -15,7 +15,6 @@ import SolidIcon from '@/_ui/Icon/SolidIcons'; import Text from '../Text'; import StringColumn from '../String'; import Json from '../Json'; -import Markdown from '../Markdown'; export default function generateColumnsData({ columnProperties, @@ -737,25 +736,6 @@ export default function generateColumnsData({ /> ); } - case 'markdown': { - return ( - - ); - } } return cellValue || ''; }, diff --git a/server/ee b/server/ee index 0eefbb71a1..683647f83d 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 0eefbb71a1d5288f49641af5efaaab25970f27d1 +Subproject commit 683647f83d3efeeadbe69c40b8e8dd5ba4e8ea06 From 2daeb10a23ad27452a61bcb77a27bcef256f66ab Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 3 Apr 2025 01:34:52 +0530 Subject: [PATCH 3/4] Hover related fixes --- .../NewTable/_components/DataTypes/Markdown.jsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx index fff48c5160..0806f1df2e 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx @@ -6,7 +6,6 @@ import useTextColor from '../DataTypes/_hooks/useTextColor'; import useTableStore from '../../_stores/tableStore'; import { getMaxHeight } from '../../_utils/helper'; import { shallow } from 'zustand/shallow'; -import remarkBreaks from 'remark-breaks'; import DOMPurify from 'dompurify'; export const MarkdownColumn = ({ @@ -65,7 +64,7 @@ export const MarkdownColumn = ({ whiteSpace: 'pre-wrap', }} > - {getCellValue(cellValue)} + {getCellValue(cellValue)} ); @@ -112,11 +111,7 @@ export const MarkdownColumn = ({ e.stopPropagation(); }} > - {isEditing ? ( - cellValue - ) : ( - {getCellValue(cellValue)} - )} +
{isEditing ? cellValue : {getCellValue(cellValue)}}
); }; @@ -155,7 +150,7 @@ export const MarkdownColumn = ({ whiteSpace: 'pre-wrap', }} > - {getCellValue(cellValue)} + {getCellValue(cellValue)} ) : ( From 60df57e9d9c22d1e0dfc9fbc07d1358c635f139f Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Mon, 7 Apr 2025 10:06:12 +0530 Subject: [PATCH 4/4] Comments resolved --- .../_components/DataTypes/Markdown.jsx | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx index 0806f1df2e..a8bb593409 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/DataTypes/Markdown.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import { determineJustifyContentValue } from '@/_helpers/utils'; import { default as ReactMarkdown } from 'react-markdown'; @@ -21,8 +21,8 @@ export const MarkdownColumn = ({ horizontalAlignment, cellSize, }) => { - const ref = React.useRef(null); - const cellTextColor = useTextColor(id, textColor, 'json'); + const ref = useRef(null); + const cellTextColor = useTextColor(id, textColor); const isMaxRowHeightAuto = useTableStore((state) => state.getTableStyles(id)?.isMaxRowHeightAuto, shallow); const maxRowHeightValue = useTableStore((state) => state.getTableStyles(id)?.maxRowHeightValue, shallow); const [hovered, setHovered] = useState(false); @@ -122,51 +122,49 @@ export const MarkdownColumn = ({ ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight); return ( - <> - } - trigger={_showOverlay && ['hover', 'focus']} - rootClose={true} - show={_showOverlay && hovered && !isEditing} - > - {!isEditable ? ( + } + trigger={_showOverlay && ['hover', 'focus']} + rootClose={true} + show={_showOverlay && hovered && !isEditing} + > + {!isEditable ? ( +
{ + if (!hovered) setHovered(true); + }} + onMouseLeave={() => { + setHovered(false); + }} + ref={ref} + > + + {getCellValue(cellValue)} + +
+ ) : ( +
{ if (!hovered) setHovered(true); }} - onMouseLeave={() => { - setHovered(false); - }} - ref={ref} + onMouseLeave={() => setHovered(false)} + className={`${isEditing ? 'h-100 content-editing' : ''} h-100`} > - - {getCellValue(cellValue)} - + {renderEditable()}
- ) : ( -
-
{ - if (!hovered) setHovered(true); - }} - onMouseLeave={() => setHovered(false)} - className={`${isEditing ? 'h-100 content-editing' : ''} h-100`} - > - {renderEditable()} -
-
- )} - - +
+ )} +
); };