Jump to content

User:Polygnotus/Scripts/DuplicateParameters.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.
// Improved Wikipedia Duplicate Parameters Detector
// Add this to your common.js file
// This script combines the best features of both duplicate parameter detection scripts

jQuery(document).ready(function($) {
  // Only run on edit pages
  if (mw.config.get('wgAction') !== 'edit' && mw.config.get('wgNamespaceNumber') !== -1) {
    return;
  }
  
  // Configuration options (can be overridden in user scripts)
  var config = {
    buttonText: 'Check Duplicate Parameters',
    summaryText: "Clean up [[Category:Pages using duplicate arguments in template calls|duplicate template arguments]]",
    moreFoundMessage: "More duplicates found, fix some and run again!",
    noneFoundMessage: 'No duplicate parameters found.',
    showResultsBox: true,
    showAlertBox: false, // Alerts disabled by default
    maxAlertsBeforeMessage: 5,
    debugMode: false // Add debug mode to help diagnose issues
  };
  
  // Allow overriding configuration
  if (typeof findargdupseditsummary === 'string') { config.summaryText = findargdupseditsummary; }
  if (typeof findargdupsmorefound === 'string') { config.moreFoundMessage = findargdupsmorefound; }
  if (typeof findargdupslinktext === 'string') { config.buttonText = findargdupslinktext; }
  if (typeof findargdupsnonefound === 'string') { config.noneFoundMessage = findargdupsnonefound; }
  if (typeof findargdupsresultsbox === 'string') { config.showResultsBox = true; }
  
  var myContent = document.getElementsByName('wpTextbox1')[0] || $('#wpTextbox1')[0];
  
  // Add both UI options (button next to title and toolbar link)
  
  // 1. Add button next to title
  $('#firstHeadingTitle').after(
    $('<button>')
      .attr('id', 'check-duplicate-params')
      .text(config.buttonText)
      .css({
        'margin-left': '15px',
        'font-size': '0.8em',
        'padding': '3px 8px',
        'cursor': 'pointer'
      })
      .click(function(e) {
        e.preventDefault();
        findDuplicateParameters();
      })
  );
  
  // 2. Add toolbar link
  mw.loader.using(['mediawiki.util']).done(function() {
    var portletlink = mw.util.addPortletLink('p-tb', '#', config.buttonText, 't-fdup');
    $(portletlink).click(function(e) {
      e.preventDefault();
      findDuplicateParameters();
    });
  });
  
  // Add a message area to display results
  if (!$('#duplicate-params-message').length) {
    $('body').append(
      $('<div>')
        .attr('id', 'duplicate-params-message')
        .css({
          'position': 'fixed',
          'bottom': '20px',
          'right': '20px',
          'padding': '10px',
          'background-color': '#f8f9fa',
          'border': '1px solid #a2a9b1',
          'border-radius': '3px',
          'box-shadow': '0 2px 5px rgba(0, 0, 0, 0.1)',
          'z-index': '1000',
          'display': 'none'
        })
    );
  }
  
  // Add a debug log area if debug mode is enabled
  if (config.debugMode && !$('#debug-log').length) {
    $('body').append(
      $('<div>')
        .attr('id', 'debug-log')
        .css({
          'position': 'fixed',
          'top': '20px',
          'right': '20px',
          'width': '600px',
          'max-height': '400px',
          'overflow-y': 'auto',
          'padding': '10px',
          'background-color': '#f8f9fa',
          'border': '1px solid #a2a9b1',
          'border-radius': '3px',
          'box-shadow': '0 2px 5px rgba(0, 0, 0, 0.1)',
          'z-index': '1000',
          'font-size': '0.8em'
        })
    );
  }
  
  // Debug log function
  function debugLog(message) {
    if (config.debugMode && $('#debug-log').length) {
      $('#debug-log').append($('<div>').text(message));
      $('#debug-log').scrollTop($('#debug-log')[0].scrollHeight);
    }
  }
  
  // Function to add results box above the edit summary
  function addResultsBox(text, num) {
    var div = document.getElementById('wpSummaryLabel')?.parentNode;
    if (div) {
      if (num < 2) {
        if (document.getElementById('FindArgDupsResultsBox')) {
          document.getElementById('FindArgDupsResultsBox').innerHTML = '';
        } else {
          div.innerHTML = '<div id="FindArgDupsResultsBox"></div>' + div.innerHTML;
        }
      }
      let div1 = document.getElementById('FindArgDupsResultsBox');
      if (div1) {
        text = text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        div1.innerHTML = div1.innerHTML + '<div class="FindArgDupsResultsBox" ' +
          'id="FindArgDupsResultsBox-' + num + '" ' +
          'style="max-height:5em; overflow:auto; padding:5px; border:#aaa 1px solid; ' +
          'background-color:cornsilk;">' + text + '</div>' + "\n";
      }
    }
  }
  
  // Function to clear the results box
  function clearResultsBox() {
    var div = document.getElementById('wpSummaryLabel')?.parentNode;
    if (div) {
      if (document.getElementById('FindArgDupsResultsBox')) {
        document.getElementById('FindArgDupsResultsBox').innerHTML = '';
      }
    }
  }
  
  // Function to show a message
  function showMessage(message, isSuccess) {
    if (config.showResultsBox) {
      clearResultsBox();
      addResultsBox(message, 1);
    }
    
    const $message = $('#duplicate-params-message');
    $message.text(message)
      .css('background-color', isSuccess ? '#d5fdf4' : '#fdd')
      .fadeIn()
      .delay(5000)
      .fadeOut();
  }
  
  // Create and show a popup for the user to choose which parameter to keep
  function showParameterChoicePopup(paramName, duplicates, templateText, callback) {
    debugLog("Showing parameter choice popup for \"" + paramName + "\" with " + duplicates.length + " values");
    
    // Create a dialog if it doesn't exist
    if (!$('#parameter-choice-dialog').length) {
      $('body').append('<div id="parameter-choice-dialog" title="Duplicate Parameters Found"></div>');
    }
    
    // Clear previous content
    const $dialog = $('#parameter-choice-dialog');
    $dialog.empty();
    
    // Add explanation text
    $dialog.append('<p>Duplicate parameter "' + paramName + '" found with different values. Please select which one to keep:</p>');
    
    // Show the template where the duplicates were found
    $dialog.append('<p style="max-height: 100px; overflow: auto; border: 1px solid #ddd; padding: 5px; background-color: #f8f8f8;">' + 
      templateText.replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</p>');
    
    // Create the options list
    const $list = $('<div class="parameter-options"></div>');
    
    // Log the duplicate values for debugging
    for (let i = 0; i < duplicates.length; i++) {
      debugLog("Option " + i + ": " + paramName + "=" + duplicates[i].value);
    }
    
    duplicates.forEach(function(param, index) {
      const $option = $('<div class="parameter-option"></div>')
        .append($('<input type="radio">')
          .attr('name', 'param-choice')
          .attr('id', 'param-' + index)
          .attr('value', index)
          .prop('checked', index === 0)
        )
        .append($('<label></label>')
          .attr('for', 'param-' + index)
          .text(paramName + ' = ' + param.value)
        );
      $list.append($option);
    });
    
    $dialog.append($list);
    
    // Open the dialog
    try {
      $dialog.dialog({
        modal: true,
        width: 400,
        buttons: {
          "Keep Selected": function() {
            const selectedIndex = parseInt($('input[name="param-choice"]:checked').val(), 10);
            debugLog("User selected to keep option " + selectedIndex + ": " + paramName + "=" + duplicates[selectedIndex].value);
            callback(selectedIndex);
            $(this).dialog("close");
          },
          Cancel: function() {
            debugLog("User cancelled parameter choice");
            $(this).dialog("close");
          }
        }
      });
    } catch (e) {
      debugLog("Error showing dialog: " + e.message);
      // Fallback if dialog fails - keep the first option
      callback(0);
    }
  }
  
  // Manual DOM-based parameter selector for when jQuery dialog fails
  function createSimpleDialog(paramName, values, callback) {
    const dialog = document.createElement('div');
    dialog.id = 'manual-parameter-choice';
    dialog.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:white;padding:20px;border:1px solid #ccc;box-shadow:0 0 10px rgba(0,0,0,0.2);z-index:1000;';
    
    let html = '<h3>Duplicate Parameter Found</h3>' +
      '<p>Parameter ' + paramName + ' has duplicate values. Choose which one to keep:</p>';
    
    values.forEach(function(param, index) {
      html += '<div>' +
        '<input type="radio" id="value-' + index + '" name="' + paramName + '" value="' + index + '"' + 
        (index === 0 ? ' checked' : '') + '>' +
        '<label for="value-' + index + '">' + paramName + ' = ' + param.value + '</label>' +
        '</div>';
    });
    
    html += '<div style="margin-top:15px;text-align:right;">' +
      '<button id="cancel-btn" style="margin-right:10px;padding:5px 10px;">Cancel</button>' +
      '<button id="confirm-btn" style="padding:5px 10px;">Keep Selected</button>' +
      '</div>';
    
    dialog.innerHTML = html;
    document.body.appendChild(dialog);
    
    // Add event listeners
    document.getElementById('confirm-btn').addEventListener('click', function() {
      const selected = document.querySelector('input[name="' + paramName + '"]:checked');
      const selectedIndex = parseInt(selected.value, 10);
      document.body.removeChild(dialog);
      callback(selectedIndex);
    });
    
    document.getElementById('cancel-btn').addEventListener('click', function() {
      document.body.removeChild(dialog);
    });
  }
  
  // Main function to find duplicate parameters
  function findDuplicateParameters() {
    if (!myContent) return;
    
    // Flag used to determine if we have issued an alert popup
    var alertsIssued = 0;
    // Flag used to determine if we've selected one of the problem templates yet
    var selectedOne = false;
    // Array used to hold the list of unnested templates
    var templateList = [];
    // Variable to store the original content
    var originalContent = myContent.value;
    // Variable to store the modified content when removing duplicates
    var updatedContent = originalContent;
    // Count of auto-removed duplicates
    var autoRemovedCount = 0;
    // Count of templates with duplicates
    var duplicateTemplatesCount = 0;
    // Count of parameters requiring user choice
    var userChoiceCount = 0;
    
    // Helper function to escape regex special characters
    function escapeRegExp(string) {
      return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }
    
    // Helper function to properly remove a parameter from template text
    function removeParamFromTemplate(template, paramName, valueToRemove) {
      // This regex matches the entire parameter including the | at the beginning
      const paramRegex = new RegExp("\\|(\\s*" + escapeRegExp(paramName) + "\\s*=\\s*" + escapeRegExp(valueToRemove) + "\\s*)(?=\\||}})", "g");
      return template.replace(paramRegex, '');
    }
    
    // Preprocess the text to handle various special cases
    function preprocessText(text) {
      // Copy the contents of the text window so we can modify it without problems
      var processed = text;
      
      // Remove some includeonly, noinclude, and onlyinclude tags
      processed = processed.replace(/<\/?[ ]*(?:includeonly|noinclude|onlyinclude)[ ]*>/gi, '');
      // Remove PAGENAME, BASEPAGENAME, ... nested inside of triple braces
      processed = processed.replace(/\{\{\{[^\{\}]*\|[ ]*\{\{[A-Z]+\}\}\}\}\}/g, '');
      // Mangle some ref tags
      processed = processed.replace(/(<ref[^<>=]*name[ ]*)=/gi, '$1&#61;');
      processed = processed.replace(/(<ref[^<>=]*group[ ]*)=/gi, '$1&#61;');
      
      // Mangle some math tags
      let loopcount = 0;
      while ((processed.search(/<[\s]*math[^<>]*>[^<>=]*=/gi) >= 0) && (loopcount < 10)) {
        processed = processed.replace(/(<[\s]*math[^<>]*>[^<>=]*)=/gi, '$1&#61;');
        loopcount++;
      }
      
      // Remove some triple braces and parserfunctions inside of triple braces
      loopcount = 0;
      while ((processed.search(/\{\{\{[^\{\}]*\}\}\}/g) >= 0) && (loopcount < 5)) {
        processed = processed.replace(/\{\{\{[^\{\}]*\}\}\}/g, '');
        processed = processed.replace(/\{\{#[a-z]+:[^{}=]*\}\}/gi, '');
        loopcount++;
      }
      
      // Replace some bare braces with HTML equivalent
      processed = processed.replace(/([^\{])\{([^\{])/g, '$1&#123;$2');
      processed = processed.replace(/([^\}])\}([^\}])/g, '$1&#125;$2');
      
      // Remove newlines and tabs which confuse the regexp search
      processed = processed.replace(/[\s]/gm, ' ');
      // Compress whitespace
      processed = processed.replace(/[\s][\s]+/gm, ' ');
      
      // Remove some nowiki and pre text
      processed = processed.replace(/<nowiki[^<>]*>(?:<[^\/]|[^<])*<\/nowiki[^<>]*>/gi, '');
      processed = processed.replace(/<pre[^<>]*>(?:<[^\/]|[^<])*<\/pre[^<>]*>/gi, '');
      
      // Remove some HTML comments
      processed = processed.replace(/<!--(?:[^>]|[^\-]>|[^\-]->)*-->/gm, '');
      
      // Modify some = inside of file/image/wikilinks which cause false positives
      loopcount = 0;
      while ((processed.search(/\[\[[^\[\]\{\}]*=/gi) >= 0) && (loopcount < 5)) {
        processed = processed.replace(/(\[\[[^\[\]\{\}]*)=/gi, '$1&#61;');
        loopcount++;
      }
      
      return processed;
    }
    
    // Function to unnest templates and extract them to the templateList array
    function extractTemplates(text) {
      var processed = text;
      var loopcount = 0;
      
      while ((processed.search(/(?:\{\{|\}\})/g) >= 0) && (loopcount < 20)) {
        // Replace some bare braces with HTML equivalent
        processed = processed.replace(/([^\{])\{([^\{])/g, '$1&#123;$2');
        processed = processed.replace(/([^\}])\}([^\}])/g, '$1&#125;$2');
        
        // Split into chunks, isolating the unnested templates
        var strlist = processed.split(/(\{\{[^\{\}]*\}\})/);
        
        // Loop through the chunks, removing the unnested templates
        for (let i = 0; i < strlist.length; i++) {
          if (strlist[i].search(/^\{\{[^\{\}]*\}\}$/) >= 0) {
            templateList.push(strlist[i]);
            strlist[i] = '';
          }
        }
        
        // Join the chunks back together for the next iteration
        processed = strlist.join('');
        loopcount++;
      }
    }
    
    // Function to add numbers for unnamed parameters
    function processUnnamedParameters(template) {
      let processed = template;
      
      // Add numbers for unnamed parameters in #invoke templates
      processed = processed.replace(/(\{\{[\s_]*#invoke[\s ]*:[^{}\|]*)\|([^{}\|=]*\|)/gi, '$1|0=$2');
      
      // Add numbers for other unnamed parameters
      let unp = 0;
      while ((processed.search(/(\{\{(?:[^{}\[\]]|\[\[[^\[\]]*\]\])*?\|)((?:[^{}\[\]=\|]|\[[^\[\]=]*\]|\[\[[^\[\]]*\]\])*(?:\||\}\}))/) >= 0) && (unp < 25)) {
        unp++;
        processed = processed.replace(/(\{\{(?:[^{}\[\]]|\[\[[^\[\]]*\]\])*?\|)((?:[^{}\[\]=\|]|\[[^\[\]=]*\]|\[\[[^\[\]]*\]\])*(?:\||\}\}))/, '$1' + unp + '=$2');
      }
      
      return processed;
    }
    
    // Function to extract a parameter value from a template
    function extractParameterValue(template, paramStart, nextParam) {
      let valueEnd = 0;
      let nestedCount = 0;
      let inLink = false;
      
      for (let j = 0; j < nextParam.length; j++) {
        if (nextParam[j] === '[' && nextParam[j+1] === '[') {
          inLink = true;
        } else if (nextParam[j] === ']' && nextParam[j+1] === ']') {
          inLink = false;
        } else if (nextParam[j] === '{') {
          nestedCount++;
        } else if (nextParam[j] === '}') {
          nestedCount--;
        } else if ((nextParam[j] === '|' || (nextParam[j] === '}' && nextParam[j+1] === '}')) && nestedCount <= 0 && !inLink) {
          valueEnd = j;
          break;
        }
      }
      
      if (valueEnd === 0) {
        valueEnd = nextParam.indexOf('|');
        if (valueEnd === -1) {
          valueEnd = nextParam.indexOf('}}');
          if (valueEnd === -1) {
            valueEnd = nextParam.length;
          }
        }
      }
      
      return nextParam.substring(0, valueEnd).trim();
    }
    
    // Function to find parameter duplicates in a template
    function findParameterDuplicates(template) {
      // Process ref tags inside templates
      let processedTemplate = template;
      let j = 0;
      while ((processedTemplate.search(/<ref[^<>\/]*>(?:<[^\/]|[^<])*=/gi) >= 0) && (j < 50)) {
        processedTemplate = processedTemplate.replace(/(<ref[^<>\/]*>(?:<[^\/]|[^<])*)=/gi, '$1&#61;');
        j++;
      }
      
      // Add numbers for unnamed parameters
      processedTemplate = processUnnamedParameters(processedTemplate);
      
      // Regular expression which matches a template arg
      const argexp = /\|\s*([^|={}\[\]]+)\s*=\s*([^|{}]+)(?=\||}})/g;
      
      // Map to store parameter names and their values
      const paramMap = new Map();
      
      // Find all parameters in the template
      let match;
      while ((match = argexp.exec(processedTemplate)) !== null) {
        const paramName = match[1].trim();
        const paramValue = match[2].trim();
        const paramFull = match[0];
        const paramPosition = match.index;
        
        debugLog("Found parameter: " + paramName + "=" + paramValue);
        
        // Check if this parameter already exists
        if (paramMap.has(paramName)) {
          const existingValues = paramMap.get(paramName);
          existingValues.push({
            value: paramValue,
            original: paramFull,
            position: paramPosition
          });
          
          paramMap.set(paramName, existingValues);
        } else {
          paramMap.set(paramName, [{
            value: paramValue,
            original: paramFull,
            position: paramPosition
          }]);
        }
      }
      
      // Check for duplicates
      const duplicateParams = [];
      for (const [paramName, values] of paramMap.entries()) {
        if (values.length > 1) {
          // Check if values are all the same
          const firstValue = values[0].value;
          const allSameValue = values.every(param => param.value === firstValue);
          
          debugLog("Duplicate parameter \"" + paramName + "\" found with " + values.length + " occurrences");
          debugLog("All values same? " + allSameValue);
          
          duplicateParams.push({
            name: paramName,
            values: values,
            allSameValue: allSameValue
          });
        }
      }
      
      return duplicateParams;
    }
    
    // Function to fix duplicate parameters in the content
    function fixDuplicateParameters(template, duplicateParams) {
      // Process each duplicate parameter
      for (const duplicate of duplicateParams) {
        const paramName = duplicate.name;
        const values = duplicate.values;
        const allSameValue = duplicate.allSameValue;
        
        debugLog("Processing duplicate: " + paramName + ", all same value: " + allSameValue);
        
        if (allSameValue) {
          // If all duplicates have the same value, keep only the first one
          for (let i = 1; i < values.length; i++) {
            const duplicateParam = values[i].original;
            const startPos = template.indexOf(duplicateParam);
            
            if (startPos !== -1) {
              // Get the text after the parameter name and =
              const afterEqualSign = template.substring(startPos + duplicateParam.length);
              
              // Find the end of the parameter value
              const paramValue = extractParameterValue(template, startPos + duplicateParam.length, afterEqualSign);
              
              // Use the helper function to properly remove the parameter
              updatedContent = removeParamFromTemplate(updatedContent, paramName, paramValue);
              
              autoRemovedCount++;
              debugLog("Auto-removed duplicate: " + paramName + "=" + paramValue);
            }
          }
        } else {
          // If duplicates have different values, show a popup for user to choose
          userChoiceCount++;
          debugLog("Showing choice dialog for " + paramName + " with " + values.length + " different values");
          
          try {
            showParameterChoicePopup(paramName, values, template, function(selectedIndex) {
              // Keep the selected parameter and remove others
              debugLog("User chose to keep " + paramName + "=" + values[selectedIndex].value);
              
              // Find positions of all duplicates in the original content
              const chosenValue = values[selectedIndex].value;
              
              // Create a temporary copy of the original content
              let tempContent = originalContent;
              
              // Process each duplicate to remove all except the selected one
              for (let i = 0; i < values.length; i++) {
                // Skip the one we want to keep
                if (i === selectedIndex) continue;
                
                const valueToRemove = values[i].value;
                debugLog("Removing duplicate: " + paramName + "=" + valueToRemove);
                
                // Remove this specific parameter value from the template
                tempContent = removeParamFromTemplate(tempContent, paramName, valueToRemove);
              }
              
              // Update the textbox with the modified content
              myContent.value = tempContent;
              updatedContent = tempContent;
              
              showMessage("Kept parameter: " + paramName + "=" + chosenValue, true);
            });
          } catch (e) {
            debugLog("Error showing jQuery dialog: " + e.message + ". Falling back to simple dialog.");
            
            // Fallback to simple DOM-based dialog if jQuery dialog fails
            createSimpleDialog(paramName, values, function(selectedIndex) {
              // Keep the selected parameter and remove others
              debugLog("User chose to keep " + paramName + "=" + values[selectedIndex].value);
              
              // Find positions of all duplicates in the original content
              const chosenValue = values[selectedIndex].value;
              
              // Create a temporary copy of the original content
              let tempContent = originalContent;
              
              // Process each duplicate to remove all except the selected one
              for (let i = 0; i < values.length; i++) {
                // Skip the one we want to keep
                if (i === selectedIndex) continue;
                
                const valueToRemove = values[i].value;
                debugLog("Removing duplicate: " + paramName + "=" + valueToRemove);
                
                // Remove this specific parameter value from the template
                tempContent = removeParamFromTemplate(tempContent, paramName, valueToRemove);
              }
              
              // Update the textbox with the modified content
              myContent.value = tempContent;
              updatedContent = tempContent;
              
              showMessage("Kept parameter: " + paramName + "=" + chosenValue, true);
            });
          }
        }
      }
    }
    
    // Main processing starts here
    debugLog("Starting duplicate parameter detection");
    
    // Preprocess the text
    const processedText = preprocessText(originalContent);
    
    // Extract templates
    extractTemplates(processedText);
    debugLog("Found " + templateList.length + " templates to check");
    
    // Process each template to find duplicates
    for (let i = 0; i < templateList.length; i++) {
      debugLog("Checking template " + (i+1) + " of " + templateList.length);
      const duplicateParams = findParameterDuplicates(templateList[i]);
      
      if (duplicateParams.length > 0) {
        duplicateTemplatesCount++;
        debugLog("Found " + duplicateParams.length + " duplicate parameters in template " + (i+1));
        
        if (!selectedOne) {
          // Try to select the template in the text area
          const templatePattern = templateList[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s*');
          const selectMatch = originalContent.match(new RegExp(templatePattern));
          
          if (selectMatch !== null) {
            myContent.setSelectionRange(selectMatch.index, selectMatch.index + selectMatch[0].length);
            myContent.focus();
            selectedOne = true;
          }
        }
        
        // Display information about the duplicates
        if (alertsIssued < config.maxAlertsBeforeMessage) {
          const duplicateNames = duplicateParams.map(param => param.name).join('", "');
          
          if (config.showResultsBox) {
            addResultsBox("Duplicate \"" + duplicateNames + "\" in\n" + templateList[i], alertsIssued + 1);
          }
          
          alertsIssued++;
          
          // Try to fix the duplicates
          fixDuplicateParameters(templateList[i], duplicateParams);
        } else if (alertsIssued === config.maxAlertsBeforeMessage) {
          // Show "more found" message in results box instead of alert
          if (config.showResultsBox) {
            addResultsBox(config.moreFoundMessage, alertsIssued + 1);
          }
          alertsIssued++;
        }
      }
    }
    
    // If we had duplicates, update the textbox content and edit summary
    if (duplicateTemplatesCount > 0) {
      // Update edit summary
      const editSummary = document.getElementsByName('wpSummary')[0];
      if (typeof editSummary === 'object') {
        if (editSummary.value.indexOf(config.summaryText) === -1) {
          if (editSummary.value.match(/[^\*\/\s][^\/\s]?\s*$/)) {
            editSummary.value += '; ' + config.summaryText;
          } else {
            editSummary.value += config.summaryText;
          }
        }
      }
      
      if (autoRemovedCount > 0 && userChoiceCount === 0) {
        // Update the textbox if we automatically removed duplicates and there are no user choices
        myContent.value = updatedContent;
        showMessage("Found " + duplicateTemplatesCount + " template(s) with duplicate parameter(s). Automatically removed " + autoRemovedCount + " identical duplicates.", true);
      } else if (userChoiceCount > 0) {
        showMessage("Found " + duplicateTemplatesCount + " template(s) with " + userChoiceCount + " parameter(s) that need user choices. Please select which values to keep.", true);
      } else if (alertsIssued === 0) {
        showMessage(config.noneFoundMessage, true);
      }
    } else {
      // No duplicates found
      showMessage(config.noneFoundMessage, true);
      if (config.showResultsBox) {
        clearResultsBox();
      }
    }
  }
});