Merge pull request #12439 from ToolJet/feat/table-markdown-mod

Markdown column added in table
This commit is contained in:
Johnson Cherian 2025-04-07 10:10:51 +05:30 committed by GitHub
commit 0907e07482
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 195 additions and 2 deletions

@ -1 +1 @@
Subproject commit d93ee7e1318f044ef2327671b8b257648071453d
Subproject commit 4b950ed3d0ba15edddf217936e9c9ae1ca3cf11a

View file

@ -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' },

View file

@ -128,6 +128,7 @@ export const StylesTabElements = ({
undefined,
'number',
'json',
'markdown',
'boolean',
'select',
'text',

View file

@ -631,6 +631,8 @@ class TableComponent extends React.Component {
return 'Multiselect';
case 'json':
return 'JSON';
case 'markdown':
return 'Markdown';
default:
capitalize(text ?? '');
}

View file

@ -0,0 +1,170 @@
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';
import useTextColor from '../DataTypes/_hooks/useTextColor';
import useTableStore from '../../_stores/tableStore';
import { getMaxHeight } from '../../_utils/helper';
import { shallow } from 'zustand/shallow';
import DOMPurify from 'dompurify';
export const MarkdownColumn = ({
id,
isEditable,
darkMode,
handleCellValueChange,
textColor,
cellValue,
column,
containerWidth,
cell,
horizontalAlignment,
cellSize,
}) => {
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);
const [isEditing, setIsEditing] = useState(false);
const cellStyles = {
color: cellTextColor ?? 'inherit',
};
const getCellValue = (value) => {
let transformedValue = value;
if (typeof value !== 'string') {
try {
transformedValue = String(value);
} catch (e) {
transformedValue = '';
}
}
return DOMPurify.sanitize(transformedValue.trim());
};
useEffect(() => {
if (!isEditable && isEditing) {
setIsEditing(false);
}
}, [isEditable]);
const getOverlay = () => {
return (
<div
className={`overlay-cell-table ${darkMode && 'dark-theme'}`}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{ color: 'var(--text-primary)' }}
>
<span
style={{
width: `${containerWidth}px`,
whiteSpace: 'pre-wrap',
}}
>
<ReactMarkdown>{getCellValue(cellValue)}</ReactMarkdown>
</span>
</div>
);
};
const renderEditable = () => {
const onChange = (e) => {
if (cellValue !== e.target.textContent) {
const value = e.target.textContent;
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
}
};
return (
<div
ref={ref}
contentEditable={'true'}
className={`h-100 text-container long-text-input d-flex${
darkMode ? ' textarea-dark-theme' : ''
} justify-content-${determineJustifyContentValue(horizontalAlignment)}`}
tabIndex={-1}
style={{
color: cellTextColor ? cellTextColor : 'inherit',
outline: 'none',
border: 'none',
background: 'inherit',
position: 'relative',
height: '100%',
flexDirection: 'column',
}}
readOnly={!isEditable}
onBlur={(e) => {
setIsEditing(false);
onChange(e);
}}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
ref.current.blur();
onChange(e);
}
}}
onFocus={(e) => {
setIsEditing(true);
e.stopPropagation();
}}
>
<div>{isEditing ? cellValue : <ReactMarkdown>{getCellValue(cellValue)}</ReactMarkdown>}</div>
</div>
);
};
const _showOverlay =
ref?.current &&
(ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth ||
ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight);
return (
<OverlayTrigger
placement="bottom"
overlay={_showOverlay ? getOverlay() : <div></div>}
trigger={_showOverlay && ['hover', 'focus']}
rootClose={true}
show={_showOverlay && hovered && !isEditing}
>
{!isEditable ? (
<div
className={`d-flex align-items-center h-100 w-100 justify-content-${determineJustifyContentValue(
horizontalAlignment
)}`}
style={cellStyles}
onMouseMove={() => {
if (!hovered) setHovered(true);
}}
onMouseLeave={() => {
setHovered(false);
}}
ref={ref}
>
<span
style={{
maxHeight: getMaxHeight(isMaxRowHeightAuto, maxRowHeightValue, cellSize),
whiteSpace: 'pre-wrap',
}}
>
<ReactMarkdown>{getCellValue(cellValue)}</ReactMarkdown>
</span>
</div>
) : (
<div className="h-100 d-flex flex-column justify-content-center position-relative">
<div
onMouseMove={() => {
if (!hovered) setHovered(true);
}}
onMouseLeave={() => setHovered(false)}
className={`${isEditing ? 'h-100 content-editing' : ''} h-100`}
>
{renderEditable()}
</div>
</div>
)}
</OverlayTrigger>
);
};

View file

@ -13,3 +13,4 @@ export { CustomDropdownColumn } from './CustomDropdown';
// export { SelectColumn } from './Select';
export { TextColumn } from './Text';
export { JsonColumn } from './JSON';
export { MarkdownColumn } from './Markdown';

View file

@ -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 (
<MarkdownColumn
isEditable={isEditable}
darkMode={darkMode}
handleCellValueChange={handleCellValueChange}
horizontalAlignment={column?.horizontalAlignment}
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
cellValue={cellValue}
column={column}
containerWidth={columnSize}
cell={cell}
id={id}
/>
);
}
default:
return cellValue || '';
}

@ -1 +1 @@
Subproject commit 1da04eef696345ce9f35d42af92e5d6de992cd85
Subproject commit 683647f83d3efeeadbe69c40b8e8dd5ba4e8ea06