Fix several composer bugs

This commit is contained in:
Chris Svenningsen 2020-11-03 18:04:22 -08:00 committed by Evan Hahn
parent e9642ae66f
commit 158ed4e455
4 changed files with 152 additions and 28 deletions

View file

@ -8655,8 +8655,13 @@ button.module-image__border-overlay:focus {
height: 100%;
.ql-editor {
caret-color: transparent;
padding: 0;
&--loaded {
caret-color: auto;
}
&.ql-blank::before {
left: 0;
right: 0;

View file

@ -24,7 +24,11 @@ import {
matchReactEmoji,
} from '../quill/emoji/matchers';
import { matchMention } from '../quill/mentions/matchers';
import { MemberRepository, getDeltaToRemoveStaleMentions } from '../quill/util';
import {
MemberRepository,
getDeltaToRemoveStaleMentions,
getTextAndMentionsFromOps,
} from '../quill/util';
Quill.register('formats/emoji', EmojiBlot);
Quill.register('formats/mention', MentionBlot);
@ -221,32 +225,7 @@ export const CompositionInput: React.ComponentType<Props> = props => {
return ['', []];
}
const mentions: Array<BodyRangeType> = [];
const text = ops.reduce((acc, { insert }) => {
if (typeof insert === 'string') {
return acc + insert;
}
if (insert.emoji) {
return acc + insert.emoji;
}
if (insert.mention) {
mentions.push({
length: 1, // The length of `\uFFFC`
mentionUuid: insert.mention.uuid,
replacementText: insert.mention.title,
start: acc.length,
});
return `${acc}\uFFFC`;
}
return acc;
}, '');
return [text.trim(), mentions];
return getTextAndMentionsFromOps(ops);
};
const focus = () => {
@ -368,7 +347,7 @@ export const CompositionInput: React.ComponentType<Props> = props => {
return false;
}
if (large) {
if (propsRef.current.large) {
return true;
}
@ -435,6 +414,26 @@ export const CompositionInput: React.ComponentType<Props> = props => {
return true;
};
const onCtrlA = () => {
const quill = quillRef.current;
if (quill === undefined) {
return;
}
quill.setSelection(0, 0);
};
const onCtrlE = () => {
const quill = quillRef.current;
if (quill === undefined) {
return;
}
quill.setSelection(quill.getLength(), 0);
};
const onChange = () => {
const quill = quillRef.current;
@ -564,6 +563,8 @@ export const CompositionInput: React.ComponentType<Props> = props => {
handler: onShortKeyEnter,
},
onEscape: { key: 27, handler: onEscape }, // 27 = Escape
onCtrlA: { key: 65, ctrlKey: true, handler: onCtrlA }, // 65 = a
onCtrlE: { key: 69, ctrlKey: true, handler: onCtrlE }, // 69 = e
},
},
emojiCompletion: {
@ -603,6 +604,7 @@ export const CompositionInput: React.ComponentType<Props> = props => {
setTimeout(() => {
quill.setSelection(quill.getLength(), 0);
quill.root.classList.add('ql-editor--loaded');
}, 0);
});

View file

@ -6,6 +6,7 @@ import Delta from 'quill-delta';
import { DeltaOperation } from 'quill';
import { ConversationType } from '../state/ducks/conversations';
import { BodyRangeType } from '../types/Util';
const FUSE_OPTIONS = {
shouldSort: true,
@ -15,6 +16,52 @@ const FUSE_OPTIONS = {
keys: ['name', 'firstName', 'profileName', 'title'],
};
export const getTextAndMentionsFromOps = (
ops: Array<DeltaOperation>
): [string, Array<BodyRangeType>] => {
const mentions: Array<BodyRangeType> = [];
const text = ops.reduce((acc, { insert }, index) => {
if (typeof insert === 'string') {
let textToAdd;
switch (index) {
case 0: {
textToAdd = insert.trimLeft();
break;
}
case ops.length - 1: {
textToAdd = insert.trimRight();
break;
}
default: {
textToAdd = insert;
break;
}
}
return acc + textToAdd;
}
if (insert.emoji) {
return acc + insert.emoji;
}
if (insert.mention) {
mentions.push({
length: 1, // The length of `\uFFFC`
mentionUuid: insert.mention.uuid,
replacementText: insert.mention.title,
start: acc.length,
});
return `${acc}\uFFFC`;
}
return acc;
}, '');
return [text, mentions];
};
export const getDeltaToRemoveStaleMentions = (
ops: Array<DeltaOperation>,
memberUuids: Array<string>

View file

@ -5,6 +5,7 @@ import { assert } from 'chai';
import {
MemberRepository,
getDeltaToRemoveStaleMentions,
getTextAndMentionsFromOps,
} from '../../quill/util';
import { ConversationType } from '../../state/ducks/conversations';
@ -162,3 +163,72 @@ describe('getDeltaToRemoveStaleMentions', () => {
});
});
});
describe('getTextAndMentionsFromOps', () => {
describe('given only text', () => {
it('returns only text trimmed', () => {
const ops = [{ insert: ' The ' }, { insert: ' text ' }];
const [resultText, resultMentions] = getTextAndMentionsFromOps(ops);
assert.equal(resultText, 'The text');
assert.equal(resultMentions.length, 0);
});
});
describe('given text, emoji, and mentions', () => {
it('returns the trimmed text with placeholders and mentions', () => {
const ops = [
{
insert: {
emoji: '😂',
},
},
{
insert: ' wow, funny, ',
},
{
insert: {
mention: {
uuid: 'abcdef',
title: '@fred',
},
},
},
];
const [resultText, resultMentions] = getTextAndMentionsFromOps(ops);
assert.equal(resultText, '😂 wow, funny, \uFFFC');
assert.deepEqual(resultMentions, [
{
length: 1,
mentionUuid: 'abcdef',
replacementText: '@fred',
start: 15,
},
]);
});
});
describe('given only mentions', () => {
it('returns the trimmed text with placeholders and mentions', () => {
const ops = [
{
insert: {
mention: {
uuid: 'abcdef',
title: '@fred',
},
},
},
];
const [resultText, resultMentions] = getTextAndMentionsFromOps(ops);
assert.equal(resultText, '\uFFFC');
assert.deepEqual(resultMentions, [
{
length: 1,
mentionUuid: 'abcdef',
replacementText: '@fred',
start: 0,
},
]);
});
});
});