mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 17:12:53 +03:00
Fix html entities and newline handling (#77)
* Fix html entities and newline handling This forces contenteditable to behave as single line input, and properly handles html entities. * Proposal without reacteditable * Fix tests and re-add focus styleé --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
parent
e19a85a5d0
commit
1c8a4058c3
2722
front/package-lock.json
generated
2722
front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,6 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"libphonenumber-js": "^1.10.26",
|
"libphonenumber-js": "^1.10.26",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-contenteditable": "^3.3.7",
|
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.4.4",
|
"react-router-dom": "^6.4.4",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import * as React from 'react';
|
import { ChangeEvent, useRef, useState } from 'react';
|
||||||
import ContentEditable, { ContentEditableEvent } from 'react-contenteditable';
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
content: string;
|
content: string;
|
||||||
@ -13,6 +12,11 @@ const StyledEditable = styled.div`
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:hover::before {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
::before {
|
::before {
|
||||||
content: '';
|
content: '';
|
||||||
@ -28,37 +32,51 @@ const StyledEditable = styled.div`
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:hover::before {
|
&:has(input:focus-within)::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
width: calc(100% + 2px);
|
||||||
|
height: calc(100% + 2px);
|
||||||
|
border: 1px solid ${(props) => props.theme.blue};
|
||||||
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
z-index: 1;
|
||||||
|
|
||||||
[contenteditable] {
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Container = styled.span`
|
const StyledInplaceInput = styled.input`
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
width: 100%;
|
||||||
padding-left: ${(props) => props.theme.spacing(2)};
|
padding-left: ${(props) => props.theme.spacing(2)};
|
||||||
|
padding-right: ${(props) => props.theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function escapeHTML(unsafeText: string): string {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.innerText = unsafeText;
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
function EditableCell({ content, changeHandler }: OwnProps) {
|
function EditableCell({ content, changeHandler }: OwnProps) {
|
||||||
const ref = React.createRef<HTMLElement>();
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [inputValue, setInputValue] = useState(content);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledEditable>
|
<StyledEditable
|
||||||
|
onClick={() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Container>
|
<Container>
|
||||||
<ContentEditable
|
<StyledInplaceInput
|
||||||
innerRef={ref}
|
ref={inputRef}
|
||||||
html={escapeHTML(content)}
|
value={inputValue}
|
||||||
disabled={false}
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
onChange={(e: ContentEditableEvent) => changeHandler(e.target.value)}
|
setInputValue(event.target.value);
|
||||||
tagName="span"
|
changeHandler(event.target.value);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</StyledEditable>
|
</StyledEditable>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { fireEvent, render } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
|
|
||||||
import { RegularEditableCell } from '../__stories__/EditableCell.stories';
|
import { RegularEditableCell } from '../__stories__/EditableCell.stories';
|
||||||
|
|
||||||
@ -8,10 +7,12 @@ it('Checks the EditableCell editing event bubbles up', async () => {
|
|||||||
const { getByTestId } = render(<RegularEditableCell changeHandler={func} />);
|
const { getByTestId } = render(<RegularEditableCell changeHandler={func} />);
|
||||||
|
|
||||||
const parent = getByTestId('content-editable-parent');
|
const parent = getByTestId('content-editable-parent');
|
||||||
expect(parent).not.toBeNull();
|
const editableInput = parent.querySelector('input');
|
||||||
const editable = parent.querySelector('[contenteditable]');
|
|
||||||
expect(editable).not.toBeNull();
|
if (!editableInput) {
|
||||||
editable && userEvent.click(editable);
|
throw new Error('Editable input not found');
|
||||||
userEvent.keyboard('a');
|
}
|
||||||
expect(func).toBeCalled();
|
|
||||||
|
fireEvent.change(editableInput, { target: { value: '23' } });
|
||||||
|
expect(func).toBeCalledWith('23');
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user