1jQuery(function($) { 2 // Initialize data 3 const query_categories = autocompleteData.query_categories; 4 const query_words = autocompleteData.query_words; 5 6 // Prepare searchable data 7 const searchableData = query_categories.reduce((acc, category) => { 8 query_words[category].forEach(([word, ...readings]) => { 9 const searchTerms = [word.toLowerCase(), ...readings.map(r => r.toLowerCase())]; 10 searchTerms.forEach(term => { 11 if (!acc[term]) acc[term] = []; 12 acc[term].push({ word, category }); 13 }); 14 }); 15 return acc; 16 }, {}); 17 18 // Cache for AJAX results 19 const ajaxCache = new Map(); 20 let ajaxCallCount = 0; 21 const MAX_AJAX_CALLS = 20; // Maximum number of queries to the AJAX API 22 const $target = $("#si-q"); 23 let composition = false; 24 25 $target.autocomplete({ 26 minLength: 1, 27 search: function(event, ui) { 28 return !composition; 29 }, 30 source: function(request, response) { 31 const terms = zenNormalizeString(request.term).split(/\s+/); 32 const lastTerm = terms[terms.length - 1].toLowerCase(); 33 const existingCategories = new Set(); 34 35 // Check for existing words and their categories 36 const existingWords = []; 37 for (let i = 0; i < terms.length - 1; i++) { 38 const word = terms[i].toLowerCase(); 39 existingWords.push(terms[i]); 40 for (const category of query_categories) { 41 if (query_words[category].some(([w]) => w.toLowerCase() === word)) { 42 existingCategories.add(category); 43 break; 44 } 45 } 46 } 47 48 // let candidates = []; 49 let candidate_words = []; 50 51 if (false) { // B 52 for (const category of query_categories) { 53 if (!existingCategories.has(category)) { 54 const results = Object.keys(searchableData) 55 .filter(key => key.startsWith(lastTerm)) 56 .flatMap(key => searchableData[key]) 57 .filter(item => item.category === category); 58 if (results.length > 0) { 59 // candidates = results; 60 candidate_words = results.map(c => c.word); 61 candidate_words = [...new Set(candidate_words)]; // make unique 62 candidate_words = candidate_words.slice(0, 10); // only the first N cases 63 break; 64 } 65 } 66 } 67 } else { // A 68 for (const category of query_categories) { 69 if (!existingCategories.has(category)) { 70 const results = Object.keys(searchableData) 71 .filter(key => key.startsWith(lastTerm)) 72 .flatMap(key => searchableData[key]) 73 .filter(item => item.category === category); 74 75 if (results.length > 0) { 76 candidate_words.push(...new Set(results.slice(0, 1).map(c => c.word))); 77 } 78 } 79 } 80 } 81 82 if (ajaxCallCount >= MAX_AJAX_CALLS) { 83 // response(candidates.map(c => c.word)); 84 response(candidate_words); 85 return; 86 } 87 88 if (! candidate_words.length) { 89 response([]); 90 return; 91 } 92 93 // Prepare query for AJAX 94 const ajaxQuery = { 95 existingWords: existingWords, 96 candidates: candidate_words 97 }; 98 99 const cacheKey = JSON.stringify(ajaxQuery); 100 101 if (ajaxCache.has(cacheKey)) { 102 filterAndRespond(ajaxCache.get(cacheKey)); 103 } else if (existingWords.length < 1) { // Leading words are not queried in AJAX. 104 filterAndRespond({ 105 validWords: candidate_words 106 }); 107 } else { 108 console.log("acr", existingWords.length, candidate_words.length, request.term); 109 110 $.ajax({
111 url: autocompleteData.root + 'rstone/v1/autocomplete', 112 method: 'POST', 113 data: { 114 action: 'autocomplete_query', 115 ex: existingWords.join(" "), 116 ca: candidate_words.join(" ") 117 }, 118 beforeSend: function(xhr) { 119 xhr.setRequestHeader('X-WP-Nonce', autocompleteData.nonce); 120 } 121 }) 122 .done(function(data, textStatus, jqXHR) { 123 ajaxCallCount++; 124 ajaxCache.set(cacheKey, data); 125 filterAndRespond(data); 126 }) 127 .fail(function(jqXHR, textStatus, errorThrown) { 128 console.error('AJAX error:', textStatus, errorThrown); 129 response([]); 130 }); 131 } 132 133 function filterAndRespond(data) { 134 const validWords = new Set(data.validWords.map(w => w.toLowerCase())); 135 console.log("filterAndRespond", validWords); 136 response(Array.from(validWords)); 137 } 138 }, 139 select: function(event, ui) { 140 const terms = this.value.split(/\s+/); 141 terms.pop(); 142 terms.push(ui.item.value); 143 this.value = terms.join(' ') + ' '; 144 145 // Displays suggestions for the next word after selection. 146 setTimeout(() => { 147 $(this).autocomplete('search', this.value); 148 }, 0); 149 150 return false; 151 } 152 }) 153 .on('compositionstart', function(event) { 154 composition = true; 155 $target.autocomplete('disable'); 156 }) 157 .on('compositionend', function(event) { 158 composition = false; 159 $target.autocomplete('enable').autocomplete('search'); 160 }); 161 ; 162 163 function zenNormalizeString(str) { 164 // å ¨è§ã¹ãã¼ã¹ãªã©ã®ç©ºç½æåãåè§ã¹ãã¼ã¹1ã¤ã«ç½®æ 165 str = str.replace(/\s/g, ' '); 166 167 // å ¨è§è±æ°åãåè§è±æ°åã«ç½®æ 168 str = str.replace(/[A-Zï½-ï½ï¼-ï¼]/g, function(s) { 169 return String.fromCharCode(s.charCodeAt(0) - 0xFEE0); 170 }); 171 172 return str; 173 } 174});
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.