// 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();}