parent
dccbf7cde8
commit
26b23d879b
@ -0,0 +1,8 @@ |
||||
.validated-text { |
||||
white-space: pre-line; |
||||
} |
||||
|
||||
.fragment-incorrect { |
||||
font-weight: bold; |
||||
color: red; |
||||
} |
@ -0,0 +1,70 @@ |
||||
import './errorsDisplay.css'; |
||||
|
||||
export type ErrorsDisplayParams = { |
||||
originalText: string; |
||||
spellcheckErrors: { |
||||
start: number; |
||||
end: number; |
||||
}[]; |
||||
}; |
||||
|
||||
type TextChunk = { |
||||
text: string; |
||||
index: number; |
||||
isCorrect: boolean; |
||||
}; |
||||
|
||||
const toChunks = ({ originalText, spellcheckErrors }: ErrorsDisplayParams) => { |
||||
const chunks: TextChunk[] = []; |
||||
let lastIndex = 0; |
||||
for (const error of spellcheckErrors) { |
||||
chunks.push({ |
||||
text: originalText.slice(lastIndex, error.start), |
||||
index: lastIndex, |
||||
isCorrect: true, |
||||
}); |
||||
chunks.push({ |
||||
text: originalText.slice(error.start, error.end), |
||||
index: error.start, |
||||
isCorrect: false, |
||||
}); |
||||
|
||||
lastIndex = error.end; |
||||
} |
||||
|
||||
chunks.push({ |
||||
text: originalText.slice(lastIndex, originalText.length), |
||||
index: lastIndex, |
||||
isCorrect: true, |
||||
}); |
||||
|
||||
return chunks.filter(({ text }) => !!text.length); |
||||
}; |
||||
|
||||
export const ErrorsDisplay = ({ |
||||
originalText, |
||||
spellcheckErrors, |
||||
}: ErrorsDisplayParams) => { |
||||
if (!spellcheckErrors.length) { |
||||
return <p>No errors!</p>; |
||||
} |
||||
|
||||
const chunks = toChunks({ originalText, spellcheckErrors }); |
||||
|
||||
return ( |
||||
<p class="validated-text"> |
||||
{chunks.map((chunk) => ( |
||||
<span |
||||
key={chunk.index} |
||||
class={ |
||||
chunk.isCorrect |
||||
? 'fragment-correct' |
||||
: 'fragment-incorrect' |
||||
} |
||||
> |
||||
{chunk.text} |
||||
</span> |
||||
))} |
||||
</p> |
||||
); |
||||
}; |
@ -1,17 +1,79 @@ |
||||
import { useEffect, useState } from 'preact/hooks'; |
||||
import { useCallback, useEffect, useState } from 'preact/hooks'; |
||||
import type { JSX } from 'preact/jsx-runtime'; |
||||
import { ErrorsDisplay, ErrorsDisplayParams } from './errorsDisplay'; |
||||
|
||||
export const SpellChecker = () => { |
||||
const [regex, setRegex] = useState<string>(); |
||||
const [regex, setRegex] = useState<RegExp>(); |
||||
const [errorsDisplayParams, setErrorsDisplayParams] = |
||||
useState<ErrorsDisplayParams>({ |
||||
originalText: '', |
||||
spellcheckErrors: [], |
||||
}); |
||||
|
||||
useEffect(() => { |
||||
if (!regex) { |
||||
setRegex(new URL(`./assets/img/abc.def`, import.meta.url).href); |
||||
window |
||||
.fetch( |
||||
new URL( |
||||
`./assets/th-en-x-basic.ooo-thesaurus`, |
||||
import.meta.url, |
||||
).href, |
||||
) |
||||
.then((response) => response.text()) |
||||
.then((regexString) => { |
||||
console.time('regex-parse'); |
||||
setRegex(new RegExp(regexString, 'gi')); |
||||
console.timeEnd('regex-parse'); |
||||
}) |
||||
.catch((err) => console.error(err)); |
||||
} |
||||
}, [regex]); |
||||
|
||||
const handleSubmit: JSX.GenericEventHandler<HTMLFormElement> = useCallback( |
||||
(e) => { |
||||
e.preventDefault(); |
||||
|
||||
if (!regex) { |
||||
return; |
||||
} |
||||
|
||||
const formData = new FormData(e.currentTarget); |
||||
const originalText = formData.get('text')?.toString() ?? ''; |
||||
|
||||
console.time('regex-execute'); |
||||
const results = [...originalText.matchAll(regex)]; |
||||
console.timeEnd('regex-execute'); |
||||
|
||||
const spellcheckErrors = results.map((result) => { |
||||
const start = result.index; |
||||
if (start === undefined) { |
||||
throw new Error('Should not happen for non-empty results'); |
||||
} |
||||
|
||||
return { |
||||
start, |
||||
end: start + result[0].length, |
||||
}; |
||||
}); |
||||
|
||||
setErrorsDisplayParams({ |
||||
originalText, |
||||
spellcheckErrors, |
||||
}); |
||||
}, |
||||
[regex], |
||||
); |
||||
|
||||
return ( |
||||
<section> |
||||
<textarea rows={10} cols={100}> |
||||
{regex} |
||||
</textarea> |
||||
<form onSubmit={handleSubmit}> |
||||
<textarea rows={10} cols={100} name="text" /> |
||||
<br /> |
||||
<button type="submit" disabled={!regex}> |
||||
Validate |
||||
</button> |
||||
</form> |
||||
<ErrorsDisplay {...errorsDisplayParams} /> |
||||
</section> |
||||
); |
||||
}; |
||||
|
@ -0,0 +1,4 @@ |
||||
export type SpellcheckError = { |
||||
start: number; |
||||
end: number; |
||||
}; |
Loading…
Reference in new issue