mw.loader.using(['mediawiki.util'], function () {
$(document).ready(function () {
console.log("Script started");
if ((mw.config.get('wgNamespaceNumber') !== 0 && mw.config.get('wgPageName') !== 'User:Polygnotus/dupreftest') || mw.config.get('wgAction') !== 'view') {
console.log("Not the correct page or action, script terminated");
return;
}
console.log("Page title:", document.title);
console.log("URL:", window.___location.href);
let referencesHeading = document.getElementById("References");
if (!referencesHeading) {
console.log("References heading not found, script terminated");
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: #ffe6e6; }
.duplicate-citation-hover { background-color: #ffcccc; }
.duplicate-citation-clicked { border: 1px dotted red; padding: 2px; background-color: #ffe6e6; }
`;
document.head.appendChild(style);
let parentDiv = referencesHeading.closest("div");
let newParagraph = document.createElement("p");
newParagraph.style.color = "red";
function addDuplicateCitationsTemplate() {
console.log("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;
// Add the template at the top of the page
var newContent = '{{Duplicate citations}}\n' + content;
// Create the edit summary.
let summary = '+{{Duplicate citations}}';
if (duplicateInfo.length > 0) {
summary += ': ';
duplicateInfo.forEach((info, index) => {
summary += `${info.url} (refs: ${info.refs.join(', ')})`;
if (index < duplicateInfo.length - 1) {
summary += '; ';
}
});
}
// Make the edit
return api.postWithToken('csrf', {
action: 'edit',
title: pageTitle,
text: newContent,
summary: summary
});
}).then(function() {
mw.notify('Successfully added the Duplicate citations template!');
// Reload the page to show the changes
___location.reload();
}).catch(function(error) {
console.error('Error:', error);
mw.notify('Failed to add the template. See console for details.', {type: 'error'});
});
}
function getDuplicateInfo() {
console.log("Getting duplicate info");
const referencesHeading = document.getElementById("References");
if (!referencesHeading) {
console.log("References heading not found");
return [];
}
console.log("References heading found:", referencesHeading.outerHTML);
let currentElement = referencesHeading.closest('div');
console.log("Parent div of References heading:", currentElement.outerHTML);
while (currentElement && !currentElement.classList.contains('reflist')) {
console.log("Checking next sibling:", currentElement.nextElementSibling ? currentElement.nextElementSibling.outerHTML : "None");
currentElement = currentElement.nextElementSibling;
}
if (!currentElement || !currentElement.classList.contains('reflist')) {
console.log("References list (div.reflist) not found after References heading");
return [];
}
console.log("References list found:", currentElement.outerHTML);
const referencesList = currentElement.querySelector('ol.references');
if (!referencesList) {
console.log("ol.references not found within div.reflist");
return [];
}
const referenceItems = referencesList.querySelectorAll('li');
console.log("Number of reference items:", referenceItems.length);
const urlMap = new Map();
const duplicates = [];
referenceItems.forEach((item, index) => {
const refNumber = index + 1; // This is the correct reference number
console.log(`Processing reference item ${refNumber}`);
const span = item.querySelector('span.reference-text');
if (!span) {
console.log(` No reference-text span found in item ${refNumber}`);
return;
}
const links = span.querySelectorAll('a');
console.log(` 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;
console.log(` Valid link found: ${url}`);
break;
}
}
if (validLink) {
const url = validLink.href;
if (urlMap.has(url)) {
urlMap.get(url).push(refNumber.toString());
console.log(` Duplicate found for URL: ${url}`);
} else {
urlMap.set(url, [refNumber.toString()]);
console.log(` New URL added to map: ${url}`);
}
} else {
console.log(` No valid link found in this item`);
}
});
urlMap.forEach((refs, url) => {
if (refs.length > 1) {
duplicates.push({ url, refs });
}
});
console.log("Number of duplicate sets found:", duplicates.length);
console.log("Duplicate sets:", duplicates);
return duplicates;
}
function calculateSimilarity(str1, str2) {
const longer = str1.length > str2.length ? str1 : str2;
const shorter = str1.length > str2.length ? str2 : str1;
const longerLength = longer.length;
if (longerLength === 0) {
return 1.0;
}
return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
}
function editDistance(s1, s2) {
s1 = s1.toLowerCase();
s2 = s2.toLowerCase();
const costs = new Array();
for (let i = 0; i <= s1.length; i++) {
let lastValue = i;
for (let j = 0; j <= s2.length; j++) {
if (i == 0)
costs[j] = j;
else {
if (j > 0) {
let newValue = costs[j - 1];
if (s1.charAt(i - 1) != s2.charAt(j - 1))
newValue = Math.min(Math.min(newValue, lastValue),
costs[j]) + 1;
costs[j - 1] = lastValue;
lastValue = newValue;
}
}
}
if (i > 0)
costs[s2.length] = lastValue;
}
return costs[s2.length];
}
function getAllVisibleText(element) {
if (element.nodeType === Node.TEXT_NODE) {
return element.textContent.trim();
}
if (element.nodeType === Node.ELEMENT_NODE) {
if (window.getComputedStyle(element).display === 'none') {
return '';
}
return Array.from(element.childNodes)
.map(child => getAllVisibleText(child))
.join(' ')
.replace(/\s+/g, ' ')
.trim();
}
return '';
}
function extractVisibleText(htmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
return getAllVisibleText(doc.body);
}
function checkDuplicateReferenceLinks() {
console.log("Checking for duplicate reference links");
const duplicateInfo = getDuplicateInfo();
if (duplicateInfo.length > 0) {
console.log("Duplicates found, creating list");
if (document.querySelector('table.box-Duplicated_citations') === null) {
const editSections = parentDiv.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);
});
}
duplicateInfo.forEach(({ url, refs }) => {
let paragraphInfo = document.createElement('span');
let urlLink = document.createElement('a');
urlLink.href = url;
urlLink.textContent = url;
urlLink.target = "_blank";
urlLink.rel = "noopener noreferrer";
paragraphInfo.appendChild(document.createTextNode('Duplicate URL: '));
paragraphInfo.appendChild(urlLink);
paragraphInfo.appendChild(document.createTextNode(' in refs: '));
refs.forEach((refNumber, index) => {
let link = document.createElement('a');
link.href = `#cite_note-${refNumber}`;
link.textContent = refNumber;
paragraphInfo.appendChild(link);
// Highlight only the specific duplicates on hover
link.addEventListener('mouseover', () => {
refs.forEach(ref => {
const citationElement = document.getElementById(`cite_note-${ref}`);
if (citationElement) {
if (ref === refNumber) {
citationElement.classList.add('duplicate-citation-hover');
} else {
citationElement.classList.add('duplicate-citation-highlight');
}
}
});
});
link.addEventListener('mouseout', () => {
refs.forEach(ref => {
const citationElement = document.getElementById(`cite_note-${ref}`);
if (citationElement) {
citationElement.classList.remove('duplicate-citation-hover');
citationElement.classList.remove('duplicate-citation-highlight');
}
});
});
// Highlight duplicates on click and allow navigation
link.addEventListener('click', () => {
// Remove previous click highlights
document.querySelectorAll('.duplicate-citation-clicked').forEach(el => {
el.classList.remove('duplicate-citation-clicked');
});
// Add new click highlights
refs.forEach(ref => {
const citationElement = document.getElementById(`cite_note-${ref}`);
if (citationElement) {
citationElement.classList.add('duplicate-citation-clicked');
}
});
// The default behavior (navigation) will now occur
});
if (index < refs.length - 1) {
paragraphInfo.appendChild(document.createTextNode(', '));
}
});
paragraphInfo.appendChild(document.createElement('br'));
newParagraph.appendChild(paragraphInfo);
});
console.log("Appending duplicate list to page");
parentDiv.after(newParagraph);
} else {
console.log("No duplicates found");
}
}
checkDuplicateReferenceLinks();
console.log("Script execution completed");
});
});