Cybrkyd's Git Repositories

writegood - commit: d7f6d3f

commit d7f6d3f776cf3f64f672fc400633371ce419fc52c65fbb4f3e1fea7230e85b39
author cybrkyd <noreply@cybrkyd.com> 2024-05-30 16:07:24 +0100
committer cybrkyd <noreply@cybrkyd.com> 2024-05-30 16:07:24 +0100

Commit Message

Multiple suggestions on grammar errors

- Grammar errors suggest multiple corrections, where applicable
- Drop-down menu allows selecting desired click-to-correct replacement

📊 Diffstat

index.html 185
1 files changed, 129 insertions(+), 56 deletions(-)

Diff

diff --git a/index.html b/index.html
index 8c061d6..dd7b086 100644
--- a/index.html
+++ b/index.html
@@ -17,6 +17,9 @@
.suggestion-item:hover{background-color:#e6f7ff;border-color:#91d5ff}
.suggestion-item:active{background-color:#bae7ff}
.none{margin-bottom:1rem}
+ .suggestion-dropdown{font-family:sans-serif;font-size:0.9rem}
+ .suggestion-option{padding:6px 12px;cursor:pointer;transition:background-color 0.2s}
+ .suggestion-option:hover{background-color:#f0f0f0}
</style>
</head>
<body>
@@ -40,9 +43,17 @@
];
const commonErrors = {
- "at it's": "at its",
- "barb wire": "barbed wire",
- "have it's": "have its"
+ "at it's": ["at its"],
+ "barb wire": ["barbed wire"],
+ "have it's": ["have its"],
+ "another words": ["in other words", "other words"],
+ "alot": ["a lot", "much"],
+ "could of": ["could have", "could've"],
+ "should of": ["should have", "should've"],
+ "their are": ["there are", "they're"],
+ "your welcome": ["you're welcome"],
+ "irregardless": ["regardless", "irrespective"],
+ "for all intensive purposes": ["for all intents and purposes"]
};
function escapeHtml(text) {
@@ -64,10 +75,10 @@
const sentenceRegex = /[^.!?]+[.!?]*/g;
const sentences = text.match(sentenceRegex) || [];
- for (const [error, suggestion] of Object.entries(commonErrors)) {
+ for (const [error, suggestions] of Object.entries(commonErrors)) {
const pattern = new RegExp(`\\b${escapeRegex(error)}\\b`, 'gi');
if (pattern.test(text)) {
- foundErrors[error] = suggestion;
+ foundErrors[error] = Array.isArray(suggestions) ? suggestions : [suggestions];
highlighted = highlighted.replace(pattern, `<span class="error-highlight">$&</span>`);
}
}
@@ -109,64 +120,126 @@
}
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>";
- }
+ const editor = document.getElementById("editor");
+ const rawText = editor.innerText;
- 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>";
- }
+ const {
+ transitionCount,
+ foundTransitions,
+ foundErrors,
+ highlighted,
+ sentenceCount
+ } = highlightAndCount(rawText);
- breakdown.innerHTML = breakdownHTML;
+ editor.innerHTML = highlighted;
- 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');
+ const percentage = sentenceCount ? (transitionCount / sentenceCount * 100).toFixed(1) : 0;
- // 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);
+ const result = document.getElementById("result");
+ result.textContent = `Transitions: ${transitionCount} (${percentage}%)`;
+ result.className = (percentage < 11) ? "low" : "high";
+
+ const breakdown = document.getElementById("breakdown");
+ let breakdownHTML = "";
- editor.innerText = newText;
+ 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>";
+
+ for (const [error, suggestions] of Object.entries(foundErrors)) {
+ const suggestionList = suggestions.map(s => escapeHtml(s)).join(' OR ');
+ breakdownHTML += `
+ <li class="suggestion-item" data-error="${escapeHtml(error)}">
+ ${error} → ${suggestionList}
+ </li>`;
+ }
- checkTransitions();
+ breakdownHTML += "</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 suggestions = foundErrors[error];
+
+ if (suggestions.length === 1) {
+ // Single suggestion - replace directly
+ const pattern = new RegExp(`\\b${escapeRegex(error)}\\b`, 'gi');
+ const editorText = editor.innerText;
+ const newText = editorText.replace(pattern, suggestions[0]);
+ editor.innerText = newText;
+ checkTransitions();
+ } else {
+ // Create drop-down menu for multiple suggestions
+ const rect = this.getBoundingClientRect();
+ const dropdown = document.createElement('div');
+ dropdown.className = 'suggestion-dropdown';
+ dropdown.style.position = 'absolute';
+ dropdown.style.left = rect.left + 'px';
+ dropdown.style.top = rect.bottom + 'px';
+ dropdown.style.background = 'white';
+ dropdown.style.border = '1px solid #ccc';
+ dropdown.style.borderRadius = '4px';
+ dropdown.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
+ dropdown.style.zIndex = '1000';
+ dropdown.style.padding = '4px 0';
+
+ suggestions.forEach(suggestion => {
+ const option = document.createElement('div');
+ option.className = 'suggestion-option';
+ option.textContent = suggestion;
+ option.style.padding = '6px 12px';
+ option.style.cursor = 'pointer';
+ option.style.transition = 'background-color 0.2s';
+
+ option.addEventListener('mouseenter', () => {
+ option.style.backgroundColor = '#e6f7ff';
+ });
+
+ option.addEventListener('mouseleave', () => {
+ option.style.backgroundColor = '';
+ });
+
+ option.addEventListener('click', (e) => {
+ e.stopPropagation();
+ const pattern = new RegExp(`\\b${escapeRegex(error)}\\b`, 'gi');
+ const editorText = editor.innerText;
+ const newText = editorText.replace(pattern, suggestion);
+ editor.innerText = newText;
+ document.body.removeChild(dropdown);
+ checkTransitions();
+ });
+
+ dropdown.appendChild(option);
+ });
+
+ // Close drop-down when clicking elsewhere
+ const closeDropdown = (e) => {
+ if (!dropdown.contains(e.target) && e.target !== this) {
+ document.body.removeChild(dropdown);
+ document.removeEventListener('click', closeDropdown);
+ }
+ };
+
+ document.body.appendChild(dropdown);
+ setTimeout(() => {
+ document.addEventListener('click', closeDropdown);
+ }, 0);
+ }
+ });
});
- });
- }
+ }
</script>
</body>