1/** 2 * åè¯å¼¹çªæ¥è¯¢æ¨¡åï¼æ¯æä¸çè¯è¯å°¾åå + å¼¹çªå é«äº®æ»å¨ + ææºç«¯è§¦æ¸ï¼ 3 * é»è¾ï¼ç´æ¥æ¥word â ç´æ¥æ¥pais â è¯å°¾å¤çæ¥word â è¯å°¾å¤çæ¥pais â å 鳿¿æ¢æ¥word â å 鳿¿æ¢æ¥pais â æªæ¾å° 4 */ 5(function() { 6 'use strict'; 7 if (window._lookupPopupLoaded) return; 8 window._lookupPopupLoaded = true; 9 10 // ==================== å¼¹çª DOM ==================== 11 var popup = document.createElement('div'); 12 popup.id = 'lookupPopup'; 13 popup.style.cssText = [ 14 'display:none','position:fixed','z-index:99999','background:#fff', 15 'border:2px solid #006400','border-radius:8px','box-shadow:0 4px 20px rgba(0,0,0,0.3)', 16 'max-width:480px','max-height:380px','overflow:auto','font-family:monospace', 17 'font-size:14px','line-height:1.6' 18 ].join(';'); 19 20 var header = document.createElement('div'); 21 header.style.cssText = [ 22 'background:#006400','color:#fff','padding:8px 12px','font-weight:bold', 23 'font-size:16px','display:flex','justify-content:space-between', 24 'align-items:center','border-radius:6px 6px 0 0','position:sticky', 25 'top:0','z-index:1' 26 ].join(';'); 27 28 var title = document.createElement('span'); 29 title.id = 'lookupPopupTitle'; 30 title.textContent = 'å¿«éæ¥è¯¢'; 31 32 var closeBtn = document.createElement('span'); 33 closeBtn.innerHTML = '✖'; 34 closeBtn.style.cssText = 'cursor:pointer;font-size:16px;padding:2px 6px;border-radius:4px;'; 35 closeBtn.onmouseover = function() { this.style.background = 'rgba(255,255,255,0.2)'; }; 36 closeBtn.onmouseout = function() { this.style.background = 'transparent'; }; 37 closeBtn.onclick = hidePopup; 38 39 header.appendChild(title); 40 header.appendChild(closeBtn); 41 42 var content = document.createElement('div'); 43 content.id = 'lookupPopupContent'; 44 content.style.cssText = 'padding:12px;'; 45 46 popup.appendChild(header); 47 popup.appendChild(content); 48 document.body.appendChild(popup); 49 50 // ==================== äºä»¶ ==================== 51 document.addEventListener('mousedown', function(e) { 52 if (popup.style.display === 'block' && !popup.contains(e.target)) hidePopup(); 53 }); 54 document.addEventListener('keydown', function(e) { 55 if (e.key === 'Escape') hidePopup(); 56 }); 57 58 function hidePopup() { 59 popup.style.display = 'none'; 60 window.getSelection().removeAllRanges(); 61 } 62 63 // ==================== éè¯ ==================== 64 function getSelectedWord() { 65 var sel = window.getSelection(); 66 if (!sel || sel.rangeCount === 0) return null; 67 var text = sel.toString().trim(); 68 if (!text) return null; 69 if (/\s/.test(text)) return null; 70 if (text.length < 1 || text.length > 35) return null; 71 text = text.replace(/^[.,;:!?()""«»â\-\/\\[\]{}|]+|[.,;:!?()""«»â\-\/\\[\]{}|]+$/g, ''); 72 if (!text || /\s/.test(text) || text.length < 1) return null; 73 return text; 74 } 75 76 // ==================== çå¬ webBrowserï¼æ¡é¢ + ææºï¼==================== 77 function initWebBrowserListener() { 78 var webBrowser = document.getElementById('webBrowser'); 79 if (!webBrowser) { setTimeout(initWebBrowserListener, 500); return; } 80 81 // æ¡é¢ç«¯ï¼é¼ æ æ¾å¼ 82 webBrowser.addEventListener('mouseup', function(e) { 83 if (popup.contains(e.target)) return; 84 setTimeout(function() { 85 var word = getSelectedWord(); 86 if (!word) return; 87 lookup(word, e.clientX, e.clientY); 88 }, 10); 89 }); 90 91 // ç§»å¨ç«¯ï¼è§¦æ¸ç»æï¼ææéè¯åæ¾æï¼ 92 webBrowser.addEventListener('touchend', function(e) { 93 if (popup.contains(e.target)) return; 94 setTimeout(function() { 95 var word = getSelectedWord(); 96 if (!word) return; 97 var touch = e.changedTouches[0]; 98 lookup(word, touch.clientX, touch.clientY); 99 }, 100); 100 }); 101 } 102 initWebBrowserListener(); 103 104 // ==================== ä¸çè¯è¯å°¾å¤ç ==================== 105 function removeSuffix(word, suffixes) { 106 for (var i = 0; i < suffixes.length; i++) {
107 if (word.endsWith(suffixes[i])) return word.slice(0, -suffixes[i].length); 108 } 109 return word; 110 } 111 112 /** å¤çè¯å°¾ï¼å» j/n/jnï¼åè¯/åä½åç¼æ¿æ¢ä¸º i */ 113 function processWordEnding(input) { 114 var ignoreSuffixes = ['jn', 'j', 'n']; 115 var replaceSuffixes = ['anta','ante','anto','inta','inte','into','onta','onte','onto', 116 'ata','ate','ato','ita','ite','ito','ota','ote','oto', 117 'as','is','os','us','u']; 118 var w = input.toLowerCase(); 119 w = removeSuffix(w, ignoreSuffixes); 120 for (var i = 0; i < replaceSuffixes.length; i++) { 121 if (w.endsWith(replaceSuffixes[i])) { 122 w = w.slice(0, -replaceSuffixes[i].length) + 'i'; 123 break; 124 } 125 } 126 return w; 127 } 128 129 /** å 鳿¿æ¢ï¼å¦ amikini â amikina/amikine/amikino */ 130 function getVowelReplacements(input) { 131 if (input.length <= 3) return []; 132 var lastChar = input[input.length - 1]; 133 var v = lastChar.toLowerCase(); 134 if (v !== 'a' && v !== 'e' && v !== 'i' && v !== 'o') return []; 135 var baseStr = input.slice(0, -1); 136 var all = ['a','e','i','o']; 137 return all.filter(function(x) { return x !== v; }).map(function(x) { return baseStr + x; }); 138 } 139 140 // ==================== æ¥è¯¢æµç¨ï¼wordä¼å â paiså åº ==================== 141 function lookup(word, mouseX, mouseY) { 142 title.textContent = 'æ¥è¯¢: ' + word; 143 content.innerHTML = '<div style="color:#999;padding:20px;text-align:center;">æ¥è¯¢ä¸...</div>'; 144 showPopup(mouseX, mouseY); 145 146 // 1. ç´æ¥æ¥ word 147 doExactSearchWord(word, function(wordResults) { 148 if (wordResults && wordResults.length > 0) { 149 displayResults(wordResults, word, null, false); 150 return; 151 } 152 153 // 2. ç´æ¥æ¥ pais 154 doExactSearchPais(word, function(paisResults) { 155 if (paisResults && paisResults.length > 0) { 156 displayResults(paisResults, word, null, true); 157 return; 158 } 159 160 // 3. è¯å°¾å¤ç 161 var processed = processWordEnding(word); 162 if (processed && processed !== word.toLowerCase()) { 163 // 3a. è¯å°¾å¤çæ¥ word 164 doExactSearchWord(processed, function(wordResults2) { 165 if (wordResults2 && wordResults2.length > 0) { 166 displayResults(wordResults2, word, processed, false); 167 return; 168 } 169 170 // 3b. è¯å°¾å¤çæ¥ pais 171 doExactSearchPais(processed, function(paisResults2) { 172 if (paisResults2 && paisResults2.length > 0) { 173 displayResults(paisResults2, word, processed, true); 174 return; 175 } 176 177 // 4. å 鳿¿æ¢æ¥ 178 tryVowelSearch(word, processed, function(results, candidate, isPais) { 179 if (results && results.length > 0) { 180 displayResults(results, word, candidate, isPais); 181 } else { 182 showNotFound(word); 183 } 184 }); 185 }); 186 }); 187 } else { 188 // è¯å°¾æ ååï¼ç´æ¥å°è¯å 鳿¿æ¢ 189 tryVowelSearch(word, word, function(results, candidate, isPais) { 190 if (results && results.length > 0) { 191 displayResults(results, word, candidate, isPais); 192 } else { 193 showNotFound(word); 194 } 195 }); 196 } 197 }); 198 }); 199 } 200 201 /** å 鳿¿æ¢æ¥è¯¢ï¼ä¾æ¬¡å°è¯ a/e/i/o å ¶ä»å é³ï¼æ¯ä¸ªåéå æ¥word忥pais */ 202 function tryVowelSearch(original, baseWord, callback) { 203 var candidates = getVowelReplacements(baseWord); 204 if (candidates.length === 0) { callback(null, null, false); return; } 205 var index = 0; 206 207 function tryNext() { 208 if (index >= candidates.length) { callback(null, null, false); return; } 209 var candidate = candidates[index++]; 210 211 // å æ¥ word 212 doExactSearchWord(candidate, function(wordResults) { 213 if (wordResults && wordResults.length > 0) { 214 callback(wordResults, candidate, false); 215 return; 216 } 217 // 忥 pais 218 doExactSearchPais(candidate, function(paisResults) { 219 if (paisResults && paisResults.length > 0) { 220 callback(paisResults, candidate, true); 221 return; 222 } 223 tryNext(); 224 }); 225 }); 226 } 227 tryNext(); 228 } 229 230 function doExactSearchWord(term, callback) { 231 fetch('http://' + window.location.host + '/exactSearchWord/' + encodeURIComponent(term)) 232 .then(function(r) { return r.json(); }) 233 .then(function(data) { 234 callback(data && data.length > 0 ? data : null); 235 }) 236 .catch(function() { callback(null); }); 237 } 238 239 function doExactSearchPais(term, callback) { 240 fetch('http://' + window.location.host + '/exactSearchPais/' + encodeURIComponent(term)) 241 .then(function(r) { return r.json(); }) 242 .then(function(data) { 243 callback(data && data.length > 0 ? data : null); 244 }) 245 .catch(function() { callback(null); }); 246 } 247 248 function showNotFound(word) {
249 title.textContent = 'æ¥è¯¢: ' + word; 250 content.innerHTML = '<div style="color:#c00;padding:20px;text-align:center;">æªæ¾å°: <b>' + escapeHtml(word) + '</b></div>'; 251 } 252 253 // ==================== æ¾ç¤ºç»æï¼æ¯æå¤ç»æé¡ºåºç½åï¼==================== 254 function displayResults(items, originalWord, processedWord, isPaisMatch) { 255 if (!items || items.length === 0) { 256 showNotFound(originalWord); 257 return; 258 } 259 260 if (processedWord && processedWord !== originalWord.toLowerCase()) { 261 title.textContent = 'æ¥è¯¢: ' + originalWord + ' â ' + processedWord + (isPaisMatch ? ' (pais)' : ''); 262 } else { 263 title.textContent = 'æ¥è¯¢: ' + originalWord + (isPaisMatch ? ' (pais)' : ''); 264 } 265 266 var html = ''; 267 items.forEach(function(item, idx) { 268 if (idx > 0) { 269 html += '<div style="border-top:1px solid #ddd;margin:8px 0;"></div>'; 270 } 271 html += '<div style="margin-bottom:8px;">'; 272 html += '<div style="font-size:18px;font-weight:bold;color:#006400;margin-bottom:4px;">' + escapeHtml(item.word) + '</div>'; 273 html += '<div class="popupDefinition" style="padding-top:4px;">' + item.definition + '</div>'; 274 html += '</div>'; 275 }); 276 277 content.innerHTML = html; 278 279 setTimeout(function() { 280 processPopupAudioLinks(); 281 if (typeof window.addAiSpeakButtons === 'function') window.addAiSpeakButtons(); 282 if (isPaisMatch) { 283 scrollToHighlightInPopup(); 284 } 285 }, 80); 286 } 287 288 /** åªç²¾ç¡®æ»å¨å°ç²çº¢é«äº®ç®æ ï¼æ¯æå¤ç»æï¼ */ 289 function scrollToHighlightInPopup() { 290 var defDivs = content.querySelectorAll('.popupDefinition'); 291 if (!defDivs.length) return; 292 293 for (var i = 0; i < defDivs.length; i++) { 294 var highlight = defDivs[i].querySelector('span[style*="#FF00FF"], span[style*="FF00FF"], b[style*="#FF00FF"], strong[style*="#FF00FF"]'); 295 if (highlight) { 296 highlight.scrollIntoView({ behavior: 'smooth', block: 'center' }); 297 var originalBg = highlight.style.backgroundColor; 298 highlight.style.backgroundColor = 'rgba(255,0,255,0.2)'; 299 highlight.style.transition = 'background-color 0.5s'; 300 (function(h, bg) { 301 setTimeout(function() { 302 h.style.backgroundColor = bg || 'transparent'; 303 }, 1500); 304 })(highlight, originalBg); 305 break; 306 } 307 } 308 } 309 310 function processPopupAudioLinks() { 311 var links = content.querySelectorAll('a[href$=".mp3"], a[href$=".MP3"]'); 312 links.forEach(function(link) { 313 if (link._popupAudioBound) return; 314 link._popupAudioBound = true; 315 link.addEventListener('click', function(e) { 316 e.preventDefault(); 317 e.stopPropagation(); 318 var href = this.getAttribute('href'); 319 if (!href) return; 320 if (typeof window.playAudioWithoutControls === 'function') { 321 window.playAudioWithoutControls(href); 322 } else if (typeof window.playAudioWithControls === 'function') { 323 window.playAudioWithControls(href); 324 } 325 }); 326 }); 327 } 328 329 function showPopup(x, y) { 330 popup.style.display = 'block'; 331 var rect = popup.getBoundingClientRect(); 332 var winW = window.innerWidth, winH = window.innerHeight; 333 var left = x + 15, top = y + 15; 334 if (left + rect.width > winW - 10) left = x - rect.width - 15; 335 if (top + rect.height > winH - 10) top = y - rect.height - 15; 336 if (left < 10) left = 10; 337 if (top < 10) top = 10; 338 popup.style.left = left + 'px'; 339 popup.style.top = top + 'px'; 340 } 341 342 function escapeHtml(text) { 343 if (!text) return ''; 344 var div = document.createElement('div'); 345 div.textContent = text; 346 return div.innerHTML; 347 } 348 349 console.log('åè¯å¼¹çªæ¥è¯¢æ¨¡åï¼wordâpais ä¼å 级 + å¤ç»æç½åï¼å·²å è½½'); 350})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.