fix emoji being inserted into mentions identifier
if the cursor is before the first mention => insert it correctly if the cursor is after the last mention => insert it correctly if the cursor is between those two => insert it at the end of the composition box
This commit is contained in:
parent
b05910e219
commit
5d6c2d94ff
|
@ -8,9 +8,9 @@ import { SizeClassType } from '../util/emoji';
|
|||
|
||||
import { RenderTextCallbackType } from '../types/Util';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
text: string;
|
||||
}
|
||||
};
|
||||
|
||||
const renderNewLines: RenderTextCallbackType = ({ text, key }) => (
|
||||
<AddNewLines key={key} text={text} />
|
||||
|
@ -28,56 +28,27 @@ const renderEmoji = ({
|
|||
renderNonEmoji: RenderTextCallbackType;
|
||||
}) => <Emojify key={key} text={text} sizeClass={sizeClass} renderNonEmoji={renderNonEmoji} />;
|
||||
|
||||
export class MessageBodyHighlight extends React.Component<Props> {
|
||||
public render() {
|
||||
const { text } = this.props;
|
||||
const results: Array<any> = [];
|
||||
const FIND_BEGIN_END = /<<left>>(.+?)<<right>>/g;
|
||||
export const MessageBodyHighlight = (props: Props) => {
|
||||
const { text } = props;
|
||||
const results: Array<any> = [];
|
||||
const FIND_BEGIN_END = /<<left>>(.+?)<<right>>/g;
|
||||
|
||||
let match = FIND_BEGIN_END.exec(text);
|
||||
let last = 0;
|
||||
let count = 1;
|
||||
let match = FIND_BEGIN_END.exec(text);
|
||||
let last = 0;
|
||||
let count = 1;
|
||||
|
||||
if (!match) {
|
||||
return <MessageBody disableJumbomoji={true} disableLinks={true} text={text} />;
|
||||
}
|
||||
if (!match) {
|
||||
return <MessageBody disableJumbomoji={true} disableLinks={true} text={text} />;
|
||||
}
|
||||
|
||||
const sizeClass = '';
|
||||
const sizeClass = '';
|
||||
|
||||
while (match) {
|
||||
if (last < match.index) {
|
||||
const beforeText = text.slice(last, match.index);
|
||||
results.push(
|
||||
renderEmoji({
|
||||
text: beforeText,
|
||||
sizeClass,
|
||||
key: count++,
|
||||
renderNonEmoji: renderNewLines,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const [, toHighlight] = match;
|
||||
results.push(
|
||||
<span className="module-message-body__highlight" key={count++}>
|
||||
{renderEmoji({
|
||||
text: toHighlight,
|
||||
sizeClass,
|
||||
key: count++,
|
||||
renderNonEmoji: renderNewLines,
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
last = FIND_BEGIN_END.lastIndex;
|
||||
match = FIND_BEGIN_END.exec(text);
|
||||
}
|
||||
|
||||
if (last < text.length) {
|
||||
while (match) {
|
||||
if (last < match.index) {
|
||||
const beforeText = text.slice(last, match.index);
|
||||
results.push(
|
||||
renderEmoji({
|
||||
text: text.slice(last),
|
||||
text: beforeText,
|
||||
sizeClass,
|
||||
key: count++,
|
||||
renderNonEmoji: renderNewLines,
|
||||
|
@ -85,6 +56,33 @@ export class MessageBodyHighlight extends React.Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
return results;
|
||||
const [, toHighlight] = match;
|
||||
results.push(
|
||||
<span className="module-message-body__highlight" key={count++}>
|
||||
{renderEmoji({
|
||||
text: toHighlight,
|
||||
sizeClass,
|
||||
key: count++,
|
||||
renderNonEmoji: renderNewLines,
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
last = FIND_BEGIN_END.lastIndex;
|
||||
match = FIND_BEGIN_END.exec(text);
|
||||
}
|
||||
}
|
||||
|
||||
if (last < text.length) {
|
||||
results.push(
|
||||
renderEmoji({
|
||||
text: text.slice(last),
|
||||
sizeClass,
|
||||
key: count++,
|
||||
renderNonEmoji: renderNewLines,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
|
|
@ -43,6 +43,7 @@ export class Emojify extends React.Component<Props> {
|
|||
while (match) {
|
||||
if (last < match.index) {
|
||||
const textWithNoEmoji = text.slice(last, match.index);
|
||||
|
||||
results.push(
|
||||
renderNonEmoji({
|
||||
text: textWithNoEmoji,
|
||||
|
|
|
@ -860,8 +860,6 @@ class MessageInner extends React.PureComponent<Props, State> {
|
|||
if (target.className === 'text-selectable' || window.contextMenuShown) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -964,6 +964,63 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
this.setState({ message });
|
||||
}
|
||||
|
||||
private getSelectionBasedOnMentions(index: number) {
|
||||
// we have to get the real selectionStart/end of an index in the mentions box.
|
||||
// this is kind of a pain as the mentions box has two inputs, one with the real text, and one with the extracted mentions
|
||||
|
||||
// the index shown to the user is actually just the visible part of the mentions (so the part between ᅲ...ᅭ
|
||||
const matches = this.state.message.match(this.mentionsRegex);
|
||||
|
||||
let lastMatchStartIndex = 0;
|
||||
let lastMatchEndIndex = 0;
|
||||
let lastRealMatchEndIndex = 0;
|
||||
|
||||
if (!matches) {
|
||||
return index;
|
||||
}
|
||||
const mapStartToLengthOfMatches = matches.map(match => {
|
||||
const displayNameStart = match.indexOf('\uFFD7') + 1;
|
||||
const displayNameEnd = match.lastIndexOf('\uFFD2');
|
||||
const displayName = match.substring(displayNameStart, displayNameEnd);
|
||||
|
||||
const currentMatchStartIndex = this.state.message.indexOf(match) + lastMatchStartIndex;
|
||||
lastMatchStartIndex = currentMatchStartIndex;
|
||||
lastMatchEndIndex = currentMatchStartIndex + match.length;
|
||||
|
||||
const realLength = displayName.length + 1;
|
||||
lastRealMatchEndIndex = lastRealMatchEndIndex + realLength;
|
||||
|
||||
// the +1 is for the @
|
||||
return {
|
||||
length: displayName.length + 1,
|
||||
lastRealMatchEndIndex,
|
||||
start: lastMatchStartIndex,
|
||||
end: lastMatchEndIndex,
|
||||
};
|
||||
});
|
||||
|
||||
const beforeFirstMatch = index < mapStartToLengthOfMatches[0].start;
|
||||
if (beforeFirstMatch) {
|
||||
// those first char are always just char, so the mentions logic does not come into account
|
||||
return index;
|
||||
}
|
||||
const lastMatchMap = _.last(mapStartToLengthOfMatches);
|
||||
|
||||
if (!lastMatchMap) {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
const indexIsAfterEndOfLastMatch = lastMatchMap.lastRealMatchEndIndex <= index;
|
||||
if (indexIsAfterEndOfLastMatch) {
|
||||
const lastEnd = lastMatchMap.end;
|
||||
const diffBetweenEndAndLastRealEnd = index - lastMatchMap.lastRealMatchEndIndex;
|
||||
return lastEnd + diffBetweenEndAndLastRealEnd - 1;
|
||||
}
|
||||
// now this is the hard part, the cursor is currently between the end of the first match and the start of the last match
|
||||
// for now, just append it to the end
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
private onEmojiClick({ colons }: { colons: string }) {
|
||||
const messageBox = this.textarea.current;
|
||||
if (!messageBox) {
|
||||
|
@ -973,10 +1030,12 @@ class SessionCompositionBoxInner extends React.Component<Props, State> {
|
|||
const { message } = this.state;
|
||||
|
||||
const currentSelectionStart = Number(messageBox.selectionStart);
|
||||
const currentSelectionEnd = Number(messageBox.selectionEnd);
|
||||
|
||||
const before = message.slice(0, currentSelectionStart);
|
||||
const end = message.slice(currentSelectionEnd);
|
||||
const realSelectionStart = this.getSelectionBasedOnMentions(currentSelectionStart);
|
||||
|
||||
const before = message.slice(0, realSelectionStart);
|
||||
const end = message.slice(realSelectionStart);
|
||||
|
||||
const newMessage = `${before}${colons}${end}`;
|
||||
|
||||
this.setState({ message: newMessage }, () => {
|
||||
|
|
|
@ -3,42 +3,27 @@ import classNames from 'classnames';
|
|||
import { Picker } from 'emoji-mart';
|
||||
import { Constants } from '../../../session';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
onEmojiClicked: (emoji: any) => void;
|
||||
show: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
interface State {
|
||||
// FIXME Use Emoji-Mart categories
|
||||
category: null;
|
||||
}
|
||||
export const SessionEmojiPanel = (props: Props) => {
|
||||
const { onEmojiClicked, show } = props;
|
||||
|
||||
export class SessionEmojiPanel extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
category: null,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { onEmojiClicked, show } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classNames('session-emoji-panel', show && 'show')}>
|
||||
<Picker
|
||||
backgroundImageFn={() => './images/emoji/emoji-sheet-twitter-32.png'}
|
||||
set={'twitter'}
|
||||
sheetSize={32}
|
||||
darkMode={true}
|
||||
color={Constants.UI.COLORS.GREEN}
|
||||
showPreview={true}
|
||||
title={''}
|
||||
onSelect={onEmojiClicked}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className={classNames('session-emoji-panel', show && 'show')}>
|
||||
<Picker
|
||||
backgroundImageFn={() => './images/emoji/emoji-sheet-twitter-32.png'}
|
||||
set={'twitter'}
|
||||
sheetSize={32}
|
||||
darkMode={true}
|
||||
color={Constants.UI.COLORS.GREEN}
|
||||
showPreview={true}
|
||||
title={''}
|
||||
onSelect={onEmojiClicked}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue