Cybrkyd's Git Repositories

writegood - commit: 867fc40

commit 867fc408702e18bd24c45cc22fd3626eea6330729e6212c821ffa579090b13bc
author cybrkyd <noreply@cybrkyd.com> 2024-05-27 14:53:36 +0100
committer cybrkyd <noreply@cybrkyd.com> 2024-05-27 14:53:36 +0100

Commit Message

Enhance with grammar checking

- Add common grammar error detection and suggestions with click-to-correct
- Re-work JS for better variable naming and separation of concerns
- Change transition threshold from <10% to <11%

📊 Diffstat

index.html 237
1 files changed, 134 insertions(+), 103 deletions(-)

Diff

diff --git a/index.html b/index.html
index 15db205..8c061d6 100644
--- a/index.html
+++ b/index.html
@@ -2,49 +2,25 @@
<html lang="en">
<head>
<meta charset="UTF-8">
- <title>Transition Word Checker</title>
+ <title>Language Checker</title>
<style>
- body {
- font-family: sans-serif;
- padding: 20px;
- max-width: 800px;
- margin: auto;
- }
- #editor {
- white-space: pre-wrap;
- word-wrap: break-word;
- overflow-y: auto;
- overflow-x: hidden;
- width: 100%;
- height: 400px;
- padding: 10px;
- border: 1px solid #ccc;
- font-family: monospace;
- font-size: 0.9rem;
- line-height: 1.5;
- }
- #editor:focus {
- outline: none;
- border: 1px solid #aaa;
- box-shadow: 0 0 4px rgba(0,0,0,0.05);
- }
- .highlight {
- background-color: yellow;
- font-weight: bold;
- }
- #result.low {
- color: red;
- }
- #result.high {
- color: green;
- }
- #breakdown {
- margin-top: 10px;
- }
+ body{font-family:sans-serif;padding:20px;max-width:800px;margin:auto}
+ h1{text-align:center}
+ #editor{white-space:pre-wrap;word-wrap:break-word;overflow-y:auto;overflow-x:hidden;width:100%;height:400px;padding:10px;border:1px solid #ccc;font-family:monospace;font-size:0.9rem;line-height:1.5}
+ #editor:focus{outline:none;border:1px solid #aaa;box-shadow:0 0 4px rgba(0,0,0,0.05)}
+ .highlight{background-color:#a0e0a0;font-weight:bold}
+ .error-highlight{background-color:#e0a0a0;font-weight:bold}
+ #result.low, .grammar{color:red}
+ #result.high{color:green}
+ #breakdown{margin-top:10px}
+ .suggestion-item{cursor:pointer;padding:4px 8px;margin:4px 0;border:1px solid #ddd;border-radius:4px;transition:background-color 0.2s}
+ .suggestion-item:hover{background-color:#e6f7ff;border-color:#91d5ff}
+ .suggestion-item:active{background-color:#bae7ff}
+ .none{margin-bottom:1rem}
</style>
</head>
<body>
- <h1>Transition Word Checker</h1>
+ <h1>Transition Words and Grammar Checker</h1>
<div id="editor" contenteditable="true" spellcheck="true"></div>
@@ -63,80 +39,135 @@
"suggests that", "because", "especially", "still", "unless", "since", "above all"
];
- function escapeHtml(text) {
- const div = document.createElement("div");
- div.textContent = text;
- return div.innerHTML;
- }
-
- function highlightAndCount(text) {
- let count = 0;
- const found = {};
- let highlighted = escapeHtml(text);
-
- const sentenceRegex = /[^.!?]+[.!?]*/g;
- const sentences = text.match(sentenceRegex) || [];
-
- const escapeRegex = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
-
- // --- (1) Transitional words at beginning or after semicolon
- for (let sentence of sentences) {
- const trimmed = sentence.trim();
- for (const phrase of transitionalWords) {
- const pattern = new RegExp(`(^|;\\s*)(${escapeRegex(phrase)})\\b`, 'gi');
- let match;
- while ((match = pattern.exec(trimmed)) !== null) {
- count++;
- found[phrase] = (found[phrase] || 0) + 1;
- }
- }
+ const commonErrors = {
+ "at it's": "at its",
+ "barb wire": "barbed wire",
+ "have it's": "have its"
+ };
+
+ function escapeHtml(text) {
+ const div = document.createElement("div");
+ div.textContent = text;
+ return div.innerHTML;
+ }
+
+ function escapeRegex(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+
+ function highlightAndCount(text) {
+ let transitionCount = 0;
+ const foundTransitions = {};
+ const foundErrors = {};
+ let highlighted = escapeHtml(text);
+
+ const sentenceRegex = /[^.!?]+[.!?]*/g;
+ const sentences = text.match(sentenceRegex) || [];
+
+ for (const [error, suggestion] of Object.entries(commonErrors)) {
+ const pattern = new RegExp(`\\b${escapeRegex(error)}\\b`, 'gi');
+ if (pattern.test(text)) {
+ foundErrors[error] = suggestion;
+ highlighted = highlighted.replace(pattern, `<span class="error-highlight">$&</span>`);
}
+ }
- // --- (2) Mid-sentence only words
- for (const phrase of midSentenceOnlyWords) {
- const pattern = new RegExp(`(?<!^|[.;!?]\\s*)\\b${escapeRegex(phrase)}\\b`, 'gi');
+ for (let sentence of sentences) {
+ const trimmed = sentence.trim();
+ for (const phrase of transitionalWords) {
+ const pattern = new RegExp(`(^|;\\s*)(${escapeRegex(phrase)})\\b`, 'gi');
let match;
- while ((match = pattern.exec(text)) !== null) {
- count++;
- found[phrase] = (found[phrase] || 0) + 1;
+ while ((match = pattern.exec(trimmed)) !== null) {
+ transitionCount++;
+ foundTransitions[phrase] = (foundTransitions[phrase] || 0) + 1;
}
}
+ }
- // Highlight only counted phrases
- const sortedPhrases = Object.keys(found).sort((a, b) => b.length - a.length);
- for (const phrase of sortedPhrases) {
- const pattern = new RegExp(`\\b(${escapeRegex(phrase)})\\b`, 'gi');
- highlighted = highlighted.replace(pattern, `<span class="highlight">$1</span>`);
+ for (const phrase of midSentenceOnlyWords) {
+ const pattern = new RegExp(`(?<!^|[.;!?]\\s*)\\b${escapeRegex(phrase)}\\b`, 'gi');
+ let match;
+ while ((match = pattern.exec(text)) !== null) {
+ transitionCount++;
+ foundTransitions[phrase] = (foundTransitions[phrase] || 0) + 1;
}
-
- return { count, found, highlighted, sentenceCount: sentences.length };
}
- function checkTransitions() {
- const editor = document.getElementById("editor");
- const rawText = editor.innerText;
-
- const { count, found, highlighted, sentenceCount } = highlightAndCount(rawText);
-
- editor.innerHTML = highlighted;
-
- const percentage = sentenceCount ? (count / sentenceCount * 100).toFixed(1) : 0;
-
- const result = document.getElementById("result");
- result.textContent = `Transitions: ${count} (${percentage}%)`;
- result.className = (percentage < 10) ? "low" : "high";
-
- const breakdown = document.getElementById("breakdown");
- if (count > 0) {
- const sorted = Object.entries(found).sort((a, b) => a[0].localeCompare(b[0]));
- breakdown.innerHTML = "<div>Found:<ul>" +
- sorted.map(([word, c]) => `<li>${word}: ${c}</li>`).join("") +
- "</ul></div>";
- } else {
- breakdown.innerHTML = "<div>No transitional words found.</div>";
- }
+ const sortedPhrases = Object.keys(foundTransitions).sort((a, b) => b.length - a.length);
+ for (const phrase of sortedPhrases) {
+ const pattern = new RegExp(`\\b${escapeRegex(phrase)}\\b`, 'gi');
+ highlighted = highlighted.replace(pattern, `<span class="highlight">$&</span>`);
}
- </script>
+
+ return {
+ transitionCount,
+ foundTransitions,
+ foundErrors,
+ highlighted,
+ sentenceCount: sentences.length
+ };
+ }
+
+ function checkTransitions() {
+ const editor = document.getElementById("editor");
+ const rawText = editor.innerText;
+
+ const {
+ transitionCount,
+ foundTransitions,
+ foundErrors,
+ highlighted,
+ sentenceCount
+ } = highlightAndCount(rawText);
+
+ editor.innerHTML = highlighted;
+
+ const percentage = sentenceCount ? (transitionCount / sentenceCount * 100).toFixed(1) : 0;
+
+ const result = document.getElementById("result");
+ result.textContent = `Transitions: ${transitionCount} (${percentage}%)`;
+ result.className = (percentage < 11) ? "low" : "high";
+
+ const breakdown = document.getElementById("breakdown");
+ let breakdownHTML = "";
+
+ if (transitionCount > 0) {
+ const sorted = Object.entries(foundTransitions).sort((a, b) => a[0].localeCompare(b[0]));
+ breakdownHTML += "<ul>" +
+ sorted.map(([word, c]) => `<li>${word}: ${c}</li>`).join("") +
+ "</ul>";
+ } else {
+ breakdownHTML += "<div class=\"none\">No transitional words found.</div>";
+ }
+
+ if (Object.keys(foundErrors).length > 0) {
+ breakdownHTML += "<div><span class=\"grammar\">Grammar Suggestions:</span><ul>" +
+ Object.entries(foundErrors).map(([word, suggestion]) =>
+ `<li class="suggestion-item" data-error="${escapeHtml(word)}" data-suggestion="${escapeHtml(suggestion)}">${word} → ${suggestion}</li>`
+ ).join("") +
+ "</ul></div>";
+ }
+
+ breakdown.innerHTML = breakdownHTML;
+
+ const suggestionItems = breakdown.querySelectorAll('.suggestion-item');
+ suggestionItems.forEach(item => {
+ item.addEventListener('click', function() {
+ const error = this.getAttribute('data-error');
+ const suggestion = this.getAttribute('data-suggestion');
+
+ // Replace all occurrences of the error with the suggestion
+ const editorText = editor.innerText;
+ const pattern = new RegExp(`\\b${escapeRegex(error)}\\b`, 'gi');
+ const newText = editorText.replace(pattern, suggestion);
+
+ editor.innerText = newText;
+
+ checkTransitions();
+ });
+ });
+ }
+ </script>
</body>
</html>