Skip to content

Commit 76b5bdc

Browse files
committed
fix: match korean substring for suggestions
1 parent ad43016 commit 76b5bdc

File tree

4 files changed

+86
-4
lines changed

4 files changed

+86
-4
lines changed

.changeset/shaggy-toes-speak.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-mentions": patch
3+
---
4+
5+
Fixed substring matching bug when typeing korean.

src/utils/createMatcher.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import escapeRegex from './escapeRegex'
2+
3+
function ch2pattern(ch) {
4+
const offset = 44032 /* '가'의 코드 */
5+
// 한국어 음절 (Korean syllables)
6+
if (/[-]/.test(ch)) {
7+
const chCode = ch.charCodeAt(0) - offset
8+
// 종성이 있으면 문자 그대로를 찾는다.
9+
if (chCode % 28 > 0) {
10+
return ch
11+
}
12+
13+
const begin = Math.floor(chCode / 28) * 28 + offset
14+
const end = begin + 27
15+
return `[\\u${begin.toString(16)}-\\u${end.toString(16)}]`
16+
}
17+
18+
// 한글 자음 (Korean consonants)
19+
if (/[-]/.test(ch)) {
20+
const con2syl = {
21+
: '가'.charCodeAt(0),
22+
: '까'.charCodeAt(0),
23+
: '나'.charCodeAt(0),
24+
: '다'.charCodeAt(0),
25+
: '따'.charCodeAt(0),
26+
: '라'.charCodeAt(0),
27+
: '마'.charCodeAt(0),
28+
: '바'.charCodeAt(0),
29+
: '빠'.charCodeAt(0),
30+
: '사'.charCodeAt(0),
31+
}
32+
const begin =
33+
con2syl[ch] ||
34+
(ch.charCodeAt(0) - 12613) /* 'ㅅ'의 코드 */ * 588 + con2syl['ㅅ']
35+
const end = begin + 587
36+
return `[${ch}\\u${begin.toString(16)}-\\u${end.toString(16)}]`
37+
}
38+
39+
// 그 외엔 그대로 내보냄 (other than korean)
40+
return escapeRegex(ch)
41+
}
42+
43+
export default function createMatcher(input) {
44+
const pattern = input
45+
.split('')
46+
.map(ch2pattern)
47+
.map(pattern => '(' + pattern + ')')
48+
.join('')
49+
50+
return new RegExp(pattern + '.*', 'i')
51+
}

src/utils/createMatcher.spec.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import createMatcher from './createMatcher'
2+
3+
describe('#createMatcher', () => {
4+
it('should match substring including input value', () => {
5+
const enRegex = createMatcher('je')
6+
expect(enRegex.test('Jesse Pinkman')).toEqual(true)
7+
expect(enRegex.test('Pinkman')).toEqual(false)
8+
9+
const enRegex2 = createMatcher('pi')
10+
expect('Jesse Pinkman'.match(enRegex2).index).toEqual(6)
11+
expect('Pinkman'.match(enRegex2).index).toEqual(0)
12+
})
13+
14+
it('should match substring including Korean consonants', () => {
15+
const koRegex = createMatcher('ㅅㄷ')
16+
expect(koRegex.test('성덕선')).toEqual(true)
17+
expect(koRegex.test('덕선')).toEqual(false)
18+
19+
const enRegex2 = createMatcher('ㄷㅅ')
20+
expect('성덕선'.match(enRegex2).index).toEqual(1)
21+
expect('덕선'.match(enRegex2).index).toEqual(0)
22+
})
23+
})

src/utils/getSubstringIndex.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import createMatcher from './createMatcher'
12
import lettersDiacritics from './diacritics'
23

34
const removeAccents = str => {
@@ -16,11 +17,13 @@ const removeAccents = str => {
1617
export const normalizeString = str => removeAccents(str).toLowerCase()
1718

1819
const getSubstringIndex = (str, substr, ignoreAccents) => {
19-
if (!ignoreAccents) {
20-
return str.toLowerCase().indexOf(substr.toLowerCase())
21-
}
20+
const display = ignoreAccents ? normalizeString(str) : str
21+
const query = ignoreAccents ? normalizeString(substr) : substr
2222

23-
return normalizeString(str).indexOf(normalizeString(substr))
23+
const regex = createMatcher(query)
24+
const match = display.match(regex)
25+
26+
return match ? match.index : -1
2427
}
2528

2629
export default getSubstringIndex

0 commit comments

Comments
 (0)