User:Polygnotus/Scripts/CheckImportedScripts.js
// <nowiki>
/*
On the wiki you run it it first checks common.js and then the skin-specific .js files, and then global.js on meta.
*/
(function() {
'use strict';
// Add link to Tools menu
mw.loader.using(['mediawiki.util'], function() {
mw.util.addPortletLink(
'p-tb',
'#',
'Check imported scripts',
't-check-scripts',
'Verify that all imported scripts exist'
);
$('#t-check-scripts').click(function(e) {
e.preventDefault();
showUsernamePrompt();
});
});
function showUsernamePrompt() {
var currentUsername = mw.config.get('wgUserName');
// Create prompt dialog
var $promptOverlay = $('<div>')
.css({
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
zIndex: 9999
});
var $promptDialog = $('<div>')
.css({
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: 'white',
border: '2px solid #a2a9b1',
borderRadius: '4px',
padding: '20px',
minWidth: '400px',
zIndex: 10000,
boxShadow: '0 2px 10px rgba(0,0,0,0.3)'
});
var $promptTitle = $('<h3>').text('Check imported scripts').css('margin-top', 0);
var $promptText = $('<p>').text('Whose scripts do you want to check?');
var $buttonContainer = $('<div>').css({
display: 'flex',
gap: '10px',
marginTop: '15px'
});
var $mineBtn = $('<button>')
.text('My scripts')
.css({
padding: '8px 20px',
flex: '1'
})
.click(function() {
$promptDialog.remove();
$promptOverlay.remove();
checkScripts(currentUsername);
});
var $otherBtn = $('<button>')
.text('Someone else\'s scripts')
.css({
padding: '8px 20px',
flex: '1'
})
.click(function() {
$promptDialog.remove();
$promptOverlay.remove();
showUsernameInput();
});
var $cancelBtn = $('<button>')
.text('Cancel')
.css({
padding: '8px 20px',
marginTop: '10px',
width: '100%'
})
.click(function() {
$promptDialog.remove();
$promptOverlay.remove();
});
// ESC key to close
$(document).on('keydown.promptDialog', function(e) {
if (e.key === 'Escape') {
$(document).off('keydown.promptDialog');
$promptDialog.remove();
$promptOverlay.remove();
}
});
$buttonContainer.append($mineBtn, $otherBtn);
$promptDialog.append($promptTitle, $promptText, $buttonContainer, $cancelBtn);
$('body').append($promptOverlay, $promptDialog);
}
function showUsernameInput() {
// Create input dialog
var $inputOverlay = $('<div>')
.css({
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
zIndex: 9999
});
var $inputDialog = $('<div>')
.css({
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: 'white',
border: '2px solid #a2a9b1',
borderRadius: '4px',
padding: '20px',
minWidth: '400px',
zIndex: 10000,
boxShadow: '0 2px 10px rgba(0,0,0,0.3)'
});
var $inputTitle = $('<h3>').text('Enter account name').css('margin-top', 0);
var $inputLabel = $('<label>').text('Account name:').css({
display: 'block',
marginBottom: '5px'
});
var $usernameInput = $('<input>')
.attr('type', 'text')
.css({
width: '100%',
padding: '8px',
border: '1px solid #a2a9b1',
borderRadius: '3px',
boxSizing: 'border-box'
});
var $errorMsg = $('<div>').css({
color: 'red',
marginTop: '10px',
display: 'none'
});
var $buttonContainer = $('<div>').css({
display: 'flex',
gap: '10px',
marginTop: '15px'
});
var $checkBtn = $('<button>')
.text('Check')
.css({
padding: '8px 20px',
flex: '1'
})
.click(function() {
var username = $usernameInput.val().trim();
if (!username) {
$errorMsg.text('Please enter a username').show();
return;
}
$(document).off('keydown.inputDialog');
$inputDialog.remove();
$inputOverlay.remove();
checkScripts(username);
});
var $cancelBtn = $('<button>')
.text('Cancel')
.css({
padding: '8px 20px',
flex: '1'
})
.click(function() {
$(document).off('keydown.inputDialog');
$inputDialog.remove();
$inputOverlay.remove();
});
// Allow Enter key to submit
$usernameInput.keypress(function(e) {
if (e.which === 13) {
$checkBtn.click();
}
});
// ESC key to close
$(document).on('keydown.inputDialog', function(e) {
if (e.key === 'Escape') {
$(document).off('keydown.inputDialog');
$inputDialog.remove();
$inputOverlay.remove();
}
});
$buttonContainer.append($checkBtn, $cancelBtn);
$inputDialog.append($inputTitle, $inputLabel, $usernameInput, $errorMsg, $buttonContainer);
$('body').append($inputOverlay, $inputDialog);
$usernameInput.focus();
}
function checkScripts(username) {
// Get the current wiki for comparison
var currentWiki = window.location.hostname;
// Files to check
var jsFiles = [
'common.js',
'vector.js',
'vector-2022.js',
'minerva.js',
'monobook.js',
'timeless.js',
'global.js'
];
// Interwiki prefix mapping - comprehensive list from Wikipedia
var interwikiMap = {
// Shorthand prefixes
'c': 'commons.wikimedia.org',
'm': 'meta.wikimedia.org',
'meta': 'meta.wikimedia.org',
'd': 'www.wikidata.org',
'f': 'www.wikifunctions.org',
'w': 'en.wikipedia.org',
'wikt': 'en.wiktionary.org',
'q': 'en.wikiquote.org',
'b': 'en.wikibooks.org',
'n': 'en.wikinews.org',
's': 'en.wikisource.org',
'v': 'en.wikiversity.org',
'voy': 'en.wikivoyage.org',
// Full names
'commons': 'commons.wikimedia.org',
'wikidata': 'www.wikidata.org',
'wikifunctions': 'www.wikifunctions.org',
'wikipedia': 'en.wikipedia.org',
'wiktionary': 'en.wiktionary.org',
'wikiquote': 'en.wikiquote.org',
'wikibooks': 'en.wikibooks.org',
'wikinews': 'en.wikinews.org',
'wikisource': 'en.wikisource.org',
'wikiversity': 'en.wikiversity.org',
'wikivoyage': 'en.wikivoyage.org',
'species': 'species.wikimedia.org',
'wikispecies': 'species.wikimedia.org',
// MediaWiki and related
'mw': 'www.mediawiki.org',
'mediawikiwiki': 'www.mediawiki.org',
'wikitech': 'wikitech.wikimedia.org',
'labsconsole': 'wikitech.wikimedia.org',
// Meta and Foundation
'metawiki': 'meta.wikimedia.org',
'metawikimedia': 'meta.wikimedia.org',
'metawikipedia': 'meta.wikimedia.org',
'foundation': 'foundation.wikimedia.org',
'wikimedia': 'foundation.wikimedia.org',
'wmf': 'foundation.wikimedia.org',
// Incubator and testing
'incubator': 'incubator.wikimedia.org',
'betawikiversity': 'beta.wikiversity.org',
'testwiki': 'test.wikipedia.org',
'test2wiki': 'test2.wikipedia.org',
'testwikidata': 'test.wikidata.org',
// Outreach and other
'outreach': 'outreach.wikimedia.org',
'outreachwiki': 'outreach.wikimedia.org',
'wikimania': 'wikimania.wikimedia.org',
'diff': 'diff.wikimedia.org',
'diffblog': 'diff.wikimedia.org',
'donate': 'donate.wikimedia.org',
// Phabricator and development
'phab': 'phabricator.wikimedia.org',
'phabricator': 'phabricator.wikimedia.org',
'gerrit': 'gerrit.wikimedia.org',
// Chapter wikis (some examples)
'translatewiki': 'translatewiki.net',
'betawiki': 'translatewiki.net'
};
// Normalize page title (replace underscores with spaces, but preserve case)
function normalizeTitle(title) {
return title.replace(/_/g, ' ');
}
// Parse interwiki prefix
function parseInterwiki(path) {
var match = path.match(/^([a-z0-9-]+):(.+)$/i);
if (match) {
var prefix = match[1].toLowerCase();
var remainder = match[2];
if (interwikiMap[prefix]) {
var targetWiki = interwikiMap[prefix];
var currentWiki = window.location.hostname;
// If the interwiki prefix points to the current wiki, treat it as a local namespace instead
if (targetWiki === currentWiki) {
return {
wiki: null,
page: normalizeTitle(path)
};
}
return {
wiki: targetWiki,
page: normalizeTitle(remainder)
};
}
}
return {
wiki: null,
page: normalizeTitle(path)
};
}
// Get URL for a page
function getPageUrl(title, wiki) {
if (wiki) {
// For external wikis, we need to properly encode the title
// Encode each component separately to preserve the structure
var parts = title.split('/');
var encodedParts = parts.map(function(part) {
return encodeURIComponent(part).replace(/%20/g, '_').replace(/%3A/g, ':');
});
return 'https://' + wiki + '/wiki/' + encodedParts.join('/');
} else {
return mw.util.getUrl(title);
}
}
// Extract importScript and mw.loader.load calls from text
// Improved to handle comments, multi-line statements, and arrays
function extractScripts(text) {
var scripts = [];
// Remove single-line comments (but not // inside strings)
// Match // only when not inside quotes
var cleanedText = text.replace(/(['"])(?:(?=(\\?))\2.)*?\1|\/\/.*/g, function(match) {
// If it's a quoted string, keep it; otherwise it's a comment, remove it
return match.startsWith("'") || match.startsWith('"') ? match : '';
});
// Remove multi-line comments
cleanedText = cleanedText.replace(/\/\*[\s\S]*?\*\//g, '');
// Match importScript('...') or importScript("...")
// Catches ALL importScript calls regardless of content
var importScriptRegex = /importScript\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
var match;
while ((match = importScriptRegex.exec(cleanedText)) !== null) {
var parsed = parseInterwiki(match[1]);
scripts.push({
type: 'script',
path: match[1],
normalizedPath: parsed.page,
wiki: parsed.wiki
});
}
// Match mw.loader.load with scripts (single string)
// Catch both .js files and URLs with title parameter
var loaderRegex = /mw\.loader\.load\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
while ((match = loaderRegex.exec(cleanedText)) !== null) {
var originalPath = match[1];
var item = originalPath;
var wiki = null;
// Extract domain from protocol-relative or full URLs
var domainMatch = item.match(/^(?:https?:)?\/\/([^/]+)/);
if (domainMatch) {
wiki = domainMatch[1];
}
// Check if it's a MediaWiki index.php URL with title parameter
var titleMatch = item.match(/[?&]title=([^&]+)/);
if (titleMatch) {
item = decodeURIComponent(titleMatch[1]);
} else {
// Check if it's a /wiki/ URL
var wikiMatch = item.match(/\/wiki\/([^?#]+)/);
if (wikiMatch) {
item = decodeURIComponent(wikiMatch[1]);
} else if (!item.match(/\.js/)) {
// Skip items that don't look like scripts
continue;
}
}
// If the extracted wiki matches the current wiki, treat as local
var currentWiki = window.location.hostname;
if (wiki === currentWiki) {
wiki = null;
}
// Parse the extracted page title (not the original URL)
var parsed = parseInterwiki(item);
scripts.push({
type: 'script',
path: originalPath, // Keep original path for display
normalizedPath: parsed.page,
wiki: wiki || parsed.wiki
});
}
// Match mw.loader.load with arrays
var loaderArrayRegex = /mw\.loader\.load\s*\(\s*\[([^\]]+)\]/g;
while ((match = loaderArrayRegex.exec(cleanedText)) !== null) {
var arrayContent = match[1];
var itemRegex = /['"]([^'"]+)['"]/g;
var itemMatch;
while ((itemMatch = itemRegex.exec(arrayContent)) !== null) {
var originalPath = itemMatch[1];
var item = originalPath;
var wiki = null;
// Extract domain from protocol-relative or full URLs
var domainMatch = item.match(/^(?:https?:)?\/\/([^/]+)/);
if (domainMatch) {
wiki = domainMatch[1];
}
// Check if it's a MediaWiki index.php URL with title parameter
var titleMatch = item.match(/[?&]title=([^&]+)/);
if (titleMatch) {
item = decodeURIComponent(titleMatch[1]);
} else {
// Check if it's a /wiki/ URL
var wikiMatch = item.match(/\/wiki\/([^?#]+)/);
if (wikiMatch) {
item = decodeURIComponent(wikiMatch[1]);
} else if (!item.match(/\.js/)) {
// Skip items that don't look like scripts
continue;
}
}
// If the extracted wiki matches the current wiki, treat as local
var currentWiki = window.location.hostname;
if (wiki === currentWiki) {
wiki = null;
}
// Parse the extracted page title (not the original URL)
var parsed = parseInterwiki(item);
scripts.push({
type: 'script',
path: originalPath, // Keep original path for display
normalizedPath: parsed.page,
wiki: wiki || parsed.wiki
});
}
}
return scripts;
}
// Create a unique key for a script (normalized path + wiki)
function getScriptKey(script) {
return (script.wiki || 'local') + '::' + script.normalizedPath;
}
// Normalize script key for cross-file duplicate detection
// This handles the case where global.js references scripts on the current wiki
function getNormalizedScriptKey(script, sourceFile) {
var wiki = script.wiki;
// If this script is from global.js and references the current wiki,
// treat it as a local script for comparison purposes
if (sourceFile === 'global.js' && wiki === currentWiki) {
wiki = null;
}
return (wiki || 'local') + '::' + script.normalizedPath;
}
// Find duplicates within a single file
function findInternalDuplicates(scripts) {
var seen = {};
var duplicates = [];
scripts.forEach(function(script) {
var key = getScriptKey(script);
if (seen[key]) {
duplicates.push(script);
} else {
seen[key] = true;
}
});
return duplicates;
}
// Check if a page exists (just check existence, ignore case and redirects)
function checkPageExists(title, wiki) {
var apiUrl = wiki
? 'https://' + wiki + '/w/api.php'
: mw.util.wikiScript('api');
return $.ajax({
url: apiUrl,
data: {
action: 'query',
titles: title,
redirects: true,
format: 'json',
formatversion: 2,
origin: '*'
},
dataType: 'json',
timeout: 10000,
headers: {
'Api-User-Agent': 'CheckImportedScripts/1.0'
}
}).then(function(data) {
var page = data.query.pages[0];
// Just check if page exists - MediaWiki handles case and redirects automatically
return !page.missing;
}).catch(function(jqXHR, textStatus, errorThrown) {
// Provide specific error messages
var errorMsg;
if (textStatus === 'timeout') {
errorMsg = 'Request timed out';
} else if (textStatus === 'error') {
if (jqXHR.status === 0) {
errorMsg = 'Network error (no connection or CORS issue)';
} else if (jqXHR.status === 404) {
errorMsg = 'API endpoint not found (404)';
} else if (jqXHR.status === 403) {
errorMsg = 'Access forbidden (403)';
} else if (jqXHR.status >= 500) {
errorMsg = 'Server error (' + jqXHR.status + ')';
} else {
errorMsg = 'HTTP error ' + jqXHR.status;
}
} else if (textStatus === 'parsererror') {
errorMsg = 'Failed to parse API response';
} else {
errorMsg = textStatus || 'Unknown error';
}
throw new Error(errorMsg);
});
}
// Create popup
var $popup = $('<div>')
.css({
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: 'white',
border: '2px solid #a2a9b1',
borderRadius: '4px',
padding: '20px',
minWidth: '500px',
maxWidth: '700px',
maxHeight: '80vh',
overflow: 'auto',
zIndex: 10000,
boxShadow: '0 2px 10px rgba(0,0,0,0.3)'
});
var $overlay = $('<div>')
.css({
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
zIndex: 9999
});
var $title = $('<h3>').text('Checking scripts for User:' + username).css('margin-top', 0);
var $progress = $('<div>').css({
fontFamily: 'monospace',
fontSize: '12px',
backgroundColor: '#f8f9fa',
padding: '10px',
borderRadius: '3px',
maxHeight: '400px',
overflow: 'auto',
marginTop: '10px'
});
var $summary = $('<div>').css({
marginTop: '15px',
padding: '10px',
backgroundColor: '#f0f0f0',
borderRadius: '3px',
fontWeight: 'bold'
});
var $closeBtn = $('<button>')
.text('Close')
.css({
marginTop: '15px',
padding: '5px 15px'
})
.click(function() {
$(document).off('keydown.resultDialog');
$popup.remove();
$overlay.remove();
});
$popup.append($title, $progress, $summary, $closeBtn);
$('body').append($overlay, $popup);
// ESC key to close
$(document).on('keydown.resultDialog', function(e) {
if (e.key === 'Escape') {
$(document).off('keydown.resultDialog');
$popup.remove();
$overlay.remove();
}
});
function addProgress(message, color) {
var $line = $('<div>').html(message).css('color', color || '#000');
$progress.append($line);
$progress.scrollTop($progress[0].scrollHeight);
}
// Main checking function
var allMissing = [];
var allInternalDuplicates = [];
var allScriptsByFile = {}; // Store scripts for cross-file duplicate checking
// Process JS files sequentially to show progress
function processJsFile(index) {
if (index >= jsFiles.length) {
// All files processed, now check for cross-file duplicates
checkCrossFileDuplicates();
return;
}
var jsFile = jsFiles[index];
var pageName = 'User:' + username + '/' + jsFile;
// If checking global.js, use Meta-Wiki
var targetWiki = (jsFile === 'global.js') ? 'meta.wikimedia.org' : null;
var fileUrl = getPageUrl(pageName, targetWiki);
var displayName = jsFile;
if (targetWiki) {
displayName += ' <small style="color: #666;">(on ' + targetWiki + ')</small>';
}
addProgress('Checking <a href="' + fileUrl + '" target="_blank" style="color: #0645ad;"><strong>' + displayName + '</strong></a>...', '#000');
// Use the appropriate API
var apiCall;
if (targetWiki) {
apiCall = $.ajax({
url: 'https://' + targetWiki + '/w/api.php',
data: {
action: 'query',
titles: pageName,
prop: 'revisions',
rvprop: 'content',
format: 'json',
formatversion: 2,
origin: '*'
},
dataType: 'json',
headers: {
'Api-User-Agent': 'CheckImportedScripts/1.0'
},
xhrFields: {
withCredentials: false
}
});
} else {
apiCall = new mw.Api().get({
action: 'query',
titles: pageName,
prop: 'revisions',
rvprop: 'content',
formatversion: 2
});
}
apiCall.then(function(data) {
var page = data.query.pages[0];
if (page.missing) {
addProgress(' → Page does not exist', '#888');
allScriptsByFile[jsFile] = [];
// 1 second delay before next file
setTimeout(function() {
processJsFile(index + 1);
}, 1000);
return;
}
if (!page.revisions) {
addProgress(' → No content found', '#888');
allScriptsByFile[jsFile] = [];
// 1 second delay before next file
setTimeout(function() {
processJsFile(index + 1);
}, 1000);
return;
}
addProgress(' → Page exists', '#080');
var content = page.revisions[0].content;
var scripts = extractScripts(content);
// Store scripts for cross-file checking
allScriptsByFile[jsFile] = scripts.map(function(s) {
return {
script: s,
sourceFile: jsFile,
sourceWiki: targetWiki
};
});
if (scripts.length === 0) {
addProgress(' → No scripts found', '#888');
// 1 second delay before next file
setTimeout(function() {
processJsFile(index + 1);
}, 1000);
return;
}
addProgress(' → Found ' + scripts.length + ' script(s)', '#000');
// Check for internal duplicates
var internalDupes = findInternalDuplicates(scripts);
if (internalDupes.length > 0) {
addProgress(' → ⚠ Found ' + internalDupes.length + ' duplicate(s) within this file', '#f80');
internalDupes.forEach(function(dupe) {
var scriptUrl = getPageUrl(dupe.normalizedPath, dupe.wiki);
allInternalDuplicates.push({
script: dupe,
sourceFile: jsFile,
sourceWiki: targetWiki
});
addProgress(' ⚠ <a href="' + scriptUrl + '" target="_blank" style="color: #f80;">' + dupe.path + '</a>', '#f80');
});
}
addProgress(' → Verifying script existence...', '#000');
// Check scripts sequentially
var scriptIndex = 0;
var missingInFile = 0;
function checkNextScript() {
if (scriptIndex >= scripts.length) {
if (missingInFile === 0) {
addProgress(' → All scripts exist', '#080');
} else {
addProgress(' → ' + missingInFile + ' missing', '#c00');
}
addProgress(''); // blank line
// Process next file after 1 second delay
setTimeout(function() {
processJsFile(index + 1);
}, 1000);
return;
}
var script = scripts[scriptIndex];
script.sourceFile = jsFile;
script.sourceWiki = targetWiki;
// Show progress: "Checking script X of Y"
addProgress(' Checking script ' + (scriptIndex + 1) + ' of ' + scripts.length + '...', '#888');
checkPageExists(script.normalizedPath, script.wiki).then(function(exists) {
var scriptUrl = getPageUrl(script.normalizedPath, script.wiki);
if (!exists) {
addProgress(' ✗ <a href="' + scriptUrl + '" target="_blank" style="color: #c00;"><strong>' + script.path + '</strong></a> (MISSING)', '#c00');
allMissing.push(script);
missingInFile++;
} else {
addProgress(' ✓ <a href="' + scriptUrl + '" target="_blank" style="color: #080;">' + script.path + '</a>', '#080');
}
scriptIndex++;
// 1 second delay between script checks
setTimeout(checkNextScript, 1000);
}).catch(function(error) {
addProgress(' ⚠ ' + script.path + ' (Error: ' + error.message + ')', '#f80');
scriptIndex++;
// 1 second delay between script checks
setTimeout(checkNextScript, 1000);
});
}
checkNextScript();
}).catch(function(error) {
var errorMsg = error.message || error.statusText || error.toString();
addProgress(' → Error: ' + errorMsg, '#c00');
allScriptsByFile[jsFile] = [];
// 1 second delay before next file
setTimeout(function() {
processJsFile(index + 1);
}, 1000);
});
}
// Check for cross-file duplicates
function checkCrossFileDuplicates() {
addProgress('Checking for cross-file duplicates...', '#000');
var skinFiles = ['vector.js', 'vector-2022.js', 'minerva.js', 'monobook.js', 'timeless.js'];
var commonFiles = ['common.js', 'global.js'];
var crossFileDuplicates = [];
// Build script index for common.js and global.js using normalized keys
var commonScripts = {};
commonFiles.forEach(function(commonFile) {
if (allScriptsByFile[commonFile]) {
allScriptsByFile[commonFile].forEach(function(item) {
var key = getNormalizedScriptKey(item.script, commonFile);
if (!commonScripts[key]) {
commonScripts[key] = [];
}
commonScripts[key].push({
file: commonFile,
wiki: item.sourceWiki,
originalScript: item.script
});
});
}
});
// First, check for duplicates between common.js and global.js themselves
Object.keys(commonScripts).forEach(function(key) {
var files = commonScripts[key];
if (files.length > 1) {
// Found duplicate between common files
for (var i = 0; i < files.length; i++) {
for (var j = i + 1; j < files.length; j++) {
// Only report if it's actually different files
if (files[i].file !== files[j].file) {
crossFileDuplicates.push({
script: files[i].originalScript,
file1: files[i].file,
wiki1: files[i].wiki,
file2: files[j].file,
wiki2: files[j].wiki,
commonScript: files[j].originalScript
});
}
}
}
}
});
// Check each skin file for duplicates with common.js or global.js
skinFiles.forEach(function(skinFile) {
if (allScriptsByFile[skinFile]) {
allScriptsByFile[skinFile].forEach(function(item) {
var key = getNormalizedScriptKey(item.script, skinFile);
if (commonScripts[key]) {
// Found duplicate
commonScripts[key].forEach(function(commonFileInfo) {
crossFileDuplicates.push({
script: item.script,
file1: skinFile,
wiki1: item.sourceWiki,
file2: commonFileInfo.file,
wiki2: commonFileInfo.wiki,
commonScript: commonFileInfo.originalScript
});
});
}
});
}
});
if (crossFileDuplicates.length > 0) {
addProgress(' → ⚠ Found ' + crossFileDuplicates.length + ' cross-file duplicate(s)', '#f80');
crossFileDuplicates.forEach(function(dupe) {
var scriptUrl = getPageUrl(dupe.script.normalizedPath, dupe.script.wiki);
var file1Display = dupe.file1 + (dupe.wiki1 ? ' (on ' + dupe.wiki1 + ')' : '');
var file2Display = dupe.file2 + (dupe.wiki2 ? ' (on ' + dupe.wiki2 + ')' : '');
// Show both the original paths for clarity
var path1 = dupe.script.path;
var path2 = dupe.commonScript.path;
if (path1 === path2) {
addProgress(' ⚠ <a href="' + scriptUrl + '" target="_blank" style="color: #f80;">' + path1 + '</a> appears in both ' + file1Display + ' and ' + file2Display, '#f80');
} else {
addProgress(' ⚠ <a href="' + scriptUrl + '" target="_blank" style="color: #f80;">' + path1 + '</a> (in ' + file1Display + ') is the same as <strong>' + path2 + '</strong> (in ' + file2Display + ')', '#f80');
}
});
} else {
addProgress(' → No cross-file duplicates found', '#080');
}
addProgress(''); // blank line
// Show final summary
showFinalSummary(crossFileDuplicates);
}
// Show final summary
function showFinalSummary(crossFileDuplicates) {
var summaryHtml = '';
var hasIssues = false;
if (allMissing.length > 0) {
hasIssues = true;
summaryHtml += '<div style="margin-bottom: 15px;"><strong style="color: #c00;">✗ Found ' + allMissing.length + ' missing script(s):</strong><br>';
summaryHtml += '<ul style="margin: 5px 0; padding-left: 20px; text-align: left;">';
allMissing.forEach(function(script) {
var scriptUrl = getPageUrl(script.normalizedPath, script.wiki);
summaryHtml += '<li style="margin: 5px 0;"><a href="' + scriptUrl + '" target="_blank"><strong>' + script.path + '</strong></a>';
if (script.wiki) {
summaryHtml += ' <small style="color: #666;">(on ' + script.wiki + ')</small>';
}
var fileUrl = getPageUrl('User:' + username + '/' + script.sourceFile, script.sourceWiki);
summaryHtml += '<br><small style="color: #666;">Found in: <a href="' + fileUrl + '" target="_blank">' + script.sourceFile + '</a>';
if (script.sourceWiki) {
summaryHtml += ' <small style="color: #666;">(on ' + script.sourceWiki + ')</small>';
}
summaryHtml += '</small></li>';
});
summaryHtml += '</ul></div>';
}
if (allInternalDuplicates.length > 0) {
hasIssues = true;
summaryHtml += '<div style="margin-bottom: 15px;"><strong style="color: #f80;">⚠ Found ' + allInternalDuplicates.length + ' internal duplicate(s):</strong><br>';
summaryHtml += '<ul style="margin: 5px 0; padding-left: 20px; text-align: left;">';
allInternalDuplicates.forEach(function(item) {
var scriptUrl = getPageUrl(item.script.normalizedPath, item.script.wiki);
var fileUrl = getPageUrl('User:' + username + '/' + item.sourceFile, item.sourceWiki);
summaryHtml += '<li style="margin: 5px 0;"><a href="' + scriptUrl + '" target="_blank">' + item.script.path + '</a>';
if (item.script.wiki) {
summaryHtml += ' <small style="color: #666;">(on ' + item.script.wiki + ')</small>';
}
summaryHtml += '<br><small style="color: #666;">Duplicate in: <a href="' + fileUrl + '" target="_blank">' + item.sourceFile + '</a>';
if (item.sourceWiki) {
summaryHtml += ' <small style="color: #666;">(on ' + item.sourceWiki + ')</small>';
}
summaryHtml += '</small></li>';
});
summaryHtml += '</ul></div>';
}
if (crossFileDuplicates.length > 0) {
hasIssues = true;
summaryHtml += '<div style="margin-bottom: 15px;"><strong style="color: #f80;">⚠ Found ' + crossFileDuplicates.length + ' cross-file duplicate(s):</strong><br>';
summaryHtml += '<ul style="margin: 5px 0; padding-left: 20px; text-align: left;">';
crossFileDuplicates.forEach(function(dupe) {
var scriptUrl = getPageUrl(dupe.script.normalizedPath, dupe.script.wiki);
var file1Url = getPageUrl('User:' + username + '/' + dupe.file1, dupe.wiki1);
var file2Url = getPageUrl('User:' + username + '/' + dupe.file2, dupe.wiki2);
summaryHtml += '<li style="margin: 5px 0;"><a href="' + scriptUrl + '" target="_blank">' + dupe.script.path + '</a>';
if (dupe.script.wiki) {
summaryHtml += ' <small style="color: #666;">(on ' + dupe.script.wiki + ')</small>';
}
summaryHtml += '<br><small style="color: #666;">Found in: <a href="' + file1Url + '" target="_blank">' + dupe.file1 + '</a>';
if (dupe.wiki1) {
summaryHtml += ' (on ' + dupe.wiki1 + ')';
}
summaryHtml += ' and <a href="' + file2Url + '" target="_blank">' + dupe.file2 + '</a>';
if (dupe.wiki2) {
summaryHtml += ' (on ' + dupe.wiki2 + ')';
}
summaryHtml += '</small></li>';
});
summaryHtml += '</ul></div>';
}
if (!hasIssues) {
summaryHtml = '✓ All scripts exist and no duplicates found!';
$summary.html(summaryHtml).css('color', 'green');
} else {
$summary.html(summaryHtml).css('color', '#000');
}
$title.text('Check complete - User:' + username);
}
// Start processing
processJsFile(0);
}
})();
// </nowiki>
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.