Jump to content

User:Polygnotus/DuplicateReferences.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Polygnotus (talk | contribs) at 17:53, 16 July 2024 (make table fullwidth and hide on mobile). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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.
mw.loader.using(['mediawiki.util'], function () {
    $(document).ready(function () {
    	
    	const DEBUG = false;
		function debug(...args) {
		    if (DEBUG) {
		        console.log(...args);
		    }
		}

        debug("Script started");

        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);

        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;
        }

        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;
        }

        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 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;

                // Define the templates to check for
                const templatesToCheck = [
                    '{{short description',
                    '{{DISPLAYTITLE',
                    '{{Lowercase title',
                    '{{Italic title',
                    '{{about',
                    '{{redirect'
                ];

                // Find the position to insert the new template
                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;
                    }
                }

                // Create the reason string
                let reason = '';
                if (duplicateInfo.length > 0) {
                    duplicateInfo.forEach((info, index) => {
                        reason += `${info.url} (refs: ${info.refs.map(r => r.number).join(', ')})`;
                        if (index < duplicateInfo.length - 1) {
                            reason += '; ';
                        }
                    });
                }

                // Insert the new template with the reason parameter
                lines.splice(insertPosition, 0, `{{Duplicate citations|reason=${reason}}}`);
                var newContent = lines.join('\n');

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

                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'});
            });
        }

        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 = null;
                    for (let link of links) {
                        const url = link.href;
                        const linkText = link.textContent.trim();
                        
                        if (
                            linkText !== "Archived" &&
                            (!url.includes("wikipedia.org/wiki/") || url.includes("Special:BookSources")) &&
                            !url.includes("_(identifier)")
                        ) {
                            validLink = link;
                            debug(`  Valid link found: ${url}`);
                            break;
                        }
                    }

                    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 = '<strong>Duplicate References</strong>';
            
            const toggleSpan = document.createElement('span');
            toggleSpan.className = 'mw-collapsible-toggle';
            toggleSpan.innerHTML = '[<a href="#" class="mw-collapsible-text">hide</a>]';
            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', () => {
                        refs.forEach(r => {
                            const citationElement = document.getElementById(r.id);
                            if (citationElement) {
                                if (r.id === ref.id) {
                                    citationElement.classList.add('duplicate-citation-hover');
                                } else {
                                    citationElement.classList.add('duplicate-citation-highlight');
                                }
                            }
                        });
                    });
                    link.addEventListener('mouseout', () => {
                        refs.forEach(r => {
                            const citationElement = document.getElementById(r.id);
                            if (citationElement) {
                                citationElement.classList.remove('duplicate-citation-hover');
                                citationElement.classList.remove('duplicate-citation-highlight');
                            }
                        });
                    });

                    link.addEventListener('click', () => {
                        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');
                            }
                        });
                    });

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

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

            return table;
        }

        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) {
                    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 {{duplicated citations}} ';
                        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);
                    });
                }
                
                const table = createCollapsibleTable(duplicateInfo);
                containerDiv.after(table);

                // Set up collapsible functionality
                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);
                }

                // Initialize state from localStorage
                const initialState = localStorage.getItem(storageKey) === 'true';
                setTableState(initialState);

                toggleLink.addEventListener('click', function(e) {
                    e.preventDefault();
                    const isCurrentlyCollapsed = tableBody.is(':hidden');
                    setTableState(!isCurrentlyCollapsed);
                });
            } else {
                debug("No duplicates found");
            }
        }
        
        checkDuplicateReferenceLinks();
        debug("Script execution completed");
    });
});