Skip to content

Commit

Permalink
Merge pull request #1690 from glific/fix/draft-js-formatting
Browse files Browse the repository at this point in the history
Fixed draft js formatting issues
  • Loading branch information
mdshamoon authored Oct 8, 2021
2 parents 3a8acd8 + 02e3445 commit cd83adb
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 143 deletions.
65 changes: 11 additions & 54 deletions src/common/RichEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React from 'react';
import reactStringReplace from 'react-string-replace';
import { convertToRaw, convertFromRaw } from 'draft-js';
import { EditorState, ContentState } from 'draft-js';
import CallIcon from '@material-ui/icons/Call';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';

const MarkDownConvertor = require('markdown-draft-js');

// Indicates how to replace different parts of the text from WhatsApp to HTML.
const regexForLink =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/gi;
Expand All @@ -14,81 +12,40 @@ export const TextReplacements: any = [
bold: {
char: '*',
tag: 'b',
replace: (text: string) => <b key={text}>{text}</b>,
replace: (text: string) => <b key={text}>{text.slice(1, text.length - 1)}</b>,
},
},
{
italics: {
char: '_',
tag: 'i',
replace: (text: string) => <i>{text}</i>,
replace: (text: string) => <i>{text.slice(1, text.length - 1)}</i>,
},
},
{
strikethrough: {
char: '~',
tag: 's',
replace: (text: string) => <s>{text}</s>,
replace: (text: string) => <s>{text.slice(1, text.length - 1)}</s>,
},
},
{
codeBlock: {
char: '``',
char: '`',
tag: 'code',
replace: (text: string) => <code>{text}</code>,
replace: (text: string) => <code>{text.slice(1, text.length - 1)}</code>,
},
},
];

// Finds double asterisks in text with a regular expression.
const textConversion = (text: any, style: any, offset: number, symbol: string) => {
const initialOffset = style.offset + offset;
const finalOffset = initialOffset + style.length + 1;
let modifiedText = text.slice(0, initialOffset) + symbol + text.slice(initialOffset);
modifiedText = modifiedText.slice(0, finalOffset) + symbol + modifiedText.slice(finalOffset);
return modifiedText;
};

// Convert Draft.js to WhatsApp message format.
export const convertToWhatsApp = (editorState: any) => {
const markdownString: any = convertToRaw(editorState.getCurrentContent());
let finalString: any = [];

finalString = markdownString.blocks.map((block: any) => {
const { text } = block;
let offset = 0;
let convertedText = text;
block.inlineStyleRanges.forEach((style: any) => {
switch (style.style) {
case 'BOLD':
convertedText = textConversion(convertedText, style, offset, '*');
break;
case 'ITALIC':
convertedText = textConversion(convertedText, style, offset, '_');
break;
default:
}
offset += 2;
});
return `${finalString}${convertedText}\n`;
});
export const getPlainTextFromEditor = (editorState: any) =>
editorState.getCurrentContent().getPlainText();

// let's return 0 element as map() always returns an array
return finalString.join('');
};

// Converts WhatsApp message formatting into HTML elements.
export const WhatsAppToDraftEditor = (text: string) => {
const regexforBold = /[*][^*]*[*]/gi;

const addedBold = text && text.replace(regexforBold, (str: any) => `*${str}*`);

const rawData = MarkDownConvertor.markdownToDraft(addedBold, {
preserveNewlines: true,
});
const contentState = convertFromRaw(rawData);
return contentState;
};
export const getEditorFromContent = (text: string) =>
EditorState.createWithContent(ContentState.createFromText(text));

export const WhatsAppToJsx = (text: any) => {
const replacements = TextReplacements;
Expand Down Expand Up @@ -134,7 +91,7 @@ export const WhatsAppToJsx = (text: any) => {
const type = Object.keys(replacement)[0];
const character: any = replacement[type].char;
const replaceFunc: any = replacement[type].replace;
const regexStr = `\\${character}{${character.length}}(.+?)\\${character}{${character.length}}`;
const regexStr = `(\\${character}{${character.length}}[^${character}\\s][^${character}]*[^${character}\\s]\\${character}{${character.length}})`;
modifiedText = reactStringReplace(modifiedText, new RegExp(regexStr, 'g'), (match: any) =>
replaceFunc(match)
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/UI/Form/EmojiInput/EmojiInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const wrapper = (
values: { input: EditorState.createEmpty() },
setFieldValue: setFieldValueMock,
}}
field={{ name: 'input', value: '', onChange: jest.fn() }}
field={{ name: 'input', value: EditorState.createEmpty(), onChange: jest.fn() }}
placeholder="Title"
/>
);
Expand Down
67 changes: 41 additions & 26 deletions src/components/UI/Form/EmojiInput/EmojiInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Picker } from 'emoji-mart';
import 'emoji-mart/css/emoji-mart.css';
import { useTranslation } from 'react-i18next';

import { convertToWhatsApp } from 'common/RichEditor';
import { getPlainTextFromEditor } from 'common/RichEditor';
import { Input } from '../Input/Input';
import Styles from './EmojiInput.module.css';

Expand Down Expand Up @@ -77,7 +77,7 @@ const DraftField = (inputProps: any) => {
};

export const EmojiInput: React.FC<EmojiInputProps> = ({
field: { onChange, ...rest },
field: { value, name, onBlur },
handleChange,
getEditorValue,
handleBlur,
Expand All @@ -86,39 +86,52 @@ export const EmojiInput: React.FC<EmojiInputProps> = ({
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const { t } = useTranslation();

const updateValue = (input: any, isEmoji = false) => {
const editorContentState = value.getCurrentContent();
const editorSelectionState: any = value.getSelection();
const ModifiedContent = Modifier.replaceText(
editorContentState,
editorSelectionState,
isEmoji ? input.native : input
);
let updatedEditorState = EditorState.push(value, ModifiedContent, 'insert-characters');
if (!isEmoji) {
const editorSelectionStateMod = updatedEditorState.getSelection();
const updatedSelection = editorSelectionStateMod.merge({
anchorOffset: editorSelectionStateMod.getAnchorOffset() - 1,
focusOffset: editorSelectionStateMod.getFocusOffset() - 1,
});
updatedEditorState = EditorState.forceSelection(updatedEditorState, updatedSelection);
}
props.form.setFieldValue(name, updatedEditorState);
};

const handleKeyCommand = (command: any, editorState: any) => {
if (command === 'underline') {
return 'handled';
}
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
props.form.setFieldValue(rest.name, newState);
return 'handled';
if (command === 'bold') {
updateValue('**');
} else if (command === 'italic') {
updateValue('__');
} else {
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
props.form.setFieldValue(name, newState);
return 'handled';
}
}
return 'not-handled';
};

const updateValue = (emoji: any) => {
const editorContentState = props.form.values[rest.name].getCurrentContent();
const editorSelectionState: any = props.form.values[rest.name].getSelection();
const ModifiedContent = Modifier.insertText(
editorContentState,
editorSelectionState,
emoji.native
);
let updatedEditorState = EditorState.createWithContent(ModifiedContent);
updatedEditorState = EditorState.moveFocusToEnd(updatedEditorState);
props.form.setFieldValue(rest.name, updatedEditorState);
};

const draftJsChange = (editorState: any) => {
if (handleChange) {
handleChange(convertToWhatsApp(props.form.values.example));
handleChange(getPlainTextFromEditor(props.form.values.example));
}
if (getEditorValue) {
getEditorValue(editorState);
} else {
props.form.setFieldValue(rest.name, editorState);
props.form.setFieldValue(name, editorState);
}
};

Expand All @@ -133,13 +146,13 @@ export const EmojiInput: React.FC<EmojiInputProps> = ({

const getSuggestions = useCallback(customSuggestionsFilter, []);

const onSearchChange = ({ value }: { value: string }) => {
setSuggestions(getSuggestions(value, mentions));
const onSearchChange = ({ value: searchValue }: { value: string }) => {
setSuggestions(getSuggestions(searchValue, mentions));
};

const inputProps = {
component: Editor,
editorState: props.form.values[rest.name],
editorState: value,
open,
suggestions,
onOpenChange,
Expand Down Expand Up @@ -168,7 +181,7 @@ export const EmojiInput: React.FC<EmojiInputProps> = ({
title={t('Pick your emoji…')}
emoji="point_up"
style={{ position: 'absolute', top: '10px', right: '0px', zIndex: 2 }}
onSelect={updateValue}
onSelect={(emojiValue) => updateValue(emojiValue, true)}
/>
) : (
<></>
Expand Down Expand Up @@ -199,7 +212,9 @@ export const EmojiInput: React.FC<EmojiInputProps> = ({
</ClickAwayListener>
);

const input = <Input field={{ ...rest }} {...props} editor={editor} emojiPicker={picker} />;
const input = (
<Input field={{ name, value, onBlur }} {...props} editor={editor} emojiPicker={picker} />
);

return input;
};
2 changes: 1 addition & 1 deletion src/components/UI/Form/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import styles from './Input.module.css';

export interface InputProps {
type?: any;
field: { name: string; onChange: any; value: any };
field: { name: string; onChange?: any; value: any; onBlur: any };
disabled?: any;
editor?: any;
label: string;
Expand Down
10 changes: 5 additions & 5 deletions src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { render, fireEvent } from '@testing-library/react';
import { EditorState } from 'draft-js';

import { WhatsAppToDraftEditor } from 'common/RichEditor';
import { ContentState, EditorState } from 'draft-js';
import WhatsAppEditor from './WhatsAppEditor';

const mockHandleKeyCommand = jest.fn();
Expand Down Expand Up @@ -50,7 +48,7 @@ describe('<WhatsAppEditor/>', () => {
};
};

const editorContent = EditorState.createWithContent(WhatsAppToDraftEditor('Hello'));
const editorContent = EditorState.createWithContent(ContentState.createFromText('Hello'));

test('it should have editor and emoji components', () => {
const { container, getByTestId } = render(<WhatsAppEditor {...defaultProps(editorContent)} />);
Expand Down Expand Up @@ -83,7 +81,9 @@ describe('<WhatsAppEditor/>', () => {
const { container, getByTestId } = render(
<WhatsAppEditor
{...defaultProps(
EditorState.createWithContent(WhatsAppToDraftEditor('*this is bold* _this is italic_'))
EditorState.createWithContent(
ContentState.createFromText('*this is bold* _this is italic_')
)
)}
/>
);
Expand Down
49 changes: 34 additions & 15 deletions src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'emoji-mart/css/emoji-mart.css';
import ReactResizeDetector from 'react-resize-detector';
import { useTranslation } from 'react-i18next';

import { convertToWhatsApp } from 'common/RichEditor';
import { getPlainTextFromEditor } from 'common/RichEditor';
import styles from './WhatsAppEditor.module.css';

interface WhatsAppEditorProps {
Expand All @@ -26,21 +26,48 @@ export const WhatsAppEditor: React.SFC<WhatsAppEditorProps> = (props) => {
setEditorState(editorStateChange);
};

const updateValue = (input: any, isEmoji: boolean = false) => {
const editorContentState = editorState.getCurrentContent();
const editorSelectionState: any = editorState.getSelection();
const ModifiedContent = Modifier.replaceText(
editorContentState,
editorSelectionState,
isEmoji ? input.native : input
);
let updatedEditorState = EditorState.push(editorState, ModifiedContent, 'insert-characters');
if (!isEmoji) {
const editorSelectionStateMod = updatedEditorState.getSelection();
const updatedSelection = editorSelectionStateMod.merge({
anchorOffset: editorSelectionStateMod.getAnchorOffset() - 1,
focusOffset: editorSelectionStateMod.getFocusOffset() - 1,
});
updatedEditorState = EditorState.forceSelection(updatedEditorState, updatedSelection);
}
setEditorState(updatedEditorState);
};

const handleKeyCommand = (command: string, editorStateChange: any) => {
// On enter, submit. Otherwise, deal with commands like normal.
if (command === 'enter') {
// Convert Draft.js to WhatsApp
sendMessage(convertToWhatsApp(editorStateChange));
sendMessage(getPlainTextFromEditor(editorStateChange));
return 'handled';
}

if (command === 'underline') {
return 'handled';
}

const newState = RichUtils.handleKeyCommand(editorStateChange, command);
if (newState) {
setEditorState(newState);
return 'handled';
if (command === 'bold') {
updateValue('**');
} else if (command === 'italic') {
updateValue('__');
} else {
const newState = RichUtils.handleKeyCommand(editorStateChange, command);
if (newState) {
setEditorState(newState);
return 'handled';
}
}
return 'not-handled';
};
Expand All @@ -57,14 +84,6 @@ export const WhatsAppEditor: React.SFC<WhatsAppEditorProps> = (props) => {
setShowEmojiPicker(false);
};

const handleEmoji = (emoji: any) => {
const contentState = editorState.getCurrentContent();
const selectionState = editorState.getSelection();
const ModifiedContent = Modifier.insertText(contentState, selectionState, emoji.native);
const editorStateCopy = EditorState.createWithContent(ModifiedContent);
setEditorState(editorStateCopy);
};

const emojiStyles: any = {
position: 'absolute',
bottom: '60px',
Expand Down Expand Up @@ -116,7 +135,7 @@ export const WhatsAppEditor: React.SFC<WhatsAppEditorProps> = (props) => {
title={t('Pick your emoji…')}
emoji="point_up"
style={emojiStyles}
onSelect={handleEmoji}
onSelect={(emoji) => updateValue(emoji, true)}
/>
) : null}
</div>
Expand Down
Loading

0 comments on commit cd83adb

Please sign in to comment.