User:Polygnotus/Scripts/SourceTable3.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/SourceTable3.
// <nowiki>
// AfD Source Assessment Tool
//Features:
//Continue where you left off
//autopopulate the refs
/*
2add:
- give eachs ref an archive.org link by default if none exists?
-numbering the wikitable
- bug: comments aren't displayed yet
*/
/*
[[User:Polygnotus/Scripts/SourceTable]]
[[Wikipedia:User_scripts/Requests#Source_assessing]]
[[Template:Source assess table]] used in [[Wikipedia:Articles for deletion/Nambiar Builders]] is a lot like [[User:Polygnotus/Scripts/SourceTable]].
I should also check out [[User:Awesome Aasim/xfdvote]]. See also [[Wikipedia:Notability_(organizations_and_companies)#How_to_apply_the_criteria]] -- [[Wikipedia_talk:WikiProject_Articles_for_creation#Source_Assessment_Tables]]
https://en.wikipedia.org/wiki/Wikipedia:User_scripts/Requests/Archive_10#h-Source_assessing-20241206231600
There's also {{ORGCRIT assess table}}, which is a variation specific to WP:NCORP (organizations and companies notability criteria).
*/
// Only run on AfD edit pages
var currentAction = mw.config.get('wgAction');
if (mw.config.get('wgPageName').startsWith('Wikipedia:Articles_for_deletion/') &&
(currentAction === 'edit' || currentAction === 'submit')) {
$(function() {
console.log('AfD Source Assessment script running...');
// Add button to page heading
if ($('#firstHeading').length) {
$('<button>')
.attr('id', 'assessSourcesButton')
.text('Assess Sources')
.css({
'margin-left': '10px',
'margin-bottom': '5px',
'display': 'inline-block',
'vertical-align': 'middle',
'font-size': '15px',
'padding': '4px 8px'
})
.click(function(e) {
e.preventDefault();
showSourceAssessmentPopup();
})
.appendTo('#firstHeading');
console.log('Assess Sources button added');
} else if ($('#editform').length) {
$('<button>')
.attr('id', 'assessSourcesButton')
.text('Assess Sources')
.css({
'margin': '10px 0',
'display': 'block'
})
.click(function(e) {
e.preventDefault();
showSourceAssessmentPopup();
})
.prependTo('#editform');
console.log('Assess Sources button added to editform');
} else if ($('.editButtons').length) {
// Fallback for preview pages
$('<button>')
.attr('id', 'assessSourcesButton')
.text('Assess Sources')
.css({
'margin': '10px 0',
'display': 'inline-block',
'padding': '4px 8px'
})
.click(function(e) {
e.preventDefault();
showSourceAssessmentPopup();
})
.prependTo('.editButtons');
console.log('Assess Sources button added to editButtons');
}
});
}
// Global state
var assessmentState = {
sources: [],
assessments: []
};
function showSourceAssessmentPopup() {
// Extract article name
var afdPageName = mw.config.get('wgPageName');
var articleName = extractArticleName(afdPageName);
console.log('Loading sources for:', articleName);
// Always try to load state from existing wikicode in textbox first
var editTextarea = document.getElementById('wpTextbox1');
if (!editTextarea) {
mw.notify('Error: Could not find edit textarea.', {type: 'error'});
return;
}
var existingState = parseExistingAssessment(editTextarea.value);
if (existingState) {
// Use existing state from textbox
assessmentState = existingState;
console.log('Loaded existing state with', existingState.sources.length, 'sources');
console.log('First source assessments:', existingState.assessments[0]);
if (existingState.assessments.length > 1) {
console.log('Second source assessments:', existingState.assessments[1]);
}
displayPopup();
} else {
// No existing assessment found, fetch article content to extract sources
console.log('No existing assessment found, fetching article sources...');
new mw.Api().get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: articleName,
formatversion: 2
}).done(function(data) {
try {
if (!data.query || !data.query.pages || data.query.pages.length === 0) {
mw.notify('Error: Could not retrieve article data.', {type: 'error'});
return;
}
var page = data.query.pages[0];
if (page.missing) {
mw.notify('The article "' + articleName + '" could not be found.', {type: 'error'});
return;
}
if (!page.revisions || page.revisions.length === 0) {
mw.notify('No revisions found for the article.', {type: 'error'});
return;
}
var pageContent = page.revisions[0].content;
var references = extractReferences(pageContent);
if (references.length === 0) {
mw.notify('No references found in the article.', {type: 'warning'});
return;
}
// Initialize state
assessmentState.sources = references;
assessmentState.assessments = references.map(function() {
return {
independent: '',
reliable: '',
significant: '',
comments: ''
};
});
displayPopup();
} catch (error) {
mw.notify('An error occurred: ' + error.message, {type: 'error'});
console.error('Error:', error);
}
}).fail(function(error) {
mw.notify('Failed to fetch article content.', {type: 'error'});
console.error('API error:', error);
});
}
}
function displayPopup() {
// Remove existing popup if present
$('#sourceAssessmentPopup').remove();
// Create popup overlay
var popup = $('<div>')
.attr('id', 'sourceAssessmentPopup')
.css({
'position': 'fixed',
'top': '0',
'left': '0',
'width': '100%',
'height': '100%',
'background': 'rgba(0,0,0,0.5)',
'z-index': '10000',
'display': 'flex',
'align-items': 'center',
'justify-content': 'center',
'overflow': 'auto'
});
// Create popup content
var content = $('<div>')
.css({
'background': 'white',
'padding': '20px',
'border-radius': '5px',
'width': '90%',
'max-height': '90%',
'overflow': 'auto',
'position': 'relative'
});
// Add close button to top right
var closeButton = $('<button>')
.text('×')
.css({
'position': 'absolute',
'top': '10px',
'right': '10px',
'border': 'none',
'background': '#f0f0f0',
'font-size': '24px',
'width': '30px',
'height': '30px',
'border-radius': '50%',
'cursor': 'pointer',
'line-height': '1',
'padding': '0'
})
.hover(
function() { $(this).css('background', '#e0e0e0'); },
function() { $(this).css('background', '#f0f0f0'); }
)
.click(function() {
popup.remove();
});
content.append(closeButton);
// Add title
content.append($('<h2>').text('Source Assessment for WP:GNG').css('margin-top', '0'));
// Create table
var table = $('<table>')
.attr('id', 'sourceAssessmentTable')
.css({
'width': '100%',
'border-collapse': 'collapse',
'margin': '20px 0'
});
// Add header
var thead = $('<thead>');
var headerRow = $('<tr>');
['#', 'Source', 'Independent', 'Reliable', 'Significant Coverage', 'Count Toward GNG', 'Comments'].forEach(function(header) {
var th = $('<th>').text(header).css({
'border': '1px solid #ccc',
'padding': '8px',
'background': '#f0f0f0',
'text-align': 'left'
});
// Set specific width for Count Toward GNG column
if (header === 'Count Toward GNG') {
th.css('width', '20px');
}
headerRow.append(th);
});
thead.append(headerRow);
table.append(thead);
// Add body
var tbody = $('<tbody>');
assessmentState.sources.forEach(function(source, index) {
var row = createAssessmentRow(source, index);
tbody.append(row);
});
table.append(tbody);
content.append(table);
// Add buttons
var buttonDiv = $('<div>').css({'text-align': 'right', 'margin-top': '10px'});
$('<button>')
.text('Generate Wikicode')
.css({'margin-right': '10px', 'padding': '8px 16px'})
.click(function() {
generateWikicode();
})
.appendTo(buttonDiv);
$('<button>')
.text('Close')
.css({'padding': '8px 16px'})
.click(function() {
popup.remove();
})
.appendTo(buttonDiv);
content.append(buttonDiv);
popup.append(content);
$('body').append(popup);
}
function createAssessmentRow(source, index) {
var assessment = assessmentState.assessments[index];
var row = $('<tr>').attr('data-index', index);
// Number column
var numberCell = $('<td>').css({
'border': '1px solid #ccc',
'padding': '8px',
'text-align': 'center',
'font-weight': 'bold'
}).text(index + 1);
row.append(numberCell);
// Source column
var sourceCell = $('<td>').css({
'border': '1px solid #ccc',
'padding': '8px',
'max-width': '300px',
'overflow': 'auto'
}).html(source);
row.append(sourceCell);
// Create radio button option boxes for Independent, Reliable, Significant Coverage
['independent', 'reliable', 'significant'].forEach(function(field) {
var cell = $('<td>').css({
'border': '1px solid #ccc',
'padding': '8px'
});
var radioGroup = $('<div>').css({'display': 'flex', 'flex-direction': 'column', 'gap': '4px'});
[
{value: 'yes', text: 'Yes'},
{value: 'no', text: 'No'},
{value: 'unknown', text: 'Unknown'},
{value: 'partial', text: 'Partial'}
].forEach(function(option) {
var radioId = 'radio_' + index + '_' + field + '_' + option.value;
var label = $('<label>').css({'display': 'flex', 'align-items': 'center', 'cursor': 'pointer'});
var radio = $('<input>')
.attr('type', 'radio')
.attr('name', 'radio_' + index + '_' + field)
.attr('id', radioId)
.val(option.value)
.css({'margin-right': '4px'});
if (assessment[field] === option.value) {
radio.attr('checked', 'checked');
}
radio.change(function() {
assessmentState.assessments[index][field] = $(this).val().trim();
updateRowStatus(row, index);
});
label.append(radio);
label.append($('<span>').text(option.text));
radioGroup.append(label);
});
cell.append(radioGroup);
row.append(cell);
});
// Count Toward GNG column
var gngCell = $('<td>').css({
'border': '1px solid #ccc',
'padding': '8px',
'text-align': 'center',
'width': '20px'
}).attr('data-gng', '');
row.append(gngCell);
// Comments column
var commentsCell = $('<td>').css({
'border': '1px solid #ccc',
'padding': '8px'
});
var commentsTextarea = $('<textarea>')
.css({'width': '100%', 'min-height': '40px'})
.val(assessment.comments)
.on('input', function() {
assessmentState.assessments[index].comments = $(this).val().trim();
});
commentsCell.append(commentsTextarea);
row.append(commentsCell);
// Update initial status
updateRowStatus(row, index);
return row;
}
function updateRowStatus(row, index) {
var assessment = assessmentState.assessments[index];
var gngCell = row.find('td[data-gng]');
var allYes = assessment.independent === 'yes' &&
assessment.reliable === 'yes' &&
assessment.significant === 'yes';
if (allYes) {
row.css('background-color', '#d4edda');
gngCell.html('<img alt="Yes" src="//upload.wikimedia.org/wikipedia/commons/thumb/2/26/Check-green.svg/20px-Check-green.svg.png" width="13" height="13">');
gngCell.attr('data-gng', 'yes');
} else if (assessment.independent || assessment.reliable || assessment.significant) {
row.css('background-color', '#f8d7da');
gngCell.html('<img alt="No" src="//upload.wikimedia.org/wikipedia/commons/thumb/4/48/Dark_Red_x.svg/20px-Dark_Red_x.svg.png" width="13" height="13">');
gngCell.attr('data-gng', 'no');
} else {
row.css('background-color', 'white');
gngCell.html('');
gngCell.attr('data-gng', '');
}
}
function generateWikicode() {
var wikicode = '\n\n{{User:Polygnotus/Templates/SourceAssessTable\n';
assessmentState.sources.forEach(function(source, index) {
var assessment = assessmentState.assessments[index];
var num = index + 1;
// Escape pipe characters
wikicode += '|src' + num + '=' + source + '\n';
wikicode += '|num' + num + '=' + num + '\n';
wikicode += '|ind' + num + '=' + assessment.independent + '\n';
wikicode += '|rel' + num + '=' + assessment.reliable + '\n';
wikicode += '|sig' + num + '=' + assessment.significant + '\n';
wikicode += '|comments' + num + '=' + assessment.comments + '\n';
});
wikicode += '}}\n';
// Insert into edit textarea
var editTextarea = document.getElementById('wpTextbox1');
if (editTextarea) {
// Remove existing assessment if present
var currentContent = editTextarea.value;
var pattern = /\n*\{\{User:Polygnotus\/Templates\/SourceAssessTable[\s\S]*?\}\}\n*/;
currentContent = currentContent.replace(pattern, '');
editTextarea.value = currentContent + wikicode;
$(editTextarea).trigger('change');
mw.notify('Source assessment added to edit area.', {type: 'success'});
$('#sourceAssessmentPopup').remove();
} else {
mw.notify('Failed to find edit textarea.', {type: 'error'});
}
}
function parseExistingAssessment(content) {
// Check if there's already an assessment in the content
// We need to find the template and handle nested braces properly
var startPattern = /\{\{User:Polygnotus\/Templates\/SourceAssessTable/;
var startMatch = content.search(startPattern);
if (startMatch === -1) {
console.log('No existing assessment template found');
return null;
}
// Find the matching closing braces by counting
var braceCount = 0;
var inTemplate = false;
var templateEnd = -1;
for (var i = startMatch; i < content.length - 1; i++) {
if (content[i] === '{' && content[i + 1] === '{') {
braceCount++;
inTemplate = true;
i++; // Skip next character
} else if (content[i] === '}' && content[i + 1] === '}') {
braceCount--;
if (braceCount === 0 && inTemplate) {
templateEnd = i + 2;
break;
}
i++; // Skip next character
}
}
if (templateEnd === -1) {
console.log('Could not find end of template');
return null;
}
var fullTemplate = content.substring(startMatch, templateEnd);
console.log('Found template, length:', fullTemplate.length);
// Extract just the parameters (between first newline after template name and closing braces)
var templateNameEnd = fullTemplate.indexOf('\n');
if (templateNameEnd === -1) {
console.log('Template has no parameters');
return null;
}
var templateContent = fullTemplate.substring(templateNameEnd + 1, fullTemplate.length - 2);
var sources = [];
var assessments = [];
// Parse parameters - handle multiline values
var params = {};
var currentParam = null;
var currentValue = '';
var lines = templateContent.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
// Check if this line starts a new parameter
var paramMatch = line.match(/^\|([a-zA-Z]+\d+)=(.*)$/);
if (paramMatch) {
// Save previous parameter if exists
if (currentParam) {
params[currentParam] = currentValue;
}
// Start new parameter
currentParam = paramMatch[1];
currentValue = paramMatch[2];
} else if (currentParam && line.trim() !== '') {
// Continue previous parameter value (multiline)
currentValue += '\n' + line;
}
}
// Don't forget to save the last parameter
if (currentParam) {
params[currentParam] = currentValue;
}
console.log('Parsed parameters:', params);
// Determine number of sources
var maxNum = 0;
Object.keys(params).forEach(function(key) {
var numMatch = key.match(/\d+$/);
if (numMatch) {
var num = parseInt(numMatch[0]);
if (num > maxNum) maxNum = num;
}
});
console.log('Found', maxNum, 'sources');
// Extract sources and assessments
for (var i = 1; i <= maxNum; i++) {
if (params['src' + i]) {
var source = params['src' + i].replace(/\{\{!\}\}/g, '|').trim();
sources.push(source);
assessments.push({
independent: (params['ind' + i] || '').trim(),
reliable: (params['rel' + i] || '').trim(),
significant: (params['sig' + i] || '').trim(),
comments: (params['comments' + i] || '').trim()
});
}
}
if (sources.length === 0) {
console.log('No sources found in template');
return null;
}
console.log('Successfully parsed', sources.length, 'sources with assessments');
return {
sources: sources,
assessments: assessments
};
}
function extractArticleName(afdPageName) {
var articleName = afdPageName.replace('Wikipedia:Articles_for_deletion/', '');
// Handle nomination numbers
var nominationMatch = articleName.match(/(.*) \((\d+)(st|nd|rd|th) nomination\)$/);
if (nominationMatch) {
articleName = nominationMatch[1];
}
return articleName;
}
function extractReferences(content) {
var references = [];
var index = 0;
while (true) {
var startIndex = content.indexOf('<ref', index);
if (startIndex === -1) break;
var endIndex = content.indexOf('>', startIndex);
if (endIndex === -1) break;
var tagContent = content.substring(startIndex, endIndex + 1);
// Skip self-closing tags
if (tagContent.endsWith('/>')) {
index = endIndex + 1;
continue;
}
var refEndIndex = content.indexOf('</ref>', endIndex);
if (refEndIndex === -1) break;
var refContent = content.substring(endIndex + 1, refEndIndex).trim();
// Skip empty refs
if (refContent) {
references.push(refContent);
}
index = refEndIndex + 6;
}
console.log('Extracted', references.length, 'references');
return references;
}
// </nowiki>