Remove fuzzy @mention search
This commit is contained in:
parent
ca83281986
commit
4def45b86a
10 changed files with 211 additions and 152 deletions
|
@ -25,10 +25,10 @@ import {
|
||||||
} from '../quill/emoji/matchers';
|
} from '../quill/emoji/matchers';
|
||||||
import { matchMention } from '../quill/mentions/matchers';
|
import { matchMention } from '../quill/mentions/matchers';
|
||||||
import {
|
import {
|
||||||
MemberRepository,
|
|
||||||
getDeltaToRemoveStaleMentions,
|
getDeltaToRemoveStaleMentions,
|
||||||
getTextAndMentionsFromOps,
|
getTextAndMentionsFromOps,
|
||||||
} from '../quill/util';
|
} from '../quill/util';
|
||||||
|
import { MemberRepository } from '../quill/memberRepository';
|
||||||
|
|
||||||
Quill.register('formats/emoji', EmojiBlot);
|
Quill.register('formats/emoji', EmojiBlot);
|
||||||
Quill.register('formats/mention', MentionBlot);
|
Quill.register('formats/mention', MentionBlot);
|
||||||
|
|
62
ts/quill/memberRepository.ts
Normal file
62
ts/quill/memberRepository.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
|
|
||||||
|
import { ConversationType } from '../state/ducks/conversations';
|
||||||
|
|
||||||
|
const FUSE_OPTIONS = {
|
||||||
|
location: 0,
|
||||||
|
shouldSort: true,
|
||||||
|
threshold: 0,
|
||||||
|
maxPatternLength: 32,
|
||||||
|
minMatchCharLength: 1,
|
||||||
|
tokenize: true,
|
||||||
|
keys: ['name', 'firstName', 'profileName', 'title'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MemberRepository {
|
||||||
|
private members: Array<ConversationType>;
|
||||||
|
|
||||||
|
private fuse: Fuse<ConversationType>;
|
||||||
|
|
||||||
|
constructor(members: Array<ConversationType> = []) {
|
||||||
|
this.members = members;
|
||||||
|
this.fuse = new Fuse<ConversationType>(this.members, FUSE_OPTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMembers(members: Array<ConversationType>): void {
|
||||||
|
this.members = members;
|
||||||
|
this.fuse = new Fuse(members, FUSE_OPTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMembers(omit?: ConversationType): Array<ConversationType> {
|
||||||
|
if (omit) {
|
||||||
|
return this.members.filter(({ id }) => id !== omit.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.members;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMemberById(id?: string): ConversationType | undefined {
|
||||||
|
return id
|
||||||
|
? this.members.find(({ id: memberId }) => memberId === id)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMemberByUuid(uuid?: string): ConversationType | undefined {
|
||||||
|
return uuid
|
||||||
|
? this.members.find(({ uuid: memberUuid }) => memberUuid === uuid)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
search(pattern: string, omit?: ConversationType): Array<ConversationType> {
|
||||||
|
const results = this.fuse.search(`${pattern}`);
|
||||||
|
|
||||||
|
if (omit) {
|
||||||
|
return results.filter(({ id }) => id !== omit.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,8 @@ import { createPortal } from 'react-dom';
|
||||||
import { ConversationType } from '../../state/ducks/conversations';
|
import { ConversationType } from '../../state/ducks/conversations';
|
||||||
import { Avatar } from '../../components/Avatar';
|
import { Avatar } from '../../components/Avatar';
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { LocalizerType } from '../../types/Util';
|
||||||
import { MemberRepository } from '../util';
|
|
||||||
|
import { MemberRepository } from '../memberRepository';
|
||||||
|
|
||||||
export interface MentionCompletionOptions {
|
export interface MentionCompletionOptions {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import Delta from 'quill-delta';
|
import Delta from 'quill-delta';
|
||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
import { MemberRepository } from '../util';
|
import { MemberRepository } from '../memberRepository';
|
||||||
|
|
||||||
export const matchMention = (
|
export const matchMention = (
|
||||||
memberRepositoryRef: RefObject<MemberRepository>
|
memberRepositoryRef: RefObject<MemberRepository>
|
||||||
|
|
|
@ -1,21 +1,11 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import Fuse from 'fuse.js';
|
|
||||||
import Delta from 'quill-delta';
|
import Delta from 'quill-delta';
|
||||||
import { DeltaOperation } from 'quill';
|
import { DeltaOperation } from 'quill';
|
||||||
|
|
||||||
import { ConversationType } from '../state/ducks/conversations';
|
|
||||||
import { BodyRangeType } from '../types/Util';
|
import { BodyRangeType } from '../types/Util';
|
||||||
|
|
||||||
const FUSE_OPTIONS = {
|
|
||||||
shouldSort: true,
|
|
||||||
threshold: 0.2,
|
|
||||||
maxPatternLength: 32,
|
|
||||||
minMatchCharLength: 1,
|
|
||||||
keys: ['name', 'firstName', 'profileName', 'title'],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTextAndMentionsFromOps = (
|
export const getTextAndMentionsFromOps = (
|
||||||
ops: Array<DeltaOperation>
|
ops: Array<DeltaOperation>
|
||||||
): [string, Array<BodyRangeType>] => {
|
): [string, Array<BodyRangeType>] => {
|
||||||
|
@ -88,49 +78,3 @@ export const getDeltaToRemoveStaleMentions = (
|
||||||
|
|
||||||
return new Delta(newOps);
|
return new Delta(newOps);
|
||||||
};
|
};
|
||||||
|
|
||||||
export class MemberRepository {
|
|
||||||
private members: Array<ConversationType>;
|
|
||||||
|
|
||||||
private fuse: Fuse<ConversationType>;
|
|
||||||
|
|
||||||
constructor(members: Array<ConversationType> = []) {
|
|
||||||
this.members = members;
|
|
||||||
this.fuse = new Fuse<ConversationType>(this.members, FUSE_OPTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMembers(members: Array<ConversationType>): void {
|
|
||||||
this.members = members;
|
|
||||||
this.fuse = new Fuse(members, FUSE_OPTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
getMembers(omit?: ConversationType): Array<ConversationType> {
|
|
||||||
if (omit) {
|
|
||||||
return this.members.filter(({ id }) => id !== omit.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.members;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMemberById(id?: string): ConversationType | undefined {
|
|
||||||
return id
|
|
||||||
? this.members.find(({ id: memberId }) => memberId === id)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMemberByUuid(uuid?: string): ConversationType | undefined {
|
|
||||||
return uuid
|
|
||||||
? this.members.find(({ uuid: memberUuid }) => memberUuid === uuid)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
search(pattern: string, omit?: ConversationType): Array<ConversationType> {
|
|
||||||
const results = this.fuse.search(pattern);
|
|
||||||
|
|
||||||
if (omit) {
|
|
||||||
return results.filter(({ id }) => id !== omit.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
134
ts/test/quill/memberRepository_test.ts
Normal file
134
ts/test/quill/memberRepository_test.ts
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import { ConversationType } from '../../state/ducks/conversations';
|
||||||
|
import { MemberRepository } from '../../quill/memberRepository';
|
||||||
|
|
||||||
|
const memberMahershala: ConversationType = {
|
||||||
|
id: '555444',
|
||||||
|
uuid: 'abcdefg',
|
||||||
|
title: 'Pal',
|
||||||
|
firstName: 'Mahershala',
|
||||||
|
profileName: 'Mr Ali',
|
||||||
|
name: 'Friend',
|
||||||
|
type: 'direct',
|
||||||
|
lastUpdated: Date.now(),
|
||||||
|
markedUnread: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const memberShia: ConversationType = {
|
||||||
|
id: '333222',
|
||||||
|
uuid: 'hijklmno',
|
||||||
|
title: 'Buddy',
|
||||||
|
firstName: 'Shia',
|
||||||
|
profileName: 'Sr LaBeouf',
|
||||||
|
name: 'Duder',
|
||||||
|
type: 'direct',
|
||||||
|
lastUpdated: Date.now(),
|
||||||
|
markedUnread: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const members: Array<ConversationType> = [memberMahershala, memberShia];
|
||||||
|
|
||||||
|
const singleMember: ConversationType = {
|
||||||
|
id: '666777',
|
||||||
|
uuid: 'pqrstuv',
|
||||||
|
title: 'The Guy',
|
||||||
|
firstName: 'Jeff',
|
||||||
|
profileName: 'Jr Klaus',
|
||||||
|
name: 'Him',
|
||||||
|
type: 'direct',
|
||||||
|
lastUpdated: Date.now(),
|
||||||
|
markedUnread: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('MemberRepository', () => {
|
||||||
|
describe('#updateMembers', () => {
|
||||||
|
it('updates with given members', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
assert.deepEqual(memberRepository.getMembers(), members);
|
||||||
|
|
||||||
|
const updatedMembers = [...members, singleMember];
|
||||||
|
memberRepository.updateMembers(updatedMembers);
|
||||||
|
assert.deepEqual(memberRepository.getMembers(), updatedMembers);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getMemberById', () => {
|
||||||
|
it('returns undefined when there is no search id', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
assert.isUndefined(memberRepository.getMemberById());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a matched member', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
assert.isDefined(memberRepository.getMemberById('555444'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined when it does not have the member', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
assert.isUndefined(memberRepository.getMemberById('nope'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getMemberByUuid', () => {
|
||||||
|
it('returns undefined when there is no search uuid', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
assert.isUndefined(memberRepository.getMemberByUuid());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a matched member', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
assert.isDefined(memberRepository.getMemberByUuid('abcdefg'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined when it does not have the member', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
assert.isUndefined(memberRepository.getMemberByUuid('nope'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#search', () => {
|
||||||
|
describe('given a prefix-matching string on last name', () => {
|
||||||
|
it('returns the match', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
const results = memberRepository.search('a');
|
||||||
|
assert.deepEqual(results, [memberMahershala]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given a prefix-matching string on first name', () => {
|
||||||
|
it('returns the match', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
const results = memberRepository.search('ma');
|
||||||
|
assert.deepEqual(results, [memberMahershala]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given a prefix-matching string on profile name', () => {
|
||||||
|
it('returns the match', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
const results = memberRepository.search('sr');
|
||||||
|
assert.deepEqual(results, [memberShia]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given a prefix-matching string on title', () => {
|
||||||
|
it('returns the match', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
const results = memberRepository.search('d');
|
||||||
|
assert.deepEqual(results, [memberShia]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given a match in the middle of a name', () => {
|
||||||
|
it('returns zero matches', () => {
|
||||||
|
const memberRepository = new MemberRepository(members);
|
||||||
|
const results = memberRepository.search('e');
|
||||||
|
assert.deepEqual(results, []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,7 +10,7 @@ import {
|
||||||
MentionCompletionOptions,
|
MentionCompletionOptions,
|
||||||
} from '../../../quill/mentions/completion';
|
} from '../../../quill/mentions/completion';
|
||||||
import { ConversationType } from '../../../state/ducks/conversations';
|
import { ConversationType } from '../../../state/ducks/conversations';
|
||||||
import { MemberRepository } from '../../../quill/util';
|
import { MemberRepository } from '../../../quill/memberRepository';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const globalAsAny = global as any;
|
const globalAsAny = global as any;
|
||||||
|
@ -222,7 +222,7 @@ describe('mentionCompletion', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stores the results, omitting `me`, and renders', () => {
|
it('stores the results, omitting `me`, and renders', () => {
|
||||||
expect(mentionCompletion.results).to.have.lengthOf(2);
|
expect(mentionCompletion.results).to.have.lengthOf(1);
|
||||||
expect((mentionCompletion.render as sinon.SinonStub).called).to.equal(
|
expect((mentionCompletion.render as sinon.SinonStub).called).to.equal(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { RefObject } from 'react';
|
||||||
import Delta from 'quill-delta';
|
import Delta from 'quill-delta';
|
||||||
|
|
||||||
import { matchMention } from '../../../quill/mentions/matchers';
|
import { matchMention } from '../../../quill/mentions/matchers';
|
||||||
import { MemberRepository } from '../../../quill/util';
|
import { MemberRepository } from '../../../quill/memberRepository';
|
||||||
import { ConversationType } from '../../../state/ducks/conversations';
|
import { ConversationType } from '../../../state/ducks/conversations';
|
||||||
|
|
||||||
class FakeTokenList<T> extends Array<T> {
|
class FakeTokenList<T> extends Array<T> {
|
||||||
|
|
|
@ -2,93 +2,11 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MemberRepository,
|
|
||||||
getDeltaToRemoveStaleMentions,
|
getDeltaToRemoveStaleMentions,
|
||||||
getTextAndMentionsFromOps,
|
getTextAndMentionsFromOps,
|
||||||
} from '../../quill/util';
|
} from '../../quill/util';
|
||||||
import { ConversationType } from '../../state/ducks/conversations';
|
|
||||||
|
|
||||||
const members: Array<ConversationType> = [
|
|
||||||
{
|
|
||||||
id: '555444',
|
|
||||||
uuid: 'abcdefg',
|
|
||||||
title: 'Mahershala Ali',
|
|
||||||
firstName: 'Mahershala',
|
|
||||||
profileName: 'Mahershala A.',
|
|
||||||
type: 'direct',
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
markedUnread: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '333222',
|
|
||||||
uuid: 'hijklmno',
|
|
||||||
title: 'Shia LaBeouf',
|
|
||||||
firstName: 'Shia',
|
|
||||||
profileName: 'Shia L.',
|
|
||||||
type: 'direct',
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
markedUnread: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const singleMember: ConversationType = {
|
|
||||||
id: '666777',
|
|
||||||
uuid: 'pqrstuv',
|
|
||||||
title: 'Fred Savage',
|
|
||||||
firstName: 'Fred',
|
|
||||||
profileName: 'Fred S.',
|
|
||||||
type: 'direct',
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
markedUnread: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('MemberRepository', () => {
|
|
||||||
describe('#updateMembers', () => {
|
|
||||||
it('updates with given members', () => {
|
|
||||||
const memberRepository = new MemberRepository(members);
|
|
||||||
assert.deepEqual(memberRepository.getMembers(), members);
|
|
||||||
|
|
||||||
const updatedMembers = [...members, singleMember];
|
|
||||||
memberRepository.updateMembers(updatedMembers);
|
|
||||||
assert.deepEqual(memberRepository.getMembers(), updatedMembers);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#getMemberById', () => {
|
|
||||||
it('returns undefined when there is no search id', () => {
|
|
||||||
const memberRepository = new MemberRepository(members);
|
|
||||||
assert.isUndefined(memberRepository.getMemberById());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns a matched member', () => {
|
|
||||||
const memberRepository = new MemberRepository(members);
|
|
||||||
assert.isDefined(memberRepository.getMemberById('555444'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns undefined when it does not have the member', () => {
|
|
||||||
const memberRepository = new MemberRepository(members);
|
|
||||||
assert.isUndefined(memberRepository.getMemberById('nope'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#getMemberByUuid', () => {
|
|
||||||
it('returns undefined when there is no search uuid', () => {
|
|
||||||
const memberRepository = new MemberRepository(members);
|
|
||||||
assert.isUndefined(memberRepository.getMemberByUuid());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns a matched member', () => {
|
|
||||||
const memberRepository = new MemberRepository(members);
|
|
||||||
assert.isDefined(memberRepository.getMemberByUuid('abcdefg'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns undefined when it does not have the member', () => {
|
|
||||||
const memberRepository = new MemberRepository(members);
|
|
||||||
assert.isUndefined(memberRepository.getMemberByUuid('nope'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getDeltaToRemoveStaleMentions', () => {
|
describe('getDeltaToRemoveStaleMentions', () => {
|
||||||
const memberUuids = ['abcdef', 'ghijkl'];
|
const memberUuids = ['abcdef', 'ghijkl'];
|
||||||
|
|
|
@ -14544,7 +14544,7 @@
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/CompositionInput.js",
|
"path": "ts/components/CompositionInput.js",
|
||||||
"line": " const emojiCompletionRef = React.useRef();",
|
"line": " const emojiCompletionRef = React.useRef();",
|
||||||
"lineNumber": 42,
|
"lineNumber": 43,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-10-26T19:12:24.410Z",
|
"updated": "2020-10-26T19:12:24.410Z",
|
||||||
"reasonDetail": "Doesn't refer to a DOM element."
|
"reasonDetail": "Doesn't refer to a DOM element."
|
||||||
|
@ -14553,7 +14553,7 @@
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/CompositionInput.js",
|
"path": "ts/components/CompositionInput.js",
|
||||||
"line": " const mentionCompletionRef = React.useRef();",
|
"line": " const mentionCompletionRef = React.useRef();",
|
||||||
"lineNumber": 43,
|
"lineNumber": 44,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-10-26T23:54:34.273Z",
|
"updated": "2020-10-26T23:54:34.273Z",
|
||||||
"reasonDetail": "Doesn't refer to a DOM element."
|
"reasonDetail": "Doesn't refer to a DOM element."
|
||||||
|
@ -14562,7 +14562,7 @@
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/CompositionInput.js",
|
"path": "ts/components/CompositionInput.js",
|
||||||
"line": " const quillRef = React.useRef();",
|
"line": " const quillRef = React.useRef();",
|
||||||
"lineNumber": 44,
|
"lineNumber": 45,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-10-26T19:12:24.410Z",
|
"updated": "2020-10-26T19:12:24.410Z",
|
||||||
"reasonDetail": "Doesn't refer to a DOM element."
|
"reasonDetail": "Doesn't refer to a DOM element."
|
||||||
|
@ -14571,7 +14571,7 @@
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/CompositionInput.js",
|
"path": "ts/components/CompositionInput.js",
|
||||||
"line": " const scrollerRef = React.useRef(null);",
|
"line": " const scrollerRef = React.useRef(null);",
|
||||||
"lineNumber": 45,
|
"lineNumber": 46,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-10-26T19:12:24.410Z",
|
"updated": "2020-10-26T19:12:24.410Z",
|
||||||
"reasonDetail": "Used with Quill for scrolling."
|
"reasonDetail": "Used with Quill for scrolling."
|
||||||
|
@ -14580,7 +14580,7 @@
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/CompositionInput.js",
|
"path": "ts/components/CompositionInput.js",
|
||||||
"line": " const propsRef = React.useRef(props);",
|
"line": " const propsRef = React.useRef(props);",
|
||||||
"lineNumber": 46,
|
"lineNumber": 47,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-10-26T19:12:24.410Z",
|
"updated": "2020-10-26T19:12:24.410Z",
|
||||||
"reasonDetail": "Doesn't refer to a DOM element."
|
"reasonDetail": "Doesn't refer to a DOM element."
|
||||||
|
@ -14588,8 +14588,8 @@
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/CompositionInput.js",
|
"path": "ts/components/CompositionInput.js",
|
||||||
"line": " const memberRepositoryRef = React.useRef(new util_1.MemberRepository());",
|
"line": " const memberRepositoryRef = React.useRef(new memberRepository_1.MemberRepository());",
|
||||||
"lineNumber": 47,
|
"lineNumber": 48,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-10-26T23:56:13.482Z",
|
"updated": "2020-10-26T23:56:13.482Z",
|
||||||
"reasonDetail": "Doesn't refer to a DOM element."
|
"reasonDetail": "Doesn't refer to a DOM element."
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue