Changeset 3751971 for mxchat-basic/trunk/js/content-selector.js
- Timestamp:
- 10/17/2026 01:47:11 PM (13 days ago)
- File:
-
- 1 edited
-
mxchat-basic/trunk/js/content-selector.js (modified) (14 diffs)
Legend:
- Unmodified
- Added
- Removed
-
mxchat-basic/trunk/js/content-selector.js
r3724923 r3751971 36 36 $closeButtons.on('click', function() { 37 37 $modal.hide(); 38 // No word count keeps reading pages behind a closed modal (M6). 39 wordCountRun++; 40 wordQueue = []; 38 41 }); 39 42 // Handle import option box clicks (for non-WordPress options) … … 171 174 $loading.show(); 172 175 $contentList.find('.mxchat-kb-content-item').remove(); 176 wordCountRun++; // a word count still arriving for the previous list is dropped 177 wordQueue = []; 173 178 174 179 const data = { … … 199 204 if (items.length > 0) { 200 205 renderContentItems(items); 206 countPendingWords(items); 201 207 renderPagination(parseInt(response.data.current_page), parseInt(response.data.total_pages)); 202 208 … … 225 231 } 226 232 233 // Word counts the list could not show yet (M6): a page whose text the import reads from its 234 // published page is counted on the server after the list is shown, a few rows per request. 235 // One request is in flight at a time, across list reloads too (each count reads a page from 236 // this site, so small hosting plans are not asked for several at once). A newer list replaces 237 // the queue and an answer for an older list is dropped. A row keeps its saved count if 238 // counting fails. Counting pauses while selected content is imported, and the import waits 239 // for a count still in flight: on a host with two PHP workers, a count and an import that 240 // both read a page from the site would leave neither read a worker to answer it. 241 let wordCountRun = 0; 242 let wordQueue = []; 243 let wordBusy = false; 244 let wordPaused = false; 245 let wordIdle = []; 246 247 function pauseWordCounts(then) { 248 wordPaused = true; 249 if (wordBusy) { 250 wordIdle.push(then); 251 } else { 252 then(); 253 } 254 } 255 256 function resumeWordCounts() { 257 wordPaused = false; 258 if (!wordBusy) { 259 pumpWordCounts(); 260 } 261 } 262 263 function countingLabel() { 264 return (mxchatSelector.i18n && mxchatSelector.i18n.countingWords) || 'Counting words…'; 265 } 266 267 function showWords(id, words) { 268 const item = allItems.find(function(i) { return i.id === id; }); 269 if (item) { 270 if (words !== null && words !== undefined) { item.word_count = words; } 271 item.word_count_pending = false; 272 } 273 const shown = item ? item.word_count : words; 274 if (shown !== null && shown !== undefined) { 275 $contentList.find('.mxchat-kb-content-item[data-id="' + id + '"] .mxchat-kb-content-words').text(shown + ' words'); 276 } 277 } 278 279 function countPendingWords(items) { 280 wordQueue = items.filter(function(item) { return item.word_count_pending; }).map(function(item) { return item.id; }); 281 if (!wordBusy && !wordPaused) { 282 pumpWordCounts(); 283 } 284 } 285 286 function pumpWordCounts() { 287 if (wordPaused || wordQueue.length === 0) { 288 wordBusy = false; 289 const waiting = wordIdle; 290 wordIdle = []; 291 waiting.forEach(function(then) { then(); }); 292 return; 293 } 294 wordBusy = true; 295 const run = wordCountRun; 296 const batch = wordQueue.splice(0, 4); 297 $.ajax({ 298 url: mxchatSelector.ajaxurl, 299 method: 'POST', 300 dataType: 'json', 301 data: { action: 'mxchat_count_indexed_words', nonce: mxchatSelector.nonce, post_ids: batch } 302 }).done(function(response) { 303 if (run !== wordCountRun) { 304 return; 305 } 306 const counts = (response && response.success && response.data && response.data.counts) || null; 307 if (!counts) { 308 batch.forEach(function(id) { showWords(id, null); }); 309 return; 310 } 311 // Ids the server had no time for go back to the front of the queue. 312 const missed = []; 313 batch.forEach(function(id) { 314 if (Object.prototype.hasOwnProperty.call(counts, id)) { showWords(id, counts[id]); } else { missed.push(id); } 315 }); 316 if (missed.length === batch.length) { 317 missed.forEach(function(id) { showWords(id, null); }); // never ask again for nothing 318 } else { 319 wordQueue.unshift.apply(wordQueue, missed); 320 } 321 }).fail(function() { 322 if (run === wordCountRun) { 323 batch.forEach(function(id) { showWords(id, null); }); 324 } 325 }).always(pumpWordCounts); 326 } 327 227 328 // Helper function to display appropriate "no results" message 228 329 function displayNoResults(processedStatus) { … … 240 341 } 241 342 343 // Server text in the list (titles, excerpts, dates, labels, links) is shown as text, 344 // never parsed as markup (M7g). Titles and excerpts come with WordPress's display 345 // entities (’, &): those are decoded first, in an inert textarea, so a 346 // title reads exactly as it does in the Posts list. 347 const escapeText = function(text) { return $('<div>').text(String(text == null ? '' : text)).html().replace(/"/g, '"').replace(/'/g, '''); }; 348 const decodeEntities = function(text) { const area = document.createElement('textarea'); area.innerHTML = String(text == null ? '' : text); return area.value; }; 349 const plainText = function(text) { return escapeText(decodeEntities(text)); }; 350 242 351 // Render content items 243 352 function renderContentItems(items) { … … 264 373 <div class="mxchat-kb-content-details"> 265 374 <div class="mxchat-kb-content-title"> 266 <a href="${ item.permalink}" target="_blank">${item.title}</a>375 <a href="${escapeText(item.permalink)}" target="_blank">${plainText(item.title)}</a> 267 376 <span class="${badgeClass}">${badgeText}</span> 268 ${isProcessed ? '<span class="mxchat-kb-last-updated">Last updated: ' + item.processed_date+ '</span>' : ''}377 ${isProcessed ? '<span class="mxchat-kb-last-updated">Last updated: ' + plainText(item.processed_date) + '</span>' : ''} 269 378 </div> 270 379 <div class="mxchat-kb-content-meta"> 271 <span class="mxchat-kb-content-type">${ item.type_label || item.type}</span>272 <span class="mxchat-kb-content-date">${ item.date}</span>273 <span class="mxchat-kb-content-words">${ item.size_label ? item.size_label : item.word_count + ' words'}</span>380 <span class="mxchat-kb-content-type">${plainText(item.type_label || item.type)}</span> 381 <span class="mxchat-kb-content-date">${plainText(item.date)}</span> 382 <span class="mxchat-kb-content-words">${plainText(item.size_label ? item.size_label : (item.word_count_pending ? countingLabel() : item.word_count + ' words'))}</span> 274 383 </div> 275 <div class="mxchat-kb-content-excerpt">${ item.excerpt}</div>384 <div class="mxchat-kb-content-excerpt">${plainText(item.excerpt)}</div> 276 385 </div> 277 386 </div> … … 487 596 let updated = 0; 488 597 let failed = 0; 598 let skipped = 0; 489 599 const results = { 490 600 success: [], 491 601 updated: [], 492 failed: [] 602 failed: [], 603 skipped: [] 493 604 }; 605 // Strings for posts a developer filter leaves out (plan 9dd0da). Server text in the 606 // results (titles, reasons, failure messages) is always escaped (M7b). 607 const i18n = mxchatSelector.i18n || {}; 608 const escapeHtml = function(text) { return $('<div>').text(String(text)).html(); }; 494 609 495 610 // UPDATED: Get current bot_id for WordPress content processing … … 567 682 dataType: 'json', 568 683 success: function(response) { 569 if (response.success) { 684 if (response.success && response.data && response.data.skipped) { 685 // A developer filter excluded this post: a skip with its reason, not a failure. 686 skipped++; 687 results.skipped.push({ 688 id: postId, 689 title: response.data.title || ('ID: ' + postId), 690 reason: response.data.reason || '' 691 }); 692 $progressModal.find('.mxchat-kb-current-item').text( 693 (i18n.skippedItem || 'Skipped "%1$s": %2$s') 694 .replace('%1$s', response.data.title || ('ID: ' + postId)) 695 .replace('%2$s', response.data.reason || '') 696 ); 697 } else if (response.success) { 570 698 if (isUpdate) { 571 699 updated++; … … 627 755 // Remove progress modal 628 756 $progressModal.remove(); 757 resumeWordCounts(); 629 758 630 759 // Determine notification type based on results … … 634 763 } else if (failed > 0) { 635 764 notificationClass = processed > 0 || updated > 0 ? 'warning' : 'error'; 765 } else if (skipped > 0 && processed === 0 && updated === 0) { 766 notificationClass = 'info'; 636 767 } 637 768 … … 653 784 } else if (updated > 0) { 654 785 resultHTML += 'Updated ' + updated + ' items successfully'; 786 } else if (skipped > 0) { 787 resultHTML += escapeHtml((i18n.onlySkipped || 'Nothing was imported: %d skipped').replace('%d', skipped)); 655 788 } else { 656 789 resultHTML += 'No items were processed successfully'; 657 790 } 658 791 792 if (skipped > 0 && (wasAborted || processed > 0 || updated > 0)) { 793 resultHTML += ', ' + escapeHtml((i18n.skippedCount || '%d skipped').replace('%d', skipped)); 794 } 795 659 796 if (failed > 0 && !wasAborted) { 660 797 resultHTML += ' with ' + failed + ' failures'; … … 663 800 resultHTML += '</h4>'; 664 801 665 // Add details if there were failures 802 // Add details if there were failures or skips 803 if (failed > 0 || skipped > 0) { 804 resultHTML += '<div class="mxchat-kb-results-details">'; 805 } 666 806 if (failed > 0) { 667 resultHTML += '<div class="mxchat-kb-results-details">';668 807 resultHTML += '<h5>Failed Items:</h5><ul>'; 669 808 670 809 results.failed.forEach(function(item) { 671 resultHTML += '<li><strong>ID: ' + item.id + '</strong>: ' + item.error+ '</li>';810 resultHTML += '<li><strong>ID: ' + escapeHtml(item.id) + '</strong>: ' + escapeHtml(item.error) + '</li>'; 672 811 }); 673 812 674 resultHTML += '</ul></div>'; 813 resultHTML += '</ul>'; 814 } 815 if (skipped > 0) { 816 resultHTML += '<h5 class="mxchat-kb-results-skipped">' + escapeHtml(i18n.skippedHeading || 'Skipped') + '</h5><ul>'; 817 results.skipped.forEach(function(item) { 818 resultHTML += '<li><strong>' + escapeHtml(item.title) + '</strong>: ' + escapeHtml(item.reason) + '</li>'; 819 }); 820 resultHTML += '</ul>'; 821 } 822 if (failed > 0 || skipped > 0) { 823 resultHTML += '</div>'; 675 824 } 676 825 … … 704 853 } 705 854 706 // Start processing the first post 707 processNext(0); 855 // Start processing the first post once no word count is reading a page (M6) 856 pauseWordCounts(function() { 857 processNext(0); 858 }); 708 859 }); 709 860 … … 836 987 ((typeof mxchatPromptsAdmin !== 'undefined' && mxchatPromptsAdmin.prompts_setting_nonce) ? mxchatPromptsAdmin.prompts_setting_nonce : ''); 837 988 838 function escapeText(text) { return $('<div>').text(String(text == null ? '' : text)).html() ; }989 function escapeText(text) { return $('<div>').text(String(text == null ? '' : text)).html().replace(/"/g, '"').replace(/'/g, '''); } 839 990 function notice(kind, text) { 840 991 $result.html('<div class="mxch-notice mxch-notice-' + kind + '" style="margin: 12px 0;"><span>' + escapeText(text) + '</span></div>').show();
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)