mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 09:02:11 +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",
|
||||
"libphonenumber-js": "^1.10.26",
|
||||
"react": "^18.2.0",
|
||||
"react-contenteditable": "^3.3.7",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.4.4",
|
||||
"web-vitals": "^2.1.4"
|
||||
|
@ -1,6 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
import * as React from 'react';
|
||||
import ContentEditable, { ContentEditableEvent } from 'react-contenteditable';
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
|
||||
type OwnProps = {
|
||||
content: string;
|
||||
@ -13,6 +12,11 @@ const StyledEditable = styled.div`
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
:hover::before {
|
||||
display: block;
|
||||
}
|
||||
|
||||
::before {
|
||||
content: '';
|
||||
@ -28,37 +32,51 @@ const StyledEditable = styled.div`
|
||||
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;
|
||||
}
|
||||
|
||||
[contenteditable] {
|
||||
outline: none;
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
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-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) {
|
||||
const ref = React.createRef<HTMLElement>();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputValue, setInputValue] = useState(content);
|
||||
|
||||
return (
|
||||
<StyledEditable>
|
||||
<StyledEditable
|
||||
onClick={() => {
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<ContentEditable
|
||||
innerRef={ref}
|
||||
html={escapeHTML(content)}
|
||||
disabled={false}
|
||||
onChange={(e: ContentEditableEvent) => changeHandler(e.target.value)}
|
||||
tagName="span"
|
||||
<StyledInplaceInput
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(event.target.value);
|
||||
changeHandler(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</StyledEditable>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
|
||||
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 parent = getByTestId('content-editable-parent');
|
||||
expect(parent).not.toBeNull();
|
||||
const editable = parent.querySelector('[contenteditable]');
|
||||
expect(editable).not.toBeNull();
|
||||
editable && userEvent.click(editable);
|
||||
userEvent.keyboard('a');
|
||||
expect(func).toBeCalled();
|
||||
const editableInput = parent.querySelector('input');
|
||||
|
||||
if (!editableInput) {
|
||||
throw new Error('Editable input not found');
|
||||
}
|
||||
|
||||
fireEvent.change(editableInput, { target: { value: '23' } });
|
||||
expect(func).toBeCalledWith('23');
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user