Jump to content

User:Polygnotus/Scripts/SourceTable3.js

From Wikipedia, the free encyclopedia
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.
// <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>