Jump to content
Wikipedia The Free Encyclopedia

User:Polygnotus/Scripts/WordCount.js

From Wikipedia, the free encyclopedia
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/WordCount.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
 // Wikipedia Talk Page Participation Analyzer
 // Only runs on talk pages and adds participation analysis links to section headers
 // This ignores text in links

 (function(){
 'use strict';

 // Only run on talk pages
 if(!mw.config.get('wgCanonicalNamespace')||
 !mw.config.get('wgCanonicalNamespace').toLowerCase().includes('talk')){
 return;
 }

 // Function to count words in text (excluding links, timestamps, and other markup)
 functioncountWords(text){
 if(!text)return0;

 // Create a temporary element to parse HTML and extract text without signature elements
 consttempDiv=document.createElement('div');
 tempDiv.innerHTML=text;

 // Remove all signature-related elements
 constelementsToRemove=tempDiv.querySelectorAll(
 'a, '+// All links
 '.ext-discussiontools-init-timestamplink, '+// Timestamp links
 '.ext-discussiontools-init-replylink-buttons, '+// Reply buttons
 '[data-mw-comment-sig], '+// Signature markers
 '.mw-editsection, '+// Edit section links
 '.ext-discussiontools-init-section-subscribe'// Subscribe links
 );
 elementsToRemove.forEach(el=>el.remove());

 // Get plain text content
 letplainText=tempDiv.textContent||tempDiv.innerText||'';

 // Remove common timestamp patterns and other signature artifacts
 plainText=plainText
 .replace(/\d{2}:\d{2},\s*\d{1,2}\s+\w+\s+\d{4}\s*\(UTC\)/g,'')// Timestamps like "12:35, 1 September 2025 (UTC)"
 .replace(/\(\s*talk\s*\)/gi,'')// (talk) links
 .replace(/\[\s*reply\s*\]/gi,'')// [reply] buttons
 .replace(/\[\s*edit\s*\]/gi,'')// [edit] buttons
 .replace(/\[\s*subscribe\s*\]/gi,'')// [subscribe] buttons
 .replace(/\[\s*unsubscribe\s*\]/gi,'')// [unsubscribe] buttons
 .replace(/📌|🔗/g,'')// Emoji icons
 .trim();

 // Debug: log what we're counting
 console.log('Text being counted:',JSON.stringify(plainText));
 constwords=plainText.split(/\s+/).filter(word=>
 word.length>0&&
 word!=='()'&&// Filter out empty parentheses
 word!=='[]'&&// Filter out empty brackets
 !/^\(\s*\)$/.test(word)&&// Filter out parentheses with only whitespace
 !/^\[\s*\]$/.test(word)// Filter out brackets with only whitespace
 );
 console.log('Words found:',words);
 console.log('Word count:',words.length);

 // Count words in the cleaned text
 returnwords.length;
 }

 // Function to extract username from user page link
 functionextractUsername(href){
 constuserMatch=href.match(/\/wiki\/User:([^\/]+)$/);
 if(userMatch){
 returndecodeURIComponent(userMatch[1].replace(/_/g,' '));
 }
 returnnull;
 }

 // Function to analyze participation in a section
 functionanalyzeSection(sectionElement){
 constuserStats={};

 // Look for DiscussionTools comment markers
 constcommentElements=sectionElement.querySelectorAll('[data-mw-comment-start]');

 if(commentElements.length>0){
 // DiscussionTools format - analyze each comment
 commentElements.forEach(commentStart=>{
 constcommentId=commentStart.getAttribute('data-mw-comment-start')||commentStart.id;

 // Skip section headers (they start with 'h-'), only process comments (start with 'c-')
 if(commentId.startsWith('h-')){
 return;
 }

 // Find the corresponding comment end element
 letcommentEnd=null;
 constallElements=sectionElement.querySelectorAll('*');
 for(letelofallElements){
 if(el.getAttribute('data-mw-comment-end')===commentId){
 commentEnd=el;
 break;
 }
 }

 if(commentEnd){
 // Extract comment content between start and end, excluding signature elements
 letcommentContent='';
 letcurrentNode=commentStart.nextSibling;

 while(currentNode&&currentNode!==commentEnd){
 if(currentNode.nodeType===Node.TEXT_NODE){
 commentContent+=currentNode.textContent;
 }elseif(currentNode.nodeType===Node.ELEMENT_NODE){
 // Skip signature elements completely
 if(!currentNode.classList.contains('ext-discussiontools-init-timestamplink')&&
 !currentNode.hasAttribute('data-mw-comment-sig')&&
 !currentNode.classList.contains('ext-discussiontools-init-replylink-buttons')&&
 currentNode.tagName!=='A'){// Skip all anchor tags
 commentContent+=currentNode.textContent;
 }
 }
 currentNode=currentNode.nextSibling;
 }

 // Clean the extracted content further
 commentContent=commentContent
 .replace(/\d{2}:\d{2},\s*\d{1,2}\s+\w+\s+\d{4}\s*\(UTC\)/g,'')// Remove timestamps
 .replace(/\(\s*talk\s*\)/gi,'')// Remove (talk)
 .replace(/\[\s*reply\s*\]/gi,'')// Remove [reply]
 .trim();

 console.log('Raw comment content:',JSON.stringify(commentContent));

 // Extract username from comment ID (format: c-Username-timestamp-...)
 letusername=null;
 constidMatch=commentId.match(/[ch]-([^-]+)-/);
 if(idMatch){
 username=decodeURIComponent(idMatch[1].replace(/_/g,' '));
 }

 // If we couldn't get username from ID, look for signature links
 if(!username){
 constparentElement=commentStart.parentElement;
 if(parentElement){
 constuserLinks=parentElement.querySelectorAll('a[href*="/wiki/User:"]');
 if(userLinks.length>0){
 username=extractUsername(userLinks[userLinks.length-1].getAttribute('href'));
 }
 }
 }

 if(username&&commentContent.trim()){
 if(!userStats[username]){
 userStats[username]={words:0,contributions:0};
 }
 userStats[username].words+=countWords(commentContent);
 userStats[username].contributions++;
 }
 }
 });
 }else{
 // Fallback: traditional method for non-DiscussionTools pages
 constuserLinks=sectionElement.querySelectorAll('a[href*="/wiki/User:"]');
 constvalidUserLinks=[];

 // Filter to only direct user page links (not subpages)
 userLinks.forEach(link=>{
 consthref=link.getAttribute('href');
 if(href.match(/\/wiki\/User:[^\/]+$/)&&!href.includes('/')){
 constusername=extractUsername(href);
 if(username){
 validUserLinks.push({element:link,username:username});
 }
 }
 });

 if(validUserLinks.length===0){
 return{participants:0,stats:[]};
 }

 // Get all text and attribute to users based on proximity to signatures
 consttextWalker=document.createTreeWalker(
 sectionElement,
 NodeFilter.SHOW_TEXT,
 null,
 false
 );

 lettextNode;
 while(textNode=textWalker.nextNode()){
 consttext=textNode.textContent.trim();
 if(text&&text.length>0){
 // Find nearest user signature
 letcurrentElement=textNode.parentElement;
 letfoundUser=null;

 while(currentElement&&currentElement!==sectionElement){
 constnearbyUserLinks=currentElement.querySelectorAll('a[href*="/wiki/User:"]');
 if(nearbyUserLinks.length>0){
 constusername=extractUsername(nearbyUserLinks[nearbyUserLinks.length-1].getAttribute('href'));
 if(username){
 foundUser=username;
 break;
 }
 }
 currentElement=currentElement.previousElementSibling||currentElement.parentElement;
 }

 if(foundUser){
 if(!userStats[foundUser]){
 userStats[foundUser]={words:0,contributions:0};
 }
 userStats[foundUser].words+=countWords(text);
 userStats[foundUser].contributions++;
 }
 }
 }
 }

 // Convert to sorted array
 conststatsArray=Object.entries(userStats)
 .map(([username,stats])=>({
 username:username,
 words:stats.words,
 contributions:stats.contributions
 }))
 .sort((a,b)=>b.words-a.words);

 return{
 participants:statsArray.length,
 stats:statsArray
 };
 }

 // Function to create and show popup with OOUI
 functionshowParticipationPopup(sectionTitle,analysisResult){
 mw.loader.using(['oojs-ui-core','oojs-ui-windows','oojs-ui-widgets']).then(function(){
 const{participants,stats}=analysisResult;

 letcontent=`<div style="font-family: sans-serif; max-height: 400px; overflow-y: auto; padding: 10px;">`;
 content+=`<h3 style="margin-top: 0; margin-bottom: 15px; font-size: 16px; line-height: 1.2;">Section: ${sectionTitle}</h3>`;
 content+=`<p style="margin-bottom: 15px;"><strong>Participants: ${participants}</strong></p>`;

 if(stats.length>0){
 content+=`<table style="width: 100%; border-collapse: collapse; margin-top: 10px; table-layout: fixed;">`;
 content+=`<tr style="background-color: #f0f0f0; border-bottom: 1px solid #ccc;">
  <th style="padding: 8px; text-align: left; width: 40%;">User</th>
  <th style="padding: 8px; text-align: right; width: 30%;">Words</th>
  <th style="padding: 8px; text-align: right; width: 30%;">Contributions</th>
  </tr>`;

 stats.forEach((stat,index)=>{
 constrowStyle=index%2===0?'background-color: #f9f9f9;':'';
 content+=`<tr style="${rowStyle} border-bottom: 1px solid #eee;">
  <td style="padding: 8px; font-weight: bold; word-wrap: break-word;">${mw.html.escape(stat.username)}</td>
  <td style="padding: 8px; text-align: right;">${stat.words.toLocaleString()}</td>
  <td style="padding: 8px; text-align: right;">${stat.contributions}</td>
  </tr>`;
 });

 content+=`</table>`;
 }else{
 content+=`<p><em>No user contributions detected in this section.</em></p>`;
 }

 content+=`</div>`;

 // Create custom dialog class
 functionParticipationDialog(config){
 ParticipationDialog.parent.call(this,config);
 }
 OO.inheritClass(ParticipationDialog,OO.ui.MessageDialog);

 ParticipationDialog.static.name='participationDialog';
 ParticipationDialog.static.title='Talk Page Participation Analysis';
 ParticipationDialog.static.actions=[
 {action:'close',label:'Close',flags:['primary']}
 ];

 ParticipationDialog.prototype.getBodyHeight=function(){
 return450;
 };

 ParticipationDialog.prototype.initialize=function(){
 ParticipationDialog.parent.prototype.initialize.apply(this,arguments);
 this.content=newOO.ui.PanelLayout({padded:true,expanded:false});
 this.content.$element.html(content);
 this.$body.append(this.content.$element);
 };

 constwindowManager=newOO.ui.WindowManager();
 $('body').append(windowManager.$element);

 constdialog=newParticipationDialog();
 windowManager.addWindows([dialog]);

 windowManager.openWindow(dialog);
 });
 }

 // Main function to add links to section headers
 functionaddSectionLinks(){
 // Handle both standard H2 tags and DiscussionTools sections
 constsectionHeaders=document.querySelectorAll('h2, .mw-heading h2');

 sectionHeaders.forEach((header,index)=>{
 // Extract section title from various possible structures
 letsectionTitle;
 constheadlineSpan=header.querySelector('.mw-headline');
 if(headlineSpan){
 // Standard structure with .mw-headline
 sectionTitle=headlineSpan.textContent.trim();
 }else{
 // DiscussionTools structure - look for comment-end span which contains the title
 constcommentEnd=header.querySelector('[data-mw-comment-end]');
 if(commentEnd){
 sectionTitle=commentEnd.textContent.trim();
 }else{
 // Fallback: clean up the full header text
 sectionTitle=header.textContent
 .replace('[analyze]','')
 .replace(/\[edit\]/g,'')
 .replace(/\[subscribe\]/g,'')
 .replace(/📌/g,'')
 .replace(/🔗/g,'')
 .trim();
 }
 }

 if(!sectionTitle){
 sectionTitle=`Section ${index+1}`;
 }

 // Skip if already has analysis link
 if(header.querySelector('a[title*="Analyze participation"]')){
 return;
 }

 // Create analysis link
 constanalysisLink=document.createElement('a');
 analysisLink.href='#';
 analysisLink.textContent='[analyze]';
 analysisLink.style.marginLeft='10px';
 analysisLink.style.fontSize='0.8em';
 analysisLink.style.color='#0645ad';
 analysisLink.title='Analyze participation in this section';

 analysisLink.addEventListener('click',function(e){
 e.preventDefault();

 // Find the section content - handle both standard and DiscussionTools structure
 letsectionContent=document.createElement('div');

 // Check if this is a DiscussionTools section (parent has mw-heading class)
 constheadingContainer=header.closest('.mw-heading');
 if(headingContainer){
 // DiscussionTools: find content after the entire heading container
 letcurrentElement=headingContainer.nextElementSibling;

 while(currentElement&&!currentElement.querySelector('h2')&&
 !currentElement.classList.contains('mw-heading')){
 sectionContent.appendChild(currentElement.cloneNode(true));
 currentElement=currentElement.nextElementSibling;
 }
 }else{
 // Standard structure: find content after the h2
 letcurrentElement=header.nextElementSibling;

 while(currentElement&&currentElement.tagName!=='H2'){
 sectionContent.appendChild(currentElement.cloneNode(true));
 currentElement=currentElement.nextElementSibling;
 }
 }

 constanalysisResult=analyzeSection(sectionContent);
 showParticipationPopup(sectionTitle,analysisResult);
 });

 // Add the link to the header
 header.appendChild(analysisLink);
 });
 }

 // Initialize when DOM is ready
 if(document.readyState==='loading'){
 document.addEventListener('DOMContentLoaded',addSectionLinks);
 }else{
 addSectionLinks();
 }

 })();

AltStyle によって変換されたページ (->オリジナル) /