Compare commits
13 Commits
d7fdf2df50
...
a279ff0cf3
Author | SHA1 | Date |
---|---|---|
|
a279ff0cf3 | |
|
f34d9fc7ef | |
|
8061ba5b11 | |
|
c0675ead58 | |
|
8f45d5d0c9 | |
|
c75ff3128c | |
|
033b447eeb | |
|
5ad4720e47 | |
|
4513592483 | |
|
faf83b4827 | |
|
ab75ebbef7 | |
|
a5acd09642 | |
|
965d985251 |
|
@ -11,6 +11,7 @@ node_modules
|
|||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
data_files/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Verse Checker
|
||||
|
||||
This is a simple web-application for checking verses.
|
||||
|
||||
You can try the application at: https://scripturememory.richardwong.io/
|
||||
|
||||
Features include:
|
||||
- Languages supported: English, Korean
|
||||
- Shuffling with random selection from selected packs
|
||||
- Hide References
|
||||
- Showing mistakes as deltas with respect to the answer
|
||||
|
||||
## To Use
|
||||
|
||||
This application uses `pnpm` as the node.js package manager.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/RichFree/VerseChecker.git
|
||||
cd VerseChecker
|
||||
pnpm run dev
|
||||
```
|
|
@ -37,11 +37,11 @@
|
|||
},
|
||||
{
|
||||
"value": "dep-1",
|
||||
"label": "DEP 1"
|
||||
"label": "DEP 1: Assurance of Salvation"
|
||||
},
|
||||
{
|
||||
"value": "dep-2",
|
||||
"label": "DEP 2",
|
||||
"label": "DEP 2: Quiet Time",
|
||||
"children": [
|
||||
{ "value": "dep-2-part-a", "label": "Why do we have Quiet Time?" },
|
||||
{ "value": "dep-2-part-b", "label": "What is Quiet Time?" },
|
||||
|
@ -50,7 +50,7 @@
|
|||
},
|
||||
{
|
||||
"value": "dep-3",
|
||||
"label": "DEP 3",
|
||||
"label": "DEP 3: The Word",
|
||||
"children": [
|
||||
{ "value": "dep-3-part-a", "label": "Authority of the Word" },
|
||||
{ "value": "dep-3-part-b", "label": "value of the Word" },
|
||||
|
@ -60,7 +60,7 @@
|
|||
},
|
||||
{
|
||||
"value": "dep-4",
|
||||
"label": "DEP 4",
|
||||
"label": "DEP 4: Prayer",
|
||||
"children": [
|
||||
{ "value": "dep-4-part-a", "label": "Command of Prayer" },
|
||||
{ "value": "dep-4-part-b", "label": "Promises and Blessings of Prayer" },
|
||||
|
@ -71,7 +71,7 @@
|
|||
},
|
||||
{
|
||||
"value": "dep-5",
|
||||
"label": "DEP 5",
|
||||
"label": "DEP 5: Fellowship",
|
||||
"children": [
|
||||
{ "value": "dep-5-part-a", "label": "Foundation of Christian Fellowship" },
|
||||
{ "value": "dep-5-part-b", "label": "Importance of fellowship" },
|
||||
|
@ -82,12 +82,28 @@
|
|||
},
|
||||
{
|
||||
"value":"dep-6",
|
||||
"label":"DEP 6",
|
||||
"label":"DEP 6: Witnessing",
|
||||
"children": [
|
||||
{ "value": "dep-6-part-a", "label": "Who is respondible for witnessing?" },
|
||||
{ "value": "dep-6-part-a", "label": "Who is responsible for witnessing?" },
|
||||
{ "value": "dep-6-part-b", "label": "Why should we witness?" },
|
||||
{ "value": "dep-6-part-c", "label": "How do we witness?" }
|
||||
{ "value": "dep-6-part-c", "label": "How do we witness?" },
|
||||
{ "value": "dep-6-part-d", "label": "Examples of witness" },
|
||||
{ "value": "dep-6-part-e", "label": "Bridge Illustration" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value":"dep-7",
|
||||
"label":"DEP 7: The Lordship of Christ",
|
||||
"children": [
|
||||
{ "value": "dep-7-part-a", "label": "We must believe in the Lordship of Christ" },
|
||||
{ "value": "dep-7-part-b", "label": "Blessings when surrendering to the Lordship" },
|
||||
{ "value": "dep-7-part-c", "label": "What to surrender in the Lordship" },
|
||||
{ "value": "dep-7-part-d", "label": "Paragons of surrendering to the Lordship" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "dep-8",
|
||||
"label": "DEP 8: World Vision"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -14,15 +14,100 @@
|
|||
"verses": "Verses:"
|
||||
},
|
||||
"verse_validator": {
|
||||
"input_reference": "Input Verse Reference: (KR)",
|
||||
"input_chapter_title": "Input Chapter Title: (KR)",
|
||||
"input_title": "Input Title: (KR)",
|
||||
"input_verse": "Input Verse: (KR)"
|
||||
"input_reference": "Input Verse Reference: ",
|
||||
"input_chapter_title": "Input Chapter Title: ",
|
||||
"input_title": "Input Title: ",
|
||||
"input_verse": "Input Verse: "
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"value": "loa",
|
||||
"label": "Lessons on Assurance"
|
||||
"label": "5확신"
|
||||
},
|
||||
{
|
||||
"value": "8구절",
|
||||
"label": "8구절"
|
||||
},
|
||||
{
|
||||
"value": "tms60",
|
||||
"label": "60구절",
|
||||
"children": [
|
||||
{ "value": "tms-60-pack-a", "label": "새로운 삶" },
|
||||
{ "value": "tms-60-pack-b", "label": "그리스도를 전파함" },
|
||||
{ "value": "tms-60-pack-c", "label": "하나님을 의뢰함" },
|
||||
{ "value": "tms-60-pack-d", "label": "그리스도 제자의 자격" },
|
||||
{ "value": "tms-60-pack-e", "label": "그리스도를 닮아감" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "dep-1",
|
||||
"label": "DEP 1"
|
||||
},
|
||||
{
|
||||
"value": "dep-2",
|
||||
"label": "DEP 2",
|
||||
"children": [
|
||||
{ "value": "dep-2-part-a", "label": "왜 QT를 가져야 하는가?" },
|
||||
{ "value": "dep-2-part-b", "label": "QT란 무엇인가?" },
|
||||
{ "value": "dep-2-part-c", "label": "QT의 본" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "dep-3",
|
||||
"label": "DEP 3",
|
||||
"children": [
|
||||
{ "value": "dep-3-part-a", "label": "말씀의 권위" },
|
||||
{ "value": "dep-3-part-b", "label": "말씀의 가치" },
|
||||
{ "value": "dep-3-part-c", "label": "말씀에 대한 태도" },
|
||||
{ "value": "dep-3-part-d", "label": "말씀의 섭취 방법 말씀의 손 예화" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "dep-4",
|
||||
"label": "DEP 4",
|
||||
"children": [
|
||||
{ "value": "dep-4-part-a", "label": "기도의 명령" },
|
||||
{ "value": "dep-4-part-b", "label": "기도의 약속과 축복" },
|
||||
{ "value": "dep-4-part-c", "label": "응답받는 기도의 조건" },
|
||||
{ "value": "dep-4-part-d", "label": "기도의 본" },
|
||||
{ "value": "dep-4-part-e", "label": "기도의 손 예화" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "dep-5",
|
||||
"label": "DEP 5",
|
||||
"children": [
|
||||
{ "value": "dep-5-part-a", "label": "교제의 기초" },
|
||||
{ "value": "dep-5-part-b", "label": "교제의 중요성" },
|
||||
{ "value": "dep-5-part-c", "label": "교제의 요소" },
|
||||
{ "value": "dep-5-part-d", "label": "교제의 태도" },
|
||||
{ "value": "dep-5-part-e", "label": "교제에서의 문제 해결" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value":"dep-6",
|
||||
"label":"DEP 6",
|
||||
"children": [
|
||||
{ "value": "dep-6-part-a", "label": "전도는 누구의 책임인가?" },
|
||||
{ "value": "dep-6-part-b", "label": "왜 전도를 해야 하나?" },
|
||||
{ "value": "dep-6-part-c", "label": "어떻게 전도하나?" },
|
||||
{ "value": "dep-6-part-d", "label": "전도의 모범" },
|
||||
{ "value": "dep-6-part-e", "label": "Bridge Illustration" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value":"dep-7",
|
||||
"label":"DEP 7",
|
||||
"children": [
|
||||
{ "value": "dep-7-part-a", "label": "주재권을 인정해야 함" },
|
||||
{ "value": "dep-7-part-b", "label": "주재권을 인정할 때의 축복" },
|
||||
{ "value": "dep-7-part-c", "label": "주재권을 인정할 영역" },
|
||||
{ "value": "dep-7-part-d", "label": "주재권을 인정한 삶의 모범" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "dep-8",
|
||||
"label": "DEP 8"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -31,13 +31,14 @@ const GenerateTestList = ({ VerseData, packs, testCount, toShuffle, toHideRefere
|
|||
}
|
||||
|
||||
const ArrayTester = ({ array, toHideReference, translate}) => {
|
||||
const list = array.map((element) => (
|
||||
const list = array.map((element, index) => (
|
||||
// key needs to be unique; chose 3 elements that will separate all elements
|
||||
<VerseValidator
|
||||
key={element.pack + element.title + element.reference}
|
||||
element={element}
|
||||
toHideReference={toHideReference}
|
||||
t={translate} // this passes the t i18 object to the function
|
||||
index={index + 1}
|
||||
/>
|
||||
))
|
||||
return list
|
||||
|
@ -104,6 +105,10 @@ function Page() {
|
|||
// function hook to change language
|
||||
// updates both i18n language and also the VerseData state variable
|
||||
const changeLanguage = (lng) => {
|
||||
// reset selection list
|
||||
setChecked([]);
|
||||
setExpanded([]);
|
||||
|
||||
// i18n.changeLanguage is async, so we should wait until its done to avoid
|
||||
// race conditions
|
||||
// console.log("change language");
|
||||
|
@ -113,25 +118,9 @@ function Page() {
|
|||
};
|
||||
|
||||
|
||||
|
||||
// // create checklist array for pack selection
|
||||
// const packList = Object.keys(VerseData);
|
||||
// // return a list of packObj's
|
||||
// // 1. packObj.pack for the pack name
|
||||
// // 2. packObj.include for whether to include the pack
|
||||
// const packObjList = packList.map((element) => {
|
||||
// // create object for each element in VerseData key list
|
||||
// const packObj = new Object();
|
||||
// packObj.pack = element;
|
||||
// packObj.include = false;
|
||||
// return packObj
|
||||
// }
|
||||
// )
|
||||
// const [packs, setPacks] = useState(packObjList)
|
||||
|
||||
// initialize state variable testCount
|
||||
// purpose: to set number of verses to test
|
||||
const [testCount, setTestCount] = useState(20)
|
||||
const [testCount, setTestCount] = useState(30)
|
||||
const testCountChange = (e) => {
|
||||
const value = e.target.value
|
||||
setTestCount(value)
|
||||
|
@ -252,7 +241,12 @@ function Page() {
|
|||
// loading component for suspense fallback
|
||||
const Loader = () => (
|
||||
<div className="App">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<img
|
||||
src={logo}
|
||||
className="App-logo"
|
||||
alt="logo"
|
||||
style={{ width: '20vw', height: 'auto' }}
|
||||
/>
|
||||
<div>loading...</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
.reference-box {
|
||||
max-width:400px;
|
||||
height: 5vh;
|
||||
field-sizing: content; /* not yet implemented in firefox and safari */
|
||||
display: block;
|
||||
width: 99%;
|
||||
border: 1px solid grey;
|
||||
|
@ -22,6 +23,7 @@
|
|||
.chapter-title-box {
|
||||
max-width:400px;
|
||||
height: 5vh;
|
||||
field-sizing: content; /* not yet implemented in firefox and safari */
|
||||
display: block;
|
||||
width: 99%;
|
||||
border: 1px solid grey;
|
||||
|
@ -32,6 +34,7 @@
|
|||
.title-box {
|
||||
max-width: 400px;
|
||||
height: 5vh;
|
||||
field-sizing: content; /* not yet implemented in firefox and safari */
|
||||
display: block;
|
||||
width: 99%;
|
||||
border: 1px solid grey;
|
||||
|
@ -46,7 +49,8 @@
|
|||
|
||||
.verse-box {
|
||||
max-width:400px;
|
||||
height: 10vh;
|
||||
min-height: 12vh;
|
||||
field-sizing: content; /* not yet implemented in firefox and safari */
|
||||
display: block;
|
||||
width: 99%;
|
||||
border: 1px solid grey;
|
||||
|
@ -73,11 +77,43 @@
|
|||
@media (prefers-color-scheme: light) {
|
||||
.correct {
|
||||
background-color: #e6ffe6; /* Change the background color as needed */
|
||||
}
|
||||
|
||||
.partial {
|
||||
background-color: #dafcff; /* Change the background color as needed */
|
||||
}
|
||||
|
||||
.incorrect {
|
||||
background-color: transparent; /* Change the background color as needed */
|
||||
}
|
||||
|
||||
:root {
|
||||
--background-color-removed: #f1ebb3;
|
||||
--background-color-added: #ffd7b6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.correct {
|
||||
background-color: #495749; /* Change the background color as needed */
|
||||
background-color: #2e5e2e; /* Change the background color as needed */
|
||||
}
|
||||
|
||||
.partial {
|
||||
background-color: #004d5c; /* Change the background color as needed */
|
||||
}
|
||||
|
||||
.incorrect {
|
||||
background-color: transparent; /* Change the background color as needed */
|
||||
}
|
||||
|
||||
:root {
|
||||
--background-color-removed: #877e2a;
|
||||
--background-color-added: #7b4418;
|
||||
}
|
||||
|
||||
.VerseValidator {
|
||||
border-top: 1px solid white; /* override for dark mode */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,76 @@
|
|||
import { useState } from "react";
|
||||
import "./VerseValidator.css";
|
||||
import { StringDiff } from "react-string-diff";
|
||||
import { containsKorean, jamoSubstringMatch } from './utils';
|
||||
|
||||
const STATE = {
|
||||
INCORRECT: 0,
|
||||
PARTIAL: 1,
|
||||
CORRECT: 2,
|
||||
};
|
||||
|
||||
|
||||
|
||||
// function to render and handle logic of each of the cells
|
||||
const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse } , toHideReference, t}) => { // useful use of destructuring here
|
||||
const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse } , toHideReference, t, index}) => { // useful use of destructuring here
|
||||
|
||||
const [inputReference, setReference] = useState('')
|
||||
const [referenceBool, setReferenceBool] = useState(false)
|
||||
const [referenceBool, setReferenceBool] = useState(STATE.INCORRECT)
|
||||
const [inputChapterTitle, setChapterTitle] = useState('')
|
||||
const [chapterTitleBool, setChapterTitleBool] = useState(false)
|
||||
const [chapterTitleBool, setChapterTitleBool] = useState(STATE.INCORRECT)
|
||||
const [inputTitle, setTitle] = useState('')
|
||||
const [titleBool, setTitleBool] = useState(false)
|
||||
const [titleBool, setTitleBool] = useState(STATE.INCORRECT)
|
||||
const [inputVerse, setVerse] = useState('')
|
||||
const [verseBool, setVerseBool] = useState(false)
|
||||
const [verseBool, setVerseBool] = useState(STATE.INCORRECT)
|
||||
const[hintBool, setHintBool] = useState(false)
|
||||
const[diffBool, setDiffBool] = useState(false)
|
||||
const [isComposing, setIsComposing] = useState(false);
|
||||
|
||||
// handle reset
|
||||
const handleReset = () => {
|
||||
setReference('');
|
||||
setReferenceBool(STATE.INCORRECT);
|
||||
setChapterTitle('');
|
||||
setChapterTitleBool(STATE.INCORRECT);
|
||||
setTitle('');
|
||||
setTitleBool(STATE.INCORRECT);
|
||||
setVerse('');
|
||||
setVerseBool(STATE.INCORRECT);
|
||||
setDiffBool(false); // optionally hide answer again
|
||||
};
|
||||
|
||||
|
||||
// function to check correctness of verse input
|
||||
// so far only perform checking on full spelling of reference names
|
||||
|
||||
// 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) => {
|
||||
const value = e.target.value;
|
||||
const string1 = String(value)
|
||||
|
@ -30,33 +81,21 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
.replace(/\s+/g, "")
|
||||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
const bool = (string1 === string2);
|
||||
|
||||
const result = resultChecker(string1, string2);
|
||||
|
||||
setReference(value);
|
||||
setReferenceBool(bool);
|
||||
setReferenceBool(result);
|
||||
};
|
||||
|
||||
{/* function to check correctness of verse input */}
|
||||
const verseChange = (e) => {
|
||||
const value = e.target.value;
|
||||
let string1 = value;
|
||||
let string2 = verse;
|
||||
string1 = String(string1)
|
||||
.replace(/[\p{P}\p{S}]/gu, "")
|
||||
.replace(/\s+/g, "")
|
||||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
string2 = String(string2)
|
||||
.replace(/[\p{P}\p{S}]/gu, "")
|
||||
.replace(/\s+/g, "")
|
||||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
const referenceClassName = `reference-box${
|
||||
referenceBool === STATE.CORRECT ? " correct" :
|
||||
referenceBool === STATE.PARTIAL ? " partial" :
|
||||
" incorrect"
|
||||
}`;
|
||||
|
||||
|
||||
const bool = string1 === string2;
|
||||
|
||||
setVerse(value);
|
||||
setVerseBool(bool);
|
||||
};
|
||||
|
||||
{/* function to check correctness of title input */}
|
||||
const titleChange = (e) => {
|
||||
|
@ -75,11 +114,19 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
|
||||
const bool = string1 === string2;
|
||||
|
||||
const result = resultChecker(string1, string2);
|
||||
|
||||
|
||||
setTitle(value);
|
||||
setTitleBool(bool);
|
||||
setTitleBool(result);
|
||||
};
|
||||
const titleClassName = `chapter-title-box${
|
||||
titleBool=== STATE.CORRECT ? " correct" :
|
||||
titleBool === STATE.PARTIAL ? " partial" :
|
||||
" incorrect"
|
||||
}`;
|
||||
|
||||
|
||||
|
||||
{/* function to check correctness of chapter title input */}
|
||||
|
@ -101,26 +148,61 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
|
||||
const bool = string1 === string2;
|
||||
const result = resultChecker(string1, string2);
|
||||
|
||||
setChapterTitle(value);
|
||||
setChapterTitleBool(bool);
|
||||
setChapterTitleBool(result);
|
||||
};
|
||||
const chapterTitleClassName = `title-box${
|
||||
chapterTitleBool=== STATE.CORRECT ? " correct" :
|
||||
chapterTitleBool === STATE.PARTIAL ? " partial" :
|
||||
" incorrect"
|
||||
}`;
|
||||
|
||||
|
||||
// check verse input
|
||||
const verseChange = (e) => {
|
||||
const value = e.target.value;
|
||||
let string1 = value;
|
||||
let string2 = verse;
|
||||
string1 = String(string1)
|
||||
.replace(/[\p{P}\p{S}]/gu, "")
|
||||
.replace(/\s+/g, "")
|
||||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
string2 = String(string2)
|
||||
.replace(/[\p{P}\p{S}]/gu, "")
|
||||
.replace(/\s+/g, "")
|
||||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
|
||||
const result = resultChecker(string1, string2);
|
||||
|
||||
setVerse(value);
|
||||
setVerseBool(result);
|
||||
};
|
||||
|
||||
const DiffViewer = ({oldValue, newValue}) => {
|
||||
const string1 = String(oldValue)
|
||||
.replace(/[\p{P}\p{S}]/gu, "")
|
||||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
const verseClassName = `verse-box${
|
||||
verseBool === STATE.CORRECT ? " correct" :
|
||||
verseBool === STATE.PARTIAL ? " partial" :
|
||||
" incorrect"
|
||||
}`;
|
||||
|
||||
|
||||
const string2 = String(newValue)
|
||||
.replace(/[\p{P}\p{S}]/gu, "")
|
||||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
// const DiffViewer = ({oldValue, newValue}) => {
|
||||
// const string1 = String(oldValue)
|
||||
// .replace(/[\p{P}\p{S}]/gu, "")
|
||||
// .toLowerCase()
|
||||
// .normalize("NFC");
|
||||
|
||||
return (<StringDiff oldValue={string1} newValue={string2} diffMethod="diffWords" />)
|
||||
}
|
||||
|
||||
// const string2 = String(newValue)
|
||||
// .replace(/[\p{P}\p{S}]/gu, "")
|
||||
// .toLowerCase()
|
||||
// .normalize("NFC");
|
||||
|
||||
// return (<StringDiff oldValue={string1} newValue={string2} diffMethod="diffWords" />)
|
||||
// }
|
||||
|
||||
const DiffViewerStrict = ({oldValue, newValue}) => {
|
||||
const string1 = String(oldValue)
|
||||
|
@ -131,12 +213,33 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
.toLowerCase()
|
||||
.normalize("NFC");
|
||||
|
||||
return (<StringDiff oldValue={string1} newValue={string2} diffMethod="diffWords" />)
|
||||
|
||||
let diffStyle = {
|
||||
added: {
|
||||
backgroundColor: 'var(--background-color-added)'
|
||||
},
|
||||
removed: {
|
||||
backgroundColor: 'var(--background-color-removed)'
|
||||
},
|
||||
default: {}
|
||||
};
|
||||
|
||||
|
||||
return (<StringDiff
|
||||
oldValue={string1}
|
||||
newValue={string2}
|
||||
diffMethod="diffWords"
|
||||
styles={diffStyle}
|
||||
/>)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="VerseValidator">
|
||||
<div className="verse-number">
|
||||
<h3>Verse {index}</h3>
|
||||
</div>
|
||||
|
||||
{/* toggle hiding reference */}
|
||||
{toHideReference ? (
|
||||
<div>
|
||||
|
@ -144,11 +247,17 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
{t('verse_validator.input_reference')}
|
||||
</label>
|
||||
<textarea
|
||||
className={`reference-box${referenceBool ? " correct" : ""}`}
|
||||
className={referenceClassName}
|
||||
type="text"
|
||||
id="referenceBox"
|
||||
name="referenceBox"
|
||||
onChange={referenceChange}
|
||||
value={inputReference}
|
||||
onChange={(event) => {
|
||||
|
||||
if (!isComposing) {
|
||||
referenceChange(event);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -164,11 +273,22 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
{t('verse_validator.input_chapter_title')}
|
||||
</label>
|
||||
<textarea
|
||||
className={`chapter-title-box${chapterTitleBool ? " correct" : ""}`}
|
||||
className={chapterTitleClassName}
|
||||
type="text"
|
||||
id="chapterTitleBox"
|
||||
name="chapterTitleBox"
|
||||
onChange={chapterTitleChange}
|
||||
value={inputChapterTitle}
|
||||
onChange={(event) => {
|
||||
if (!isComposing) {
|
||||
chapterTitleChange(event);
|
||||
}
|
||||
}}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={(event) => {
|
||||
setIsComposing(false);
|
||||
chapterTitleChange(event);
|
||||
}}
|
||||
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -178,11 +298,22 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
{t('verse_validator.input_title')}
|
||||
</label>
|
||||
<textarea
|
||||
className={`title-box${titleBool ? " correct" : ""}`}
|
||||
className={titleClassName}
|
||||
type="text"
|
||||
id="titleBox"
|
||||
name="titleBox"
|
||||
onChange={titleChange}
|
||||
value={inputTitle}
|
||||
onChange={(event) => {
|
||||
if (!isComposing) {
|
||||
titleChange(event);
|
||||
}
|
||||
}}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={(event) => {
|
||||
setIsComposing(false);
|
||||
titleChange(event);
|
||||
}}
|
||||
|
||||
/>
|
||||
|
||||
{/* input box for verse */}
|
||||
|
@ -190,17 +321,30 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
{t('verse_validator.input_verse')}
|
||||
</label>
|
||||
<textarea
|
||||
className={`verse-box${verseBool ? " correct" : ""}`}
|
||||
className={verseClassName}
|
||||
type="text"
|
||||
id="verseBox"
|
||||
name="verseBox"
|
||||
onChange={verseChange}
|
||||
value={inputVerse}
|
||||
onChange={(event) => {
|
||||
if (!isComposing) {
|
||||
verseChange(event);
|
||||
}
|
||||
}}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={(event) => {
|
||||
setIsComposing(false);
|
||||
verseChange(event);
|
||||
}}
|
||||
|
||||
|
||||
/>
|
||||
|
||||
{/* button to toggle show answer*/}
|
||||
<div className="answer-button-box">
|
||||
{/* <button onClick={() => setHintBool(!hintBool)}>Show Answer:</button> */}
|
||||
<button onClick={() => setDiffBool(!diffBool)}>Show Answer:</button>
|
||||
<button onClick={handleReset}>Reset</button>
|
||||
</div>
|
||||
|
||||
{/* This shows the difference between given and input answers*/}
|
||||
|
@ -221,7 +365,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
{chapterTitle && (
|
||||
<div>
|
||||
ChapterTitle:
|
||||
<DiffViewer
|
||||
<DiffViewerStrict
|
||||
oldValue={chapterTitle}
|
||||
newValue={inputChapterTitle}
|
||||
/>
|
||||
|
@ -231,7 +375,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
|||
<p></p>
|
||||
<div>
|
||||
Title:
|
||||
<DiffViewer
|
||||
<DiffViewerStrict
|
||||
oldValue={title}
|
||||
newValue={inputTitle}
|
||||
/>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,86 @@
|
|||
// jamo utils
|
||||
|
||||
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);
|
||||
}
|
Loading…
Reference in New Issue