User:Polygnotus/DuplicateReferences.js

mw.loader.using(['mediawiki.util'], function {    $(document).ready(function  { const DEBUG = false; function debug(...args) { if (DEBUG) console.log(...args); }

debug("Script started");

// Check if we're on the correct page and action if ((mw.config.get('wgNamespaceNumber') !== 0 && mw.config.get('wgPageName') !== 'User:Polygnotus/dupreftest') || mw.config.get('wgAction') !== 'view') { debug("Not the correct page or action, script terminated"); return; }

debug("Page title:", document.title); debug("URL:", window.location.href);

// Find the references section const referencesHeader = document.querySelector("h2#References"); if (!referencesHeader) { debug("References heading not found, script terminated"); return; }

const containerDiv = referencesHeader.closest("div"); if (!containerDiv) { debug("Container div not found, script terminated"); return; }

const reflistDiv = findNextReflistDiv(containerDiv); if (!reflistDiv) { debug("Reflist div not found, script terminated"); return; }

const referencesList = reflistDiv.querySelector('ol.references'); if (!referencesList) { debug("ol.references not found within reflist div"); return; }

// Add styles addStyles;

// Main function to check for duplicate references function checkDuplicateReferenceLinks { debug("Checking for duplicate reference links"); const duplicateInfo = getDuplicateInfo; if (duplicateInfo.length > 0) { debug("Duplicates found, creating collapsible table"); if (document.querySelector('table.box-Duplicated_citations') === null) { addTemplateLink; }               const table = createCollapsibleTable(duplicateInfo); containerDiv.after(table);

setupCollapsibleTable(table); } else { debug("No duplicates found"); }       }

// Helper functions function findNextReflistDiv(element) { let nextElement = element.nextElementSibling; while (nextElement) { if (nextElement.tagName.toLowerCase === 'div' &&                    (nextElement.classList.contains('reflist') || nextElement.classList.contains('mw-references-wrap'))) { return nextElement; }               nextElement = nextElement.nextElementSibling; }           return null; }

function addStyles { const style = document.createElement('style'); style.textContent = ` li:target { border: 1px dotted red; padding: 2px; background-color: #ffcccc !important; } .duplicate-citation-highlight { background-color: #e1eeff; } .duplicate-citation-hover { background-color: #cce0ff; border: 1px dotted blue; } .duplicate-citation-clicked { border: 1px dotted red; padding: 2px; background-color: #ffe6e6; } .mw-collapsible-toggle { font-weight: normal; float: right; } .duplicate-references-table { width: 100%; } @media only screen and (max-width: 768px) { .duplicate-references-table { display: none; } }           `;            document.head.appendChild(style); }

function getDuplicateInfo { debug("Getting duplicate info"); const referenceItems = referencesList.children; debug("Number of reference items:", referenceItems.length); const urlMap = new Map; const duplicates = [];

let refNumber = 0; for (let item of referenceItems) { if (item.tagName.toLowerCase === 'li') { refNumber++; const refId = item.id; debug(`Processing reference item ${refNumber} (${refId})`); const span = item.querySelector('span.reference-text'); if (!span) { debug(` No reference-text span found in item ${refNumber}`); continue; }                   const links = span.querySelectorAll('a'); debug(` Number of links in this span: ${links.length}`);

let validLink = Array.from(links).find(link => {                       const url = link.href;                        const linkText = link.textContent.trim;                        return linkText !== "Archived" &&                               (!url.includes("wikipedia.org/wiki/") || url.includes("Special:BookSources")) &&                               !url.includes("_(identifier)");                    });

if (validLink) { const url = validLink.href; if (urlMap.has(url)) { urlMap.get(url).push({id: refId, number: refNumber}); debug(` Duplicate found for URL: ${url}`); } else { urlMap.set(url, [{id: refId, number: refNumber}]); debug(` New URL added to map: ${url}`); }                   } else { debug(` No valid link found in this item`); }               }            }

urlMap.forEach((refs, url) => {               if (refs.length > 1) {                    duplicates.push({ url, refs });                }            });

debug("Number of duplicate sets found:", duplicates.length); debug("Duplicate sets:", duplicates); return duplicates; }

function createCollapsibleTable(duplicateInfo) { const table = document.createElement('table'); table.className = 'wikitable mw-collapsible duplicate-references-table'; table.setAttribute('role', 'presentation');

const tbody = document.createElement('tbody'); table.appendChild(tbody);

const headerRow = document.createElement('tr'); const headerCell = document.createElement('td'); headerCell.innerHTML = ' Duplicate References '; const toggleSpan = document.createElement('span'); toggleSpan.className = 'mw-collapsible-toggle'; toggleSpan.innerHTML = '[hide]'; headerCell.appendChild(toggleSpan); headerRow.appendChild(headerCell); tbody.appendChild(headerRow);

duplicateInfo.forEach(({ url, refs }) => {               const row = document.createElement('tr');                const cell = document.createElement('td');

let urlLink = document.createElement('a'); urlLink.href = url; urlLink.textContent = url; urlLink.target = "_blank"; urlLink.rel = "noopener noreferrer";

cell.appendChild(urlLink); cell.appendChild(document.createTextNode(' in refs: '));

refs.forEach((ref, index) => {                   let link = document.createElement('a');                    link.href = `#${ref.id}`;                    link.textContent = ref.number;                    cell.appendChild(link);

link.addEventListener('mouseover', => highlightCitations(refs, ref.id)); link.addEventListener('mouseout', => removeCitationHighlight(refs)); link.addEventListener('click', => clickCitation(refs));

if (index < refs.length - 1) { cell.appendChild(document.createTextNode(', ')); }               });

row.appendChild(cell); tbody.appendChild(row); });

return table; }

function highlightCitations(refs, currentId) { refs.forEach(r => {               const citationElement = document.getElementById(r.id);                if (citationElement) {                    if (r.id === currentId) {                        citationElement.classList.add('duplicate-citation-hover');                    } else {                        citationElement.classList.add('duplicate-citation-highlight');                    }                }            }); }

function removeCitationHighlight(refs) { refs.forEach(r => {               const citationElement = document.getElementById(r.id);                if (citationElement) {                    citationElement.classList.remove('duplicate-citation-hover');                    citationElement.classList.remove('duplicate-citation-highlight');                }            }); }

function clickCitation(refs) { document.querySelectorAll('.duplicate-citation-clicked').forEach(el => {               el.classList.remove('duplicate-citation-clicked');            }); refs.forEach(r => {               const citationElement = document.getElementById(r.id);                if (citationElement) {                    citationElement.classList.add('duplicate-citation-clicked');                }            }); }

function setupCollapsibleTable(table) { const toggleLink = table.querySelector('.mw-collapsible-toggle a'); const tableBody = $(table).find('tr:not(:first-child)'); const storageKey = 'duplicateReferencesTableState';

function setTableState(isCollapsed) { if (isCollapsed) { tableBody.hide; toggleLink.textContent = 'show'; } else { tableBody.show; toggleLink.textContent = 'hide'; }               localStorage.setItem(storageKey, isCollapsed); }

const initialState = localStorage.getItem(storageKey) === 'true'; setTableState(initialState);

toggleLink.addEventListener('click', function(e) {               e.preventDefault;                const isCurrentlyCollapsed = tableBody.is(':hidden');                setTableState(!isCurrentlyCollapsed);            }); }

function addTemplateLink { const editSections = containerDiv.querySelectorAll('span.mw-editsection'); editSections.forEach(editSection => {               let spanBefore = document.createElement('span');                spanBefore.className = 'mw-editsection-bracket';                spanBefore.textContent = '[';                let addTemplateLink = document.createElement('a');                addTemplateLink.textContent = ' add  ';                addTemplateLink.href = '#';                addTemplateLink.addEventListener('click', function(e) { e.preventDefault; addDuplicateCitationsTemplate; });               let spanAfter = document.createElement('span');                spanAfter.className = 'mw-editsection-bracket';                spanAfter.textContent = ']';                editSection.appendChild(spanBefore);                editSection.appendChild(addTemplateLink);                editSection.appendChild(spanAfter);            }); }

function addDuplicateCitationsTemplate { debug("Adding duplicate citations template"); var api = new mw.Api; var pageTitle = mw.config.get('wgPageName');

let duplicateInfo = getDuplicateInfo;

api.get({               action: 'query',                prop: 'revisions',                titles: pageTitle,                rvprop: 'content',                rvslots: 'main',                formatversion: 2            }).then(function(data) {                var page = data.query.pages[0];                var content = page.revisions[0].slots.main.content;

const templatesToCheck = [ '{{short description', '{{DISPLAYTITLE', '{{Lowercase title', '{{Italic title', '{{about', '{{redirect' ];

let insertPosition = 0; let lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { let line = lines[i].trim.toLowerCase; if (templatesToCheck.some(template => line.startsWith(template.toLowerCase))) { insertPosition = i + 1; } else if (line && !line.startsWith('{{') && !line.startsWith('__')) { break; }               }

let reason = duplicateInfo.map(info =>                    `${info.url} (refs: ${info.refs.map(r => r.number).join(', ')})`                ).join('; ');

const date = new Date; const currentDate = `${date.toLocaleString('en-US', { month: 'long' })} ${date.getFullYear}`;

lines.splice(insertPosition, 0, `{{Duplicate citations|reason=${reason}|date=${currentDate}}}`); var newContent = lines.join('\n');

let summary = `+{{Duplicate citations|reason=${reason}|date=${currentDate}}}`;

return api.postWithToken('csrf', {                   action: 'edit',                    title: pageTitle,                    text: newContent,                    summary: summary                }); }).then(function { mw.notify('Successfully added the Duplicate citations template!'); location.reload; }).catch(function(error) { console.error('Error:', error); mw.notify('Failed to add the template. See console for details.', {type: 'error'}); });       }

// Run the main function checkDuplicateReferenceLinks; debug("Script execution completed"); }); });