|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8"> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | + <title>Writing Style Analyzer</title> |
| 7 | +<style> |
| 8 | +* { |
| 9 | + box-sizing: border-box; |
| 10 | +} |
| 11 | + |
| 12 | +body { |
| 13 | + font-family: Helvetica, Arial, sans-serif; |
| 14 | + line-height: 1.6; |
| 15 | + margin: 0; |
| 16 | + padding: 20px; |
| 17 | + background: #f5f5f5; |
| 18 | +} |
| 19 | + |
| 20 | +.container { |
| 21 | + max-width: 800px; |
| 22 | + margin: 0 auto; |
| 23 | + background: white; |
| 24 | + padding: 20px; |
| 25 | + border-radius: 8px; |
| 26 | + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| 27 | +} |
| 28 | + |
| 29 | +h1 { |
| 30 | + margin-top: 0; |
| 31 | + color: #333; |
| 32 | +} |
| 33 | + |
| 34 | +textarea { |
| 35 | + width: 100%; |
| 36 | + height: 200px; |
| 37 | + padding: 12px; |
| 38 | + border: 1px solid #ddd; |
| 39 | + border-radius: 4px; |
| 40 | + margin-bottom: 20px; |
| 41 | + font-size: 16px; |
| 42 | + font-family: inherit; |
| 43 | +} |
| 44 | + |
| 45 | +.results { |
| 46 | + margin-top: 20px; |
| 47 | +} |
| 48 | + |
| 49 | +.category { |
| 50 | + margin-bottom: 20px; |
| 51 | + padding: 15px; |
| 52 | + background: #f8f9fa; |
| 53 | + border-radius: 4px; |
| 54 | +} |
| 55 | + |
| 56 | +.category h2 { |
| 57 | + margin-top: 0; |
| 58 | + color: #444; |
| 59 | + font-size: 1.2em; |
| 60 | +} |
| 61 | + |
| 62 | +.highlight { |
| 63 | + background: #ffd700; |
| 64 | + padding: 2px 4px; |
| 65 | + border-radius: 2px; |
| 66 | +} |
| 67 | + |
| 68 | +.warning { |
| 69 | + color: #856404; |
| 70 | + background-color: #fff3cd; |
| 71 | + border: 1px solid #ffeeba; |
| 72 | + padding: 10px; |
| 73 | + margin-bottom: 10px; |
| 74 | + border-radius: 4px; |
| 75 | +} |
| 76 | +</style> |
| 77 | +</head> |
| 78 | +<body> |
| 79 | + <div class="container"> |
| 80 | + <h1>Writing Style Analyzer</h1> |
| 81 | + <p>Adapted from <a href="https://matt.might.net/articles/shell-scripts-for-passive-voice-weasel-words-duplicates/">these shell scripts</a> published by Matt Might.</p> |
| 82 | + <p>Paste your text below to check for weasel words, passive voice, and duplicate words:</p> |
| 83 | + <textarea id="input" placeholder="Enter your text here..."></textarea> |
| 84 | + <div id="results" class="results"></div> |
| 85 | + </div> |
| 86 | + |
| 87 | +<script type="module"> |
| 88 | +// Weasel words from the bash script |
| 89 | +const weaselWords = [ |
| 90 | + 'many', 'various', 'very', 'fairly', 'several', 'extremely', |
| 91 | + 'exceedingly', 'quite', 'remarkably', 'few', 'surprisingly', |
| 92 | + 'mostly', 'largely', 'huge', 'tiny', 'excellent', 'interestingly', |
| 93 | + 'significantly', 'substantially', 'clearly', 'vast', 'relatively', |
| 94 | + 'completely' |
| 95 | +] |
| 96 | + |
| 97 | +// Common irregular verbs for passive voice detection |
| 98 | +const irregularVerbs = [ |
| 99 | + 'awoken', 'been', 'born', 'beat', 'become', 'begun', 'bent', |
| 100 | + 'bound', 'bitten', 'bled', 'blown', 'broken', 'brought', |
| 101 | + 'built', 'burnt', 'bought', 'caught', 'chosen', 'come', |
| 102 | + 'dealt', 'done', 'drawn', 'driven', 'eaten', 'fallen', |
| 103 | + 'fought', 'found', 'flown', 'forgotten', 'given', 'gone', |
| 104 | + 'grown', 'hung', 'heard', 'hidden', 'held', 'kept', 'known', |
| 105 | + 'laid', 'led', 'left', 'lost', 'made', 'meant', 'met', 'paid', |
| 106 | + 'put', 'read', 'run', 'said', 'seen', 'sold', 'sent', 'set', |
| 107 | + 'shown', 'shut', 'sung', 'sat', 'slept', 'spoken', 'spent', |
| 108 | + 'stood', 'taken', 'taught', 'told', 'thought', 'thrown', |
| 109 | + 'understood', 'worn', 'won', 'written' |
| 110 | +] |
| 111 | + |
| 112 | +function findWeaselWords(text) { |
| 113 | + const results = [] |
| 114 | + const words = text.toLowerCase().split(/\b/) |
| 115 | + |
| 116 | + words.forEach((word, index) => { |
| 117 | + if (weaselWords.includes(word.trim())) { |
| 118 | + results.push({ |
| 119 | + word: word.trim(), |
| 120 | + index: index, |
| 121 | + context: getContext(text, index) |
| 122 | + }) |
| 123 | + } |
| 124 | + }) |
| 125 | + |
| 126 | + return results |
| 127 | +} |
| 128 | + |
| 129 | +function findPassiveVoice(text) { |
| 130 | + const results = [] |
| 131 | + const beVerbs = ['am', 'is', 'are', 'was', 'were', 'be', 'been', 'being'] |
| 132 | + const words = text.toLowerCase().split(/\s+/) |
| 133 | + |
| 134 | + words.forEach((word, index) => { |
| 135 | + if (beVerbs.includes(word)) { |
| 136 | + const nextWord = words[index + 1] |
| 137 | + if (nextWord && ( |
| 138 | + nextWord.endsWith('ed') || |
| 139 | + irregularVerbs.includes(nextWord) |
| 140 | + )) { |
| 141 | + results.push({ |
| 142 | + construction: `${word} ${nextWord}`, |
| 143 | + context: getContext(text, index) |
| 144 | + }) |
| 145 | + } |
| 146 | + } |
| 147 | + }) |
| 148 | + |
| 149 | + return results |
| 150 | +} |
| 151 | + |
| 152 | +function findDuplicateWords(text) { |
| 153 | + const results = [] |
| 154 | + const words = text.toLowerCase().split(/\s+/) |
| 155 | + |
| 156 | + words.forEach((word, index) => { |
| 157 | + if (index > 0 && word === words[index - 1]) { |
| 158 | + results.push({ |
| 159 | + word: word, |
| 160 | + context: getContext(text, index) |
| 161 | + }) |
| 162 | + } |
| 163 | + }) |
| 164 | + |
| 165 | + return results |
| 166 | +} |
| 167 | + |
| 168 | +function getContext(text, index) { |
| 169 | + const words = text.split(/\s+/) |
| 170 | + const start = Math.max(0, index - 3) |
| 171 | + const end = Math.min(words.length, index + 4) |
| 172 | + return words.slice(start, end).join(' ') |
| 173 | +} |
| 174 | + |
| 175 | +function displayResults(weasels, passives, duplicates) { |
| 176 | + const resultsDiv = document.getElementById('results') |
| 177 | + resultsDiv.innerHTML = '' |
| 178 | + |
| 179 | + // Weasel Words |
| 180 | + const weaselDiv = document.createElement('div') |
| 181 | + weaselDiv.className = 'category' |
| 182 | + weaselDiv.innerHTML = ` |
| 183 | + <h2>Weasel Words</h2> |
| 184 | + ${weasels.length === 0 ? 'No weasel words found.' : |
| 185 | + weasels.map(w => ` |
| 186 | + <div class="warning"> |
| 187 | + Found "<span class="highlight">${w.word}</span>" in: "${w.context}" |
| 188 | + </div> |
| 189 | + `).join('')} |
| 190 | + ` |
| 191 | + resultsDiv.appendChild(weaselDiv) |
| 192 | + |
| 193 | + // Passive Voice |
| 194 | + const passiveDiv = document.createElement('div') |
| 195 | + passiveDiv.className = 'category' |
| 196 | + passiveDiv.innerHTML = ` |
| 197 | + <h2>Passive Voice</h2> |
| 198 | + ${passives.length === 0 ? 'No passive voice constructions found.' : |
| 199 | + passives.map(p => ` |
| 200 | + <div class="warning"> |
| 201 | + Found passive voice "<span class="highlight">${p.construction}</span>" in: "${p.context}" |
| 202 | + </div> |
| 203 | + `).join('')} |
| 204 | + ` |
| 205 | + resultsDiv.appendChild(passiveDiv) |
| 206 | + |
| 207 | + // Duplicate Words |
| 208 | + const duplicateDiv = document.createElement('div') |
| 209 | + duplicateDiv.className = 'category' |
| 210 | + duplicateDiv.innerHTML = ` |
| 211 | + <h2>Duplicate Words</h2> |
| 212 | + ${duplicates.length === 0 ? 'No duplicate words found.' : |
| 213 | + duplicates.map(d => ` |
| 214 | + <div class="warning"> |
| 215 | + Found duplicate word "<span class="highlight">${d.word}</span>" in: "${d.context}" |
| 216 | + </div> |
| 217 | + `).join('')} |
| 218 | + ` |
| 219 | + resultsDiv.appendChild(duplicateDiv) |
| 220 | +} |
| 221 | + |
| 222 | +// Set up event listener |
| 223 | +document.getElementById('input').addEventListener('input', (e) => { |
| 224 | + const text = e.target.value |
| 225 | + const weasels = findWeaselWords(text) |
| 226 | + const passives = findPassiveVoice(text) |
| 227 | + const duplicates = findDuplicateWords(text) |
| 228 | + displayResults(weasels, passives, duplicates) |
| 229 | +}) |
| 230 | +</script> |
| 231 | +</body> |
| 232 | +</html> |
0 commit comments