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
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
data_files/
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.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",
|
"value": "dep-1",
|
||||||
"label": "DEP 1"
|
"label": "DEP 1: Assurance of Salvation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": "dep-2",
|
"value": "dep-2",
|
||||||
"label": "DEP 2",
|
"label": "DEP 2: Quiet Time",
|
||||||
"children": [
|
"children": [
|
||||||
{ "value": "dep-2-part-a", "label": "Why do we have Quiet Time?" },
|
{ "value": "dep-2-part-a", "label": "Why do we have Quiet Time?" },
|
||||||
{ "value": "dep-2-part-b", "label": "What is Quiet Time?" },
|
{ "value": "dep-2-part-b", "label": "What is Quiet Time?" },
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": "dep-3",
|
"value": "dep-3",
|
||||||
"label": "DEP 3",
|
"label": "DEP 3: The Word",
|
||||||
"children": [
|
"children": [
|
||||||
{ "value": "dep-3-part-a", "label": "Authority of the Word" },
|
{ "value": "dep-3-part-a", "label": "Authority of the Word" },
|
||||||
{ "value": "dep-3-part-b", "label": "value of the Word" },
|
{ "value": "dep-3-part-b", "label": "value of the Word" },
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": "dep-4",
|
"value": "dep-4",
|
||||||
"label": "DEP 4",
|
"label": "DEP 4: Prayer",
|
||||||
"children": [
|
"children": [
|
||||||
{ "value": "dep-4-part-a", "label": "Command of Prayer" },
|
{ "value": "dep-4-part-a", "label": "Command of Prayer" },
|
||||||
{ "value": "dep-4-part-b", "label": "Promises and Blessings of Prayer" },
|
{ "value": "dep-4-part-b", "label": "Promises and Blessings of Prayer" },
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": "dep-5",
|
"value": "dep-5",
|
||||||
"label": "DEP 5",
|
"label": "DEP 5: Fellowship",
|
||||||
"children": [
|
"children": [
|
||||||
{ "value": "dep-5-part-a", "label": "Foundation of Christian Fellowship" },
|
{ "value": "dep-5-part-a", "label": "Foundation of Christian Fellowship" },
|
||||||
{ "value": "dep-5-part-b", "label": "Importance of fellowship" },
|
{ "value": "dep-5-part-b", "label": "Importance of fellowship" },
|
||||||
|
@ -82,12 +82,28 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value":"dep-6",
|
"value":"dep-6",
|
||||||
"label":"DEP 6",
|
"label":"DEP 6: Witnessing",
|
||||||
"children": [
|
"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-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:"
|
"verses": "Verses:"
|
||||||
},
|
},
|
||||||
"verse_validator": {
|
"verse_validator": {
|
||||||
"input_reference": "Input Verse Reference: (KR)",
|
"input_reference": "Input Verse Reference: ",
|
||||||
"input_chapter_title": "Input Chapter Title: (KR)",
|
"input_chapter_title": "Input Chapter Title: ",
|
||||||
"input_title": "Input Title: (KR)",
|
"input_title": "Input Title: ",
|
||||||
"input_verse": "Input Verse: (KR)"
|
"input_verse": "Input Verse: "
|
||||||
},
|
},
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"value": "loa",
|
"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 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
|
// key needs to be unique; chose 3 elements that will separate all elements
|
||||||
<VerseValidator
|
<VerseValidator
|
||||||
key={element.pack + element.title + element.reference}
|
key={element.pack + element.title + element.reference}
|
||||||
element={element}
|
element={element}
|
||||||
toHideReference={toHideReference}
|
toHideReference={toHideReference}
|
||||||
t={translate} // this passes the t i18 object to the function
|
t={translate} // this passes the t i18 object to the function
|
||||||
|
index={index + 1}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
return list
|
return list
|
||||||
|
@ -104,6 +105,10 @@ function Page() {
|
||||||
// function hook to change language
|
// function hook to change language
|
||||||
// updates both i18n language and also the VerseData state variable
|
// updates both i18n language and also the VerseData state variable
|
||||||
const changeLanguage = (lng) => {
|
const changeLanguage = (lng) => {
|
||||||
|
// reset selection list
|
||||||
|
setChecked([]);
|
||||||
|
setExpanded([]);
|
||||||
|
|
||||||
// i18n.changeLanguage is async, so we should wait until its done to avoid
|
// i18n.changeLanguage is async, so we should wait until its done to avoid
|
||||||
// race conditions
|
// race conditions
|
||||||
// console.log("change language");
|
// 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
|
// initialize state variable testCount
|
||||||
// purpose: to set number of verses to test
|
// purpose: to set number of verses to test
|
||||||
const [testCount, setTestCount] = useState(20)
|
const [testCount, setTestCount] = useState(30)
|
||||||
const testCountChange = (e) => {
|
const testCountChange = (e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
setTestCount(value)
|
setTestCount(value)
|
||||||
|
@ -252,7 +241,12 @@ function Page() {
|
||||||
// loading component for suspense fallback
|
// loading component for suspense fallback
|
||||||
const Loader = () => (
|
const Loader = () => (
|
||||||
<div className="App">
|
<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>loading...</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
.reference-box {
|
.reference-box {
|
||||||
max-width:400px;
|
max-width:400px;
|
||||||
height: 5vh;
|
height: 5vh;
|
||||||
|
field-sizing: content; /* not yet implemented in firefox and safari */
|
||||||
display: block;
|
display: block;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
.chapter-title-box {
|
.chapter-title-box {
|
||||||
max-width:400px;
|
max-width:400px;
|
||||||
height: 5vh;
|
height: 5vh;
|
||||||
|
field-sizing: content; /* not yet implemented in firefox and safari */
|
||||||
display: block;
|
display: block;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
|
@ -32,6 +34,7 @@
|
||||||
.title-box {
|
.title-box {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
height: 5vh;
|
height: 5vh;
|
||||||
|
field-sizing: content; /* not yet implemented in firefox and safari */
|
||||||
display: block;
|
display: block;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
|
@ -46,7 +49,8 @@
|
||||||
|
|
||||||
.verse-box {
|
.verse-box {
|
||||||
max-width:400px;
|
max-width:400px;
|
||||||
height: 10vh;
|
min-height: 12vh;
|
||||||
|
field-sizing: content; /* not yet implemented in firefox and safari */
|
||||||
display: block;
|
display: block;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
|
@ -73,11 +77,43 @@
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
.correct {
|
.correct {
|
||||||
background-color: #e6ffe6; /* Change the background color as needed */
|
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) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.correct {
|
.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 { useState } from "react";
|
||||||
import "./VerseValidator.css";
|
import "./VerseValidator.css";
|
||||||
import { StringDiff } from "react-string-diff";
|
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
|
// 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 [inputReference, setReference] = useState('')
|
||||||
const [referenceBool, setReferenceBool] = useState(false)
|
const [referenceBool, setReferenceBool] = useState(STATE.INCORRECT)
|
||||||
const [inputChapterTitle, setChapterTitle] = useState('')
|
const [inputChapterTitle, setChapterTitle] = useState('')
|
||||||
const [chapterTitleBool, setChapterTitleBool] = useState(false)
|
const [chapterTitleBool, setChapterTitleBool] = useState(STATE.INCORRECT)
|
||||||
const [inputTitle, setTitle] = useState('')
|
const [inputTitle, setTitle] = useState('')
|
||||||
const [titleBool, setTitleBool] = useState(false)
|
const [titleBool, setTitleBool] = useState(STATE.INCORRECT)
|
||||||
const [inputVerse, setVerse] = useState('')
|
const [inputVerse, setVerse] = useState('')
|
||||||
const [verseBool, setVerseBool] = useState(false)
|
const [verseBool, setVerseBool] = useState(STATE.INCORRECT)
|
||||||
const[hintBool, setHintBool] = useState(false)
|
const[hintBool, setHintBool] = useState(false)
|
||||||
const[diffBool, setDiffBool] = 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 referenceChange = (e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
const string1 = String(value)
|
const string1 = String(value)
|
||||||
|
@ -30,33 +81,21 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
.replace(/\s+/g, "")
|
.replace(/\s+/g, "")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.normalize("NFC");
|
.normalize("NFC");
|
||||||
const bool = (string1 === string2);
|
|
||||||
|
const result = resultChecker(string1, string2);
|
||||||
|
|
||||||
setReference(value);
|
setReference(value);
|
||||||
setReferenceBool(bool);
|
setReferenceBool(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
{/* function to check correctness of verse input */}
|
const referenceClassName = `reference-box${
|
||||||
const verseChange = (e) => {
|
referenceBool === STATE.CORRECT ? " correct" :
|
||||||
const value = e.target.value;
|
referenceBool === STATE.PARTIAL ? " partial" :
|
||||||
let string1 = value;
|
" incorrect"
|
||||||
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 bool = string1 === string2;
|
|
||||||
|
|
||||||
setVerse(value);
|
|
||||||
setVerseBool(bool);
|
|
||||||
};
|
|
||||||
|
|
||||||
{/* function to check correctness of title input */}
|
{/* function to check correctness of title input */}
|
||||||
const titleChange = (e) => {
|
const titleChange = (e) => {
|
||||||
|
@ -75,11 +114,19 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.normalize("NFC");
|
.normalize("NFC");
|
||||||
|
|
||||||
const bool = string1 === string2;
|
|
||||||
|
const result = resultChecker(string1, string2);
|
||||||
|
|
||||||
|
|
||||||
setTitle(value);
|
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 */}
|
{/* function to check correctness of chapter title input */}
|
||||||
|
@ -101,26 +148,61 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.normalize("NFC");
|
.normalize("NFC");
|
||||||
|
|
||||||
const bool = string1 === string2;
|
const result = resultChecker(string1, string2);
|
||||||
|
|
||||||
setChapterTitle(value);
|
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 verseClassName = `verse-box${
|
||||||
const string1 = String(oldValue)
|
verseBool === STATE.CORRECT ? " correct" :
|
||||||
.replace(/[\p{P}\p{S}]/gu, "")
|
verseBool === STATE.PARTIAL ? " partial" :
|
||||||
.toLowerCase()
|
" incorrect"
|
||||||
.normalize("NFC");
|
}`;
|
||||||
|
|
||||||
|
|
||||||
const string2 = String(newValue)
|
// const DiffViewer = ({oldValue, newValue}) => {
|
||||||
.replace(/[\p{P}\p{S}]/gu, "")
|
// const string1 = String(oldValue)
|
||||||
.toLowerCase()
|
// .replace(/[\p{P}\p{S}]/gu, "")
|
||||||
.normalize("NFC");
|
// .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 DiffViewerStrict = ({oldValue, newValue}) => {
|
||||||
const string1 = String(oldValue)
|
const string1 = String(oldValue)
|
||||||
|
@ -131,12 +213,33 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.normalize("NFC");
|
.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 (
|
return (
|
||||||
<div className="VerseValidator">
|
<div className="VerseValidator">
|
||||||
|
<div className="verse-number">
|
||||||
|
<h3>Verse {index}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* toggle hiding reference */}
|
{/* toggle hiding reference */}
|
||||||
{toHideReference ? (
|
{toHideReference ? (
|
||||||
<div>
|
<div>
|
||||||
|
@ -144,11 +247,17 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
{t('verse_validator.input_reference')}
|
{t('verse_validator.input_reference')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
className={`reference-box${referenceBool ? " correct" : ""}`}
|
className={referenceClassName}
|
||||||
type="text"
|
type="text"
|
||||||
id="referenceBox"
|
id="referenceBox"
|
||||||
name="referenceBox"
|
name="referenceBox"
|
||||||
onChange={referenceChange}
|
value={inputReference}
|
||||||
|
onChange={(event) => {
|
||||||
|
|
||||||
|
if (!isComposing) {
|
||||||
|
referenceChange(event);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -164,11 +273,22 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
{t('verse_validator.input_chapter_title')}
|
{t('verse_validator.input_chapter_title')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
className={`chapter-title-box${chapterTitleBool ? " correct" : ""}`}
|
className={chapterTitleClassName}
|
||||||
type="text"
|
type="text"
|
||||||
id="chapterTitleBox"
|
id="chapterTitleBox"
|
||||||
name="chapterTitleBox"
|
name="chapterTitleBox"
|
||||||
onChange={chapterTitleChange}
|
value={inputChapterTitle}
|
||||||
|
onChange={(event) => {
|
||||||
|
if (!isComposing) {
|
||||||
|
chapterTitleChange(event);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCompositionStart={handleCompositionStart}
|
||||||
|
onCompositionEnd={(event) => {
|
||||||
|
setIsComposing(false);
|
||||||
|
chapterTitleChange(event);
|
||||||
|
}}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -178,11 +298,22 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
{t('verse_validator.input_title')}
|
{t('verse_validator.input_title')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
className={`title-box${titleBool ? " correct" : ""}`}
|
className={titleClassName}
|
||||||
type="text"
|
type="text"
|
||||||
id="titleBox"
|
id="titleBox"
|
||||||
name="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 */}
|
{/* input box for verse */}
|
||||||
|
@ -190,17 +321,30 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
{t('verse_validator.input_verse')}
|
{t('verse_validator.input_verse')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
className={`verse-box${verseBool ? " correct" : ""}`}
|
className={verseClassName}
|
||||||
type="text"
|
type="text"
|
||||||
id="verseBox"
|
id="verseBox"
|
||||||
name="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*/}
|
{/* button to toggle show answer*/}
|
||||||
<div className="answer-button-box">
|
<div className="answer-button-box">
|
||||||
{/* <button onClick={() => setHintBool(!hintBool)}>Show Answer:</button> */}
|
{/* <button onClick={() => setHintBool(!hintBool)}>Show Answer:</button> */}
|
||||||
<button onClick={() => setDiffBool(!diffBool)}>Show Answer:</button>
|
<button onClick={() => setDiffBool(!diffBool)}>Show Answer:</button>
|
||||||
|
<button onClick={handleReset}>Reset</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* This shows the difference between given and input answers*/}
|
{/* This shows the difference between given and input answers*/}
|
||||||
|
@ -221,7 +365,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
{chapterTitle && (
|
{chapterTitle && (
|
||||||
<div>
|
<div>
|
||||||
ChapterTitle:
|
ChapterTitle:
|
||||||
<DiffViewer
|
<DiffViewerStrict
|
||||||
oldValue={chapterTitle}
|
oldValue={chapterTitle}
|
||||||
newValue={inputChapterTitle}
|
newValue={inputChapterTitle}
|
||||||
/>
|
/>
|
||||||
|
@ -231,7 +375,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
|
||||||
<p></p>
|
<p></p>
|
||||||
<div>
|
<div>
|
||||||
Title:
|
Title:
|
||||||
<DiffViewer
|
<DiffViewerStrict
|
||||||
oldValue={title}
|
oldValue={title}
|
||||||
newValue={inputTitle}
|
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