diff --git a/src/VerseValidator.jsx b/src/VerseValidator.jsx index 378498a..03dce78 100644 --- a/src/VerseValidator.jsx +++ b/src/VerseValidator.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import "./VerseValidator.css"; import { StringDiff } from "react-string-diff"; +import { containsKorean, jamoSubstringMatch } from './jamoUtils'; const STATE = { INCORRECT: 0, @@ -21,13 +22,38 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse const [verseBool, setVerseBool] = useState(STATE.INCORRECT) const[hintBool, setHintBool] = useState(false) const[diffBool, setDiffBool] = useState(false) - const [isComposing, setIsComposing] = useState(false); + const [isComposing, setIsComposing] = useState(false); + + // Handle the start of composition const handleCompositionStart = () => { setIsComposing(true); }; + function resultChecker(string1, string2) { + var result = STATE.INCORRECT; // init + // contains korean + if (containsKorean(string1)) { + if (string1 === string2) { + result = STATE.CORRECT; + } else if (jamoSubstringMatch(string2, string1) & string1 !== "") { + result = STATE.PARTIAL; + } else { + result = STATE.INCORRECT; + } + } else { // does not contain korean + if (string1 === string2) { + result = STATE.CORRECT; + } else if (string2.startsWith(string1) & string1 !== "") { + result = STATE.PARTIAL; + } else { + result = STATE.INCORRECT; + } + } + return result; + + } // function to check correctness of reference input const referenceChange = (e) => { @@ -40,14 +66,8 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse .replace(/\s+/g, "") .toLowerCase() .normalize("NFC"); - var result = STATE.INCORRECT; // init - if (string1 === string2) { - result = STATE.CORRECT; - } else if (string2.startsWith(string1) & string1 !== "") { - result = STATE.PARTIAL - } else { - result = STATE.INCORRECT - } + + const result = resultChecker(string1, string2); setReference(value); setReferenceBool(result); @@ -79,14 +99,9 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse .toLowerCase() .normalize("NFC"); - var result = STATE.INCORRECT; // init - if (string1 === string2) { - result = STATE.CORRECT; - } else if (string2.startsWith(string1) & string1 !== "") { - result = STATE.PARTIAL - } else { - result = STATE.INCORRECT - } + + const result = resultChecker(string1, string2); + setTitle(value); setTitleBool(result); @@ -118,14 +133,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse .toLowerCase() .normalize("NFC"); - var result = STATE.INCORRECT; // init - if (string1 === string2) { - result = STATE.CORRECT; - } else if (string2.startsWith(string1) & string1 !== "") { - result = STATE.PARTIAL - } else { - result = STATE.INCORRECT - } + const result = resultChecker(string1, string2); setChapterTitle(value); setChapterTitleBool(result); @@ -153,14 +161,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse .toLowerCase() .normalize("NFC"); - var result = STATE.INCORRECT; // init - if (string1 === string2) { - result = STATE.CORRECT; - } else if (string2.startsWith(string1) & string1 !== "") { - result = STATE.PARTIAL - } else { - result = STATE.INCORRECT - } + const result = resultChecker(string1, string2); setVerse(value); setVerseBool(result); @@ -215,15 +216,11 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse id="referenceBox" name="referenceBox" onChange={(event) => { + if (!isComposing) { referenceChange(event); } }} - onCompositionStart={handleCompositionStart} - onCompositionEnd={(event) => { - setIsComposing(false); - referenceChange(event); - }} /> ) : ( diff --git a/src/jamoUtils.js b/src/jamoUtils.js new file mode 100644 index 0000000..add1d49 --- /dev/null +++ b/src/jamoUtils.js @@ -0,0 +1,84 @@ +const BASE_CODE = 44032; +const CHO = 588; +const JUNG = 28; + +const cho = ["ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"]; +const jung = ["ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ", "ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ", "ㅣ"]; +const jong = ["", "ㄱ", "ㄲ", "ㄳ", "ㄴ", "ㄵ", "ㄶ", "ㄷ", "ㄹ", "ㄺ", "ㄻ", "ㄼ", "ㄽ", "ㄾ", "ㄿ", "ㅀ", "ㅁ", "ㅂ", "ㅄ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"]; + +// Decompose the medial vowel into individual components, if necessary +const jungSplit = { + "ㅘ": ["ㅗ", "ㅏ"], + "ㅙ": ["ㅗ", "ㅐ"], + "ㅚ": ["ㅗ", "ㅣ"], + "ㅝ": ["ㅜ", "ㅓ"], + "ㅞ": ["ㅜ", "ㅔ"], + "ㅟ": ["ㅜ", "ㅣ"], + "ㅢ": ["ㅡ", "ㅣ"], +}; + +// decompose ending consonents +const jongSplit = { + "ㄲ": ["ㄱ", "ㄱ"], + "ㄳ": ["ㄱ", "ㅅ"], + "ㄵ": ["ㄴ", "ㅈ"], + "ㄶ": ["ㄴ", "ㅎ"], + "ㄺ": ["ㄹ", "ㄱ"], + "ㄻ": ["ㄹ", "ㅁ"], + "ㄼ": ["ㄹ", "ㅂ"], + "ㄽ": ["ㄹ", "ㅅ"], + "ㄾ": ["ㄹ", "ㅌ"], + "ㄿ": ["ㄹ", "ㅍ"], + "ㅀ": ["ㄹ", "ㅎ"], + "ㅄ": ["ㅂ", "ㅅ"], + "ㅆ": ["ㅅ", "ㅅ"], +} + +export function decomposeHangul(character) { + const code = character.charCodeAt(0) - BASE_CODE; + + if (code < 0 || code > 11171) { + // Return character as is if it's not a Hangul syllable + return character; + } + + const choIndex = Math.floor(code / CHO); + const jungIndex = Math.floor((code - (choIndex * CHO)) / JUNG); + const jongIndex = code % JUNG; + + // Decompose cho, jung, and jong + const choJamo = cho[choIndex]; + const jungJamo = jung[jungIndex]; + const jongJamo = jong[jongIndex]; + + // Handle double jamo in jung + const jungComponents = jungSplit[jungJamo] || [jungJamo]; + + + // handle double jamo in jong + const jongComponents = jongSplit[jongJamo] || [jongJamo]; + + return [choJamo, ...jungComponents, ...jongComponents].join(''); +} + +export function decomposeStringToJamo(inputString) { + return inputString.split('').map(decomposeHangul).join(''); +} + +export function jamoSubstringMatch(mainString, substring) { + const decomposedMain = decomposeStringToJamo(mainString) + .replace(/\s+/g, ""); + const decomposedSub = decomposeStringToJamo(substring) + .replace(/\s+/g, ""); + + return decomposedMain.startsWith(decomposedSub); +} + +// Unicode Ranges for Korean Characters: +// Hangul Syllables: \uAC00-\uD7A3 +// Hangul Jamo: \u1100-\u11FF (for initial consonants, vowels, and final consonants) +// Hangul Compatibility Jamo: \u3130-\u318F +export function containsKorean(text) { + const koreanRegex = /[\u1100-\u11FF\u3130-\u318F\uAC00-\uD7A3]/; + return koreanRegex.test(text); +} \ No newline at end of file