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.
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.
// Only run on the watchlist pageif(window.___location.href.includes('wikipedia.org/wiki/Special:Watchlist')){// Main function to create and show the UIfunctioninitDiscussionToolsManager(){// Create main container with better stylingconstcontainer=document.createElement('div');container.id='discussion-tools-manager';container.style.margin='20px 0';container.style.padding='15px';container.style.border='1px solid #a2a9b1';container.style.borderRadius='5px';container.style.backgroundColor='#f8f9fa';container.style.fontFamily='sans-serif';container.style.boxShadow='0 1px 2px rgba(0,0,0,0.1)';container.style.maxWidth='100%';// Add a header sectionconstheader=document.createElement('div');header.style.display='flex';header.style.justifyContent='space-between';header.style.alignItems='center';header.style.marginBottom='15px';header.style.borderBottom='1px solid #eaecf0';header.style.paddingBottom='10px';consttitleLink=document.createElement('a');titleLink.href='https://en.wikipedia.org/wiki/User:Polygnotus/Scripts/DiscussionToolsDrafts';titleLink.textContent='DiscussionToolsDrafts';titleLink.target='_blank';titleLink.style.backgroundImage='url(/w/skins/Vector/resources/skins.vector.styles.legacy/images/link-external-small-ltr-progressive.svg?fb64d)';titleLink.style.backgroundPosition='center right';titleLink.style.backgroundRepeat='no-repeat';titleLink.style.backgroundSize='0.857em';titleLink.style.paddingRight='1em';header.appendChild(titleLink);// Add buttons containerconstbuttonsContainer=document.createElement('div');buttonsContainer.style.display='flex';buttonsContainer.style.gap='10px';// Check if collapsed state is storedconstisCollapsed=localStorage.getItem('discussionToolsManagerCollapsed')==='true';// Add refresh buttonconstrefreshButton=document.createElement('button');refreshButton.className='cdx-button cdx-button--action-default';refreshButton.textContent='↻ Refresh';refreshButton.addEventListener('click',function(){refreshData();});buttonsContainer.appendChild(refreshButton);// Add toggle buttonconsttoggleButton=document.createElement('button');toggleButton.className='cdx-button cdx-button--action-default';toggleButton.textContent=isCollapsed?'▼ Expand':'▲ Collapse';toggleButton.addEventListener('click',function(){constcontentArea=document.getElementById('discussion-tools-content-wrapper');constisNowCollapsed=contentArea.style.display!=='none';// Toggle content area visibilitycontentArea.style.display=isNowCollapsed?'none':'block';toggleButton.textContent=isNowCollapsed?'▼ Expand':'▲ Collapse';// Store preference in localStoragelocalStorage.setItem('discussionToolsManagerCollapsed',isNowCollapsed.toString());});buttonsContainer.appendChild(toggleButton);header.appendChild(buttonsContainer);container.appendChild(header);// Add descriptionconstdescription=document.createElement('p');description.innerHTML='This tool helps you manage saved DiscussionTools drafts in your browser storage. <span style="color: #3366cc; text-decoration: underline;">Click here to expand/collapse</span>.';description.style.marginBottom='15px';description.style.color='#54595d';description.style.cursor='pointer';// Add click event to description to toggle content areadescription.addEventListener('click',function(){constcontentArea=document.getElementById('discussion-tools-content-wrapper');constisNowCollapsed=contentArea.style.display!=='none';// Toggle content area visibilitycontentArea.style.display=isNowCollapsed?'none':'block';toggleButton.textContent=isNowCollapsed?'▼ Expand':'▲ Collapse';// Store preference in localStoragelocalStorage.setItem('discussionToolsManagerCollapsed',isNowCollapsed.toString());});container.appendChild(description);// Create a wrapper for all content that can be collapsedconstcontentWrapper=document.createElement('div');contentWrapper.id='discussion-tools-content-wrapper';// Set initial display state based on stored preferencecontentWrapper.style.display=isCollapsed?'none':'block';// Create delete button with updated textconstdeleteButton=document.createElement('button');deleteButton.className='cdx-button cdx-button--action-destructive';deleteButton.textContent='Delete empty drafts';deleteButton.style.marginBottom='20px';deleteButton.addEventListener('click',function(){constdeleted=deleteEmptyReplies();alert(`Deleted ${deleted} empty or editsummary-only drafts.`);refreshData();});contentWrapper.appendChild(deleteButton);// Create content area that will be populated with dataconstcontentArea=document.createElement('div');contentArea.id='discussion-tools-content';contentWrapper.appendChild(contentArea);// Add the wrapper to the containercontainer.appendChild(contentWrapper);// Add the container below the main content divconstcontentDiv=document.querySelector('div#content');if(contentDiv){contentDiv.parentNode.insertBefore(container,contentDiv.nextSibling);}else{document.body.appendChild(container);}// Add animation stylesconststyle=document.createElement('style');style.textContent=` @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .notification { position: fixed; bottom: 20px; right: 20px; background-color: #28a745; color: white; padding: 10px 15px; border-radius: 4px; z-index: 9999; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: opacity 0.5s; } `;document.head.appendChild(style);// Load initial datarefreshData();}// Function to refresh the data displayfunctionrefreshData(){constcontentArea=document.getElementById('discussion-tools-content');if(!contentArea)return;// Safety checkcontentArea.innerHTML='';// Clear existing contentconstresults=findDiscussionToolsReplyPairs();if(Object.keys(results).length===0){constnoResults=document.createElement('p');noResults.textContent='No DiscussionTools reply drafts were found in your browser storage.';noResults.style.padding='10px';noResults.style.backgroundColor='#eaecf0';noResults.style.borderRadius='4px';contentArea.appendChild(noResults);return;}// Create section for localStorageconstsection=createDraftsSection('Drafts',results);contentArea.appendChild(section);}// Function to create a drafts sectionfunctioncreateDraftsSection(title,entries){constsection=document.createElement('div');section.className='storage-section';section.style.marginBottom='15px';section.style.border='1px solid #c8ccd1';section.style.borderRadius='4px';section.style.overflow='hidden';// Create headerconstheader=document.createElement('div');header.className='section-header';header.style.padding='10px 15px';header.style.backgroundColor='#eaecf0';header.style.display='flex';header.style.justifyContent='space-between';header.style.alignItems='center';// Add title and countconstsectionTitle=document.createElement('span');sectionTitle.innerHTML=`<strong>${title}</strong> <span style="color:#54595d">(${Object.keys(entries).length} entries)</span>`;header.appendChild(sectionTitle);section.appendChild(header);// Create content areaconstcontent=document.createElement('div');content.className='section-content';content.style.maxHeight='500px';content.style.overflow='auto';// Populate with entriesconstlist=document.createElement('ul');list.style.listStyleType='none';list.style.padding='0';list.style.margin='0';for(const[key,value]ofObject.entries(entries)){// Format the itemconstitem=document.createElement('li');item.style.padding='12px 15px';item.style.borderBottom='1px solid #eaecf0';item.style.display='flex';item.style.justifyContent='space-between';item.style.alignItems='flex-start';// Highlight empty or summary-only repliesif(isEmptyOrSummaryOnlyReply(value)){item.style.backgroundColor='#ffeaea';}// Create the content container (left side)constcontentDiv=document.createElement('div');contentDiv.style.flexGrow='1';contentDiv.style.paddingRight='10px';// Format page title from key and create actual linksletformattedKey=key;try{// Check if the key is in the format "mw-ext-DiscussionTools-reply/c-XXXXXXXX"constisCommentID=key.includes('/c-');if(isCommentID){// Extract the comment IDconstcommentParts=key.split('/');constprefix=commentParts[0];// "mw-ext-DiscussionTools-reply"constcommentId=commentParts[1];// c-XXXXXXXX// Create the Special:GoToComment linkconstcommentUrl=`/wiki/Special:GoToComment/${commentId}`;// Format with actual link to the commentformattedKey=`<span style="color:#54595d">${prefix}</span> / <a href="${commentUrl}" style="color:#3366cc" title="Go to this specific comment">${commentId}</a>`;}// For page-based keys in the format "mw-ext-DiscussionTools-reply|PageName"elseif(key.includes('|')){constparts=key.split('|');constprefix=parts[0];// The prefix part (like "mw-ext-DiscussionTools-reply")constpageName=parts[1].replace(/_/g,' ');// The page name// Create the wiki URLconstpageUrl=`/wiki/${parts[1]}`;// Use the raw page name with underscores for the URL// Format with actual linkformattedKey=`<span style="color:#54595d">${prefix}</span> | <a href="${pageUrl}" style="color:#3366cc">${pageName}</a>`;}}catch(e){// If there's an error in parsing, use the original keyconsole.log('Error parsing key:',e);}contentDiv.innerHTML=`<div><strong>${formattedKey}</strong></div><div style="font-family:monospace;margin-top:5px;word-break:break-all;color:#54595d">${value}</div>`;item.appendChild(contentDiv);// Create delete button for individual entryconstdeleteEntryBtn=document.createElement('button');deleteEntryBtn.className='cdx-button cdx-button--action-destructive cdx-button--icon-only';deleteEntryBtn.setAttribute('aria-label','Delete entry');//deleteEntryBtn.textContent = 'Delete';deleteEntryBtn.innerHTML='<span class="cdx-icon cdx-icon--medium"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>delete</title><g><path d="M17 2h-3.5l-1-1h-5l-1 1H3v2h14zM4 17a2 2 0 002 2h8a2 2 0 002-2V5H4z"></path></g></svg></span>';//const trashIcon = document.createElement('span');//trashIcon.className = 'cdx-button__icon cdx-button__icon--trash';//deleteEntryBtn.appendChild(trashIcon);// Add delete functionalitydeleteEntryBtn.addEventListener('click',function(e){e.stopPropagation();// Prevent triggering parent click eventslocalStorage.removeItem(key);// Remove the item from the displayitem.style.animation='fadeOut 0.3s';setTimeout(()=>{item.remove();// Update the count in the section headerconstcountSpan=section.querySelector('.section-header span span');if(countSpan){constcurrentCount=parseInt(countSpan.textContent.match(/\d+/)[0]);countSpan.textContent=`(${currentCount-1} entries)`;}},300);// Show success messageconstnotification=document.createElement('div');notification.className='notification';notification.textContent=`Deleted entry: ${key.split('|')[1]||key.split('/')[1]||key}`;document.body.appendChild(notification);// Remove notification after 3 secondssetTimeout(()=>{notification.style.opacity='0';setTimeout(()=>notification.remove(),500);},3000);});item.appendChild(deleteEntryBtn);list.appendChild(item);}content.appendChild(list);section.appendChild(content);// Always display the contentcontent.style.display='block';returnsection;}// Function to find all key-value pairs where the key begins with "mw-ext-DiscussionTools-reply"functionfindDiscussionToolsReplyPairs(){consttargetPrefix="mw-ext-DiscussionTools-reply";constresult={};// Search in localStoragefor(leti=0;i<localStorage.length;i++){constkey=localStorage.key(i);if(key&&key.startsWith(targetPrefix)){result[key]=localStorage.getItem(key);}}returnresult;}// Function to check if a reply is empty or only contains a summary without real contentfunctionisEmptyOrSummaryOnlyReply(value){try{constparsed=JSON.parse(value);// Check for completely empty replies: {"title":""}if(parsed&&typeofparsed==='object'&&Object.keys(parsed).length===1&&'title'inparsed&&parsed.title===''){returntrue;}// Check for empty replies with advanced options: {"showAdvanced":"","title":"","saveable":"","mode":"source"}if(parsed&&typeofparsed==='object'&&'title'inparsed&&parsed.title===''&&'showAdvanced'inparsed&&parsed.showAdvanced===''&&'mode'inparsed&&(!('ve-changes'inparsed)||!parsed['ve-changes']||parsed['ve-changes'].length===0)){returntrue;}// Check for replies that only have a summary but no actual content// This catches cases like: {"showAdvanced":"","saveable":"","mode":"source","summary":"/* Some section */ Reply"}if(parsed&&typeofparsed==='object'&&'summary'inparsed&&'mode'inparsed&&(!('ve-changes'inparsed)||!parsed['ve-changes']||parsed['ve-changes'].length===0)){returntrue;}// Additional check for replies with ve-changes but no actual content enteredif(parsed&&typeofparsed==='object'&&'ve-changes'inparsed&&Array.isArray(parsed['ve-changes'])&&parsed['ve-changes'].length===0){returntrue;}returnfalse;}catch(e){// If we can't parse it, assume it's not emptyreturnfalse;}}// Function to delete empty repliesfunctiondeleteEmptyReplies(){consttargetPrefix="mw-ext-DiscussionTools-reply";letdeletedCount=0;// Check localStorageconstlocalStorageKeys=[];for(leti=0;i<localStorage.length;i++){constkey=localStorage.key(i);if(key&&key.startsWith(targetPrefix)){localStorageKeys.push(key);}}for(constkeyoflocalStorageKeys){constvalue=localStorage.getItem(key);if(isEmptyOrSummaryOnlyReply(value)){localStorage.removeItem(key);deletedCount++;console.log(`Deleted from localStorage: ${key}`);}}returndeletedCount;}// Initialize the toolinitDiscussionToolsManager();}