User:Polygnotus/Scripts/ListGenerator2.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/ListGenerator2.
// <nowiki>
// Wikipedia List Generator - Special:BlankPage/Listgen
// Universal Wikipedia List Copier adapted for dedicated blank page interface
$(document).ready(function() {
// Add link to tools menu (runs on all pages)
mw.util.addPortletLink(
'p-tb', // portlet ID (tools menu)
'/wiki/Special:BlankPage/Listgen', // link URL
'List Generator', // link text
't-listgen', // link ID
'Generate lists from Wikipedia categories, search results, backlinks, and more' // tooltip text
);
// Only run on Special:BlankPage/Listgen
if (mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
mw.config.get('wgPageName') === 'Special:BlankPage/Listgen') {
// Set up the page
$('#firstHeading').text('List Generator');
document.title = 'List Generator';
setupListGeneratorInterface();
}
});
const CONFIG = {
API_DELAY: 500,
MAX_RETRIES: 3,
BASE_URL: 'https://en.wikipedia.org',
API_URL: 'https://en.wikipedia.org/w/api.php'
};
// Global state for pause/stop functionality
const OPERATION_STATE = {
isPaused: false,
shouldStop: false,
currentOperation: null
};
// ===== CORE UTILITIES =====
function addTooltip(element, text) {
element.title = text;
}
function formatItems(items, includeUrls, baseUrl = `${CONFIG.BASE_URL}/wiki/`) {
if (!includeUrls) return items.join('\n');
return items.map(item => `${baseUrl}${encodeURIComponent(item.replace(/ /g, '_'))}`).join('\n');
}
async function copyToClipboardOrDownload(text, filename, statusElement) {
const success = await tryClipboardCopy(text);
if (!success) {
statusElement.html(`<p>Clipboard access failed. Click the link below to download items:</p>`);
offerTextAsDownload(text, filename, statusElement);
}
return success;
}
function offerTextAsDownload(text, filename, statusElement) {
const blob = new Blob([text], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const downloadLink = $('<a>')
.attr('href', url)
.attr('download', filename || 'wikipedia-items.txt')
.text(`Download ${filename || 'items'} as text file`)
.css('display', 'block')
.css('margin-top', '10px');
statusElement.append(downloadLink);
}
async function tryClipboardCopy(text) {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {}
}
// Fallback method
try {
const textarea = document.createElement('textarea');
Object.assign(textarea.style, {
position: 'fixed',
left: '-999999px',
top: '-999999px'
});
textarea.value = text;
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
const success = document.execCommand('copy');
document.body.removeChild(textarea);
return success;
} catch {
return false;
}
}
// ===== API UTILITIES =====
async function makeApiRequest(url, retryCount = 0) {
// Check for stop signal
if (OPERATION_STATE.shouldStop) {
throw new Error('Operation stopped by user');
}
// Handle pause
while (OPERATION_STATE.isPaused && !OPERATION_STATE.shouldStop) {
await new Promise(resolve => setTimeout(resolve, 100));
}
// Use dynamic delay from UI
const delay = parseInt($('#delay-input').val()) || CONFIG.API_DELAY;
await new Promise(resolve => setTimeout(resolve, delay));
try {
const response = await fetch(url);
if (response.status === 429 || response.status >= 500) {
if (retryCount < CONFIG.MAX_RETRIES) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
return makeApiRequest(url, retryCount + 1);
}
throw new Error(`Request failed after ${CONFIG.MAX_RETRIES} retries: ${response.status}`);
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.error?.code === 'maxlag') {
const waitTime = (data.error.lag || 5 + 2) * 1000;
await new Promise(resolve => setTimeout(resolve, waitTime));
return makeApiRequest(url, retryCount);
}
if (data.error) {
throw new Error(`API Error: ${data.error.code} - ${data.error.info}`);
}
return data;
} catch (error) {
if (retryCount < CONFIG.MAX_RETRIES && !OPERATION_STATE.shouldStop) {
await new Promise(resolve => setTimeout(resolve, 1000));
return makeApiRequest(url, retryCount + 1);
}
throw error;
}
}
// Generic paginated API fetcher
async function fetchAllPages(apiConfig, statusCallback) {
let allItems = [];
let continueToken = null;
let pagesProcessed = 0;
do {
// Check for stop signal
if (OPERATION_STATE.shouldStop) {
throw new Error('Operation stopped by user');
}
const url = apiConfig.buildUrl(continueToken);
statusCallback(`${apiConfig.progressMessage} (page ${pagesProcessed + 1})...`);
const data = await makeApiRequest(url);
const { items, continueToken: nextToken } = apiConfig.parseResponse(data);
allItems = allItems.concat(items);
continueToken = nextToken;
pagesProcessed++;
statusCallback(`Retrieved ${allItems.length} ${apiConfig.itemType} (page ${pagesProcessed})...`);
} while (continueToken && !OPERATION_STATE.shouldStop);
return allItems;
}
// ===== CONSOLIDATED FETCH METHODS =====
async function fetchPaginatedList(listType, params, statusCallback = () => {}) {
const configs = {
categoryMembers: {
list: 'categorymembers',
titleParam: 'cmtitle',
continueParam: 'cmcontinue',
limitParam: 'cmlimit',
namespaceParam: 'cmnamespace',
dataPath: 'categorymembers',
defaultNamespaces: '0|1|2|3|4|5|6|7|8|9|10|11|12|13|15'
},
categorySubcategories: {
list: 'categorymembers',
titleParam: 'cmtitle',
continueParam: 'cmcontinue',
limitParam: 'cmlimit',
namespaceParam: 'cmnamespace',
dataPath: 'categorymembers',
defaultNamespaces: '14'
},
backlinks: {
list: 'backlinks',
titleParam: 'bltitle',
continueParam: 'blcontinue',
limitParam: 'bllimit',
namespaceParam: 'blnamespace',
dataPath: 'backlinks'
},
transclusions: {
list: 'embeddedin',
titleParam: 'eititle',
continueParam: 'eicontinue',
limitParam: 'eilimit',
namespaceParam: 'einamespace',
dataPath: 'embeddedin'
},
prefixPages: {
list: 'allpages',
titleParam: 'apprefix',
continueParam: 'apcontinue',
limitParam: 'aplimit',
namespaceParam: 'apnamespace',
dataPath: 'allpages'
},
search: {
list: 'search',
titleParam: 'srsearch',
continueParam: 'sroffset',
limitParam: 'srlimit',
dataPath: 'search'
}
};
const config = configs[listType];
if (!config) {
throw new Error(`Unknown list type: ${listType}`);
}
return fetchAllPages({
buildUrl: (continueToken) => {
let url = `${CONFIG.API_URL}?action=query&list=${config.list}&${config.limitParam}=max&maxlag=5&format=json&origin=*`;
// Add title/search parameter
if (config.titleParam && params.title) {
if (listType === 'categoryMembers' || listType === 'categorySubcategories') {
url += `&${config.titleParam}=Category:${encodeURIComponent(params.title)}`;
} else {
url += `&${config.titleParam}=${encodeURIComponent(params.title)}`;
}
}
// Add namespace parameter
if (config.namespaceParam) {
const namespace = params.namespace !== undefined ? params.namespace : config.defaultNamespaces;
if (namespace !== null) {
url += `&${config.namespaceParam}=${namespace}`;
}
}
// Add continuation token
if (continueToken) {
url += `&${config.continueParam}=${continueToken}`;
}
return url;
},
parseResponse: (data) => ({
items: data.query?.[config.dataPath]?.map(item => item.title) || [],
continueToken: data.continue?.[config.continueParam] || null
}),
progressMessage: params.progressMessage || `Fetching ${listType}`,
itemType: params.itemType || 'items'
}, statusCallback);
}
// Individual fetch methods
async function fetchCategoryMembers(categoryTitle, statusCallback) {
return fetchPaginatedList('categoryMembers', {
title: categoryTitle,
progressMessage: `Fetching items for: ${categoryTitle}`,
itemType: 'items'
}, statusCallback);
}
async function fetchCategorySubcategories(categoryTitle, statusCallback) {
return fetchPaginatedList('categorySubcategories', {
title: categoryTitle,
progressMessage: `Fetching subcategories for: ${categoryTitle}`,
itemType: 'subcategories'
}, statusCallback);
}
async function fetchBacklinks(targetTitle, namespaces, statusCallback) {
return fetchPaginatedList('backlinks', {
title: targetTitle,
namespace: namespaces,
progressMessage: `Fetching backlinks for: ${targetTitle}`,
itemType: 'backlinks'
}, statusCallback);
}
async function fetchTransclusions(targetTitle, namespaces, statusCallback) {
return fetchPaginatedList('transclusions', {
title: targetTitle,
namespace: namespaces,
progressMessage: `Fetching transclusions for: ${targetTitle}`,
itemType: 'transclusions'
}, statusCallback);
}
async function fetchPrefixPages(prefix, namespace, statusCallback) {
return fetchPaginatedList('prefixPages', {
title: prefix,
namespace: namespace,
progressMessage: `Fetching pages with prefix "${prefix}" in namespace ${namespace}`,
itemType: 'pages'
}, statusCallback);
}
async function fetchSearchResults(query, statusCallback) {
return fetchPaginatedList('search', {
title: query,
progressMessage: `Searching for: "${query}"`,
itemType: 'search results'
}, statusCallback);
}
// Recursive methods
async function fetchCategoryMembersRecursive(categoryTitle, statusCallback) {
const visited = new Set();
const allItems = [];
const queue = [categoryTitle];
let totalCategories = 0;
while (queue.length > 0) {
if (OPERATION_STATE.shouldStop) {
statusCallback('Operation stopped by user.');
break;
}
const currentCategory = queue.shift();
const categoryKey = `Category:${currentCategory}`;
if (visited.has(categoryKey)) continue;
visited.add(categoryKey);
totalCategories++;
statusCallback(`Getting items from "${currentCategory}" (processed ${totalCategories} categories, found ${allItems.length} items, queue: ${queue.length})...`);
if (OPERATION_STATE.shouldStop) break;
const currentItems = await fetchCategoryMembers(currentCategory, statusCallback);
if (OPERATION_STATE.shouldStop) break;
allItems.push(...currentItems);
if (OPERATION_STATE.shouldStop) break;
const subcategories = await fetchCategorySubcategories(currentCategory, statusCallback);
if (OPERATION_STATE.shouldStop) break;
for (const subcategory of subcategories) {
if (OPERATION_STATE.shouldStop) break;
if (!visited.has(subcategory)) {
queue.push(subcategory.replace('Category:', ''));
}
}
}
return [...new Set(allItems)];
}
async function fetchCategorySubcategoriesRecursive(categoryTitle, statusCallback) {
const visited = new Set();
const allSubcategories = [];
const queue = [`Category:${categoryTitle}`];
while (queue.length > 0) {
if (OPERATION_STATE.shouldStop) {
statusCallback('Operation stopped by user.');
break;
}
const currentCategory = queue.shift();
if (visited.has(currentCategory)) continue;
visited.add(currentCategory);
statusCallback(`Exploring subcategories (found ${allSubcategories.length} categories, queue: ${queue.length})...`);
if (OPERATION_STATE.shouldStop) break;
const categoryNameForApi = currentCategory.replace('Category:', '');
const directSubcategories = await fetchCategorySubcategories(categoryNameForApi, statusCallback);
if (OPERATION_STATE.shouldStop) break;
for (const subcategory of directSubcategories) {
if (OPERATION_STATE.shouldStop) break;
if (!visited.has(subcategory)) {
allSubcategories.push(subcategory);
queue.push(subcategory);
}
}
}
return [...new Set(allSubcategories)];
}
async function fetchCategoryBothRecursive(categoryTitle, statusCallback) {
const visited = new Set();
const allItems = [];
const allSubcategories = [];
const queue = [categoryTitle];
let totalCategories = 0;
while (queue.length > 0) {
if (OPERATION_STATE.shouldStop) {
statusCallback('Operation stopped by user.');
break;
}
const currentCategory = queue.shift();
const categoryKey = `Category:${currentCategory}`;
if (visited.has(categoryKey)) continue;
visited.add(categoryKey);
totalCategories++;
statusCallback(`Getting items and subcategories from "${currentCategory}" (processed ${totalCategories} categories, found ${allItems.length} items, ${allSubcategories.length} subcategories, queue: ${queue.length})...`);
if (OPERATION_STATE.shouldStop) break;
const [currentItems, directSubcategories] = await Promise.all([
fetchCategoryMembers(currentCategory, statusCallback),
fetchCategorySubcategories(currentCategory, statusCallback)
]);
if (OPERATION_STATE.shouldStop) break;
allItems.push(...currentItems);
for (const subcategory of directSubcategories) {
if (OPERATION_STATE.shouldStop) break;
if (!visited.has(subcategory)) {
allSubcategories.push(subcategory);
queue.push(subcategory.replace('Category:', ''));
}
}
}
return [...new Set([...allItems, ...allSubcategories])];
}
// ===== UTILITY FUNCTIONS =====
const updateStatus = function(message) {
$('#status-text').html(message);
};
// ===== UI SETUP =====
const setupListGeneratorInterface = function() {
const content = $('#mw-content-text');
content.html(`
<div id="listgen-container" style="max-width: 1200px; margin: 0 auto; padding: 20px;">
<div style="margin-bottom: 30px;">
<h2>Wikipedia List Generator</h2>
<p>Generate lists from Wikipedia categories, search results, backlinks, and more.</p>
</div>
<div id="listgen-tabs" style="margin-bottom: 0px;">
<button class="listgen-tab active" data-tab="category">Categories</button>
<button class="listgen-tab" data-tab="backlinks">Whatlinkshere</button>
<button class="listgen-tab" data-tab="prefix">Prefix Search</button>
<button class="listgen-tab" data-tab="search">Search Results</button>
</div>
<div id="category-tab" class="listgen-tab-content active">
<div class="listgen-section">
<h3>Category Tools</h3>
<div class="input-group">
<label for="category-input">Category name (without "Category:" prefix):</label>
<input type="text" id="category-input" placeholder="e.g., American novelists" style="width: 100%; padding: 8px; box-sizing: border-box;">
</div>
<div class="button-group">
<button id="cat-members">Get Category Members</button>
<button id="cat-members-recursive">Get Members (Recursive)</button>
<button id="cat-subcats">Get Subcategories</button>
<button id="cat-subcats-recursive">Get Subcategories (Recursive)</button>
<button id="cat-both">Get Both</button>
<button id="cat-both-recursive">Get Both (Recursive)</button>
</div>
</div>
</div>
<div id="backlinks-tab" class="listgen-tab-content">
<div class="listgen-section">
<h3>Backlinks Tools</h3>
<div class="input-group">
<label for="backlinks-input">Page title:</label>
<input type="text" id="backlinks-input" placeholder="e.g., United States" style="width: 100%; padding: 8px; box-sizing: border-box;">
</div>
<div class="button-group">
<button id="backlinks-all">Get All Backlinks</button>
<button id="backlinks-mainspace">Get Mainspace Backlinks</button>
<button id="backlinks-non-mainspace">Get Non-Mainspace Backlinks</button>
</div>
</div>
<div class="listgen-section">
<h3>Transclusions Tools</h3>
<div class="input-group">
<label for="transclusions-input">Page title (typically a template):</label>
<input type="text" id="transclusions-input" placeholder="e.g., Infobox person" style="width: 100%; padding: 8px; box-sizing: border-box;">
</div>
<div class="button-group">
<button id="transclusions-all">Get All Transclusions</button>
<button id="transclusions-mainspace">Get Mainspace Transclusions</button>
<button id="transclusions-non-mainspace">Get Non-Mainspace Transclusions</button>
</div>
</div>
</div>
<div id="prefix-tab" class="listgen-tab-content">
<div class="listgen-section">
<h3>Prefix Search Tools</h3>
<div class="input-group">
<label for="prefix-input">Page title with namespace prefix (if any):</label>
<input type="text" id="prefix-input" placeholder="e.g., List of, User:Jimbo, Template:Infobox" style="width: 100%; padding: 8px; box-sizing: border-box;">
<div style="font-size: 12px; color: #666; margin-top: 5px;">
Examples: "List of" (mainspace), "User:Jimbo" (user namespace), "Template:Infobox" (template namespace)
</div>
</div>
<div class="button-group">
<button id="prefix-search">Get Pages with Prefix</button>
</div>
</div>
</div>
<div id="search-tab" class="listgen-tab-content">
<div class="listgen-section">
<h3>Search Results Tools</h3>
<div class="input-group">
<label for="search-input">Search query:</label>
<input type="text" id="search-input" placeholder="e.g., American authors" style="width: 100%; padding: 8px; box-sizing: border-box;">
</div>
<div class="button-group">
<button id="search-results">Get Search Results</button>
</div>
</div>
</div>
<div class="options-section" style="margin: 20px 0; padding: 15px; background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 3px;">
<div style="margin-bottom: 15px;">
<label>
<input type="checkbox" id="include-urls"> Include URLs
</label>
<span style="margin-left: 10px; color: #666; font-size: 12px;">Check to include full Wikipedia URLs for each item</span>
</div>
<div style="margin-bottom: 15px;">
<label for="delay-input" style="display: inline-block; width: 200px;">API Request Delay (ms):</label>
<input type="number" id="delay-input" value="500" min="100" max="5000" step="100" style="width: 100px; padding: 3px;">
<span style="margin-left: 10px; color: #666; font-size: 12px;">Time to wait between API requests (100-5000ms)</span>
</div>
<div class="control-buttons">
<button id="pause-btn" style="background: #ff9500; color: white; border: none; padding: 8px 15px; border-radius: 3px; margin-right: 10px;" disabled>⏸️ Pause</button>
<button id="stop-btn" style="background: #d33; color: white; border: none; padding: 8px 15px; border-radius: 3px;" disabled>⏹️ Stop</button>
</div>
</div>
<div id="status-section" style="margin: 20px 0; padding: 15px; background: #f0f0f0; border-radius: 3px; min-height: 50px;">
<div id="status-text">Ready to generate lists...</div>
</div>
</div>
`);
// Add CSS styles
$('<style>').text(`
.listgen-tab {
background: #f8f9fa;
border: 1px solid #a2a9b1;
border-bottom: none;
padding: 10px 15px;
cursor: pointer;
margin-right: 2px;
}
.listgen-tab.active {
background: white;
font-weight: bold;
}
.listgen-tab-content {
display: none;
background: white;
border: 1px solid #a2a9b1;
padding: 20px;
border-radius: 0 0 3px 3px;
}
.listgen-tab-content.active {
display: block;
}
.listgen-section {
margin-bottom: 20px;
}
.listgen-section h3 {
margin-top: 0;
margin-bottom: 15px;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.button-group button {
margin-right: 10px;
margin-bottom: 10px;
padding: 8px 12px;
cursor: pointer;
background: #0645ad;
color: white;
border: none;
border-radius: 3px;
}
.button-group button:hover {
background: #0b57d0;
}
.button-group button:disabled {
background: #ccc;
cursor: not-allowed;
}
`).appendTo('head');
setupEventHandlers();
};
const setupEventHandlers = function() {
// Tab switching
$('.listgen-tab').on('click', function() {
const tabName = $(this).data('tab');
$('.listgen-tab').removeClass('active');
$('.listgen-tab-content').removeClass('active');
$(this).addClass('active');
$(`#${tabName}-tab`).addClass('active');
});
// Button handlers
$('#cat-members').on('click', () => handleCategoryAction('members'));
$('#cat-members-recursive').on('click', () => handleCategoryAction('members-recursive'));
$('#cat-subcats').on('click', () => handleCategoryAction('subcats'));
$('#cat-subcats-recursive').on('click', () => handleCategoryAction('subcats-recursive'));
$('#cat-both').on('click', () => handleCategoryAction('both'));
$('#cat-both-recursive').on('click', () => handleCategoryAction('both-recursive'));
$('#backlinks-all').on('click', () => handleBacklinksAction('all'));
$('#backlinks-mainspace').on('click', () => handleBacklinksAction('mainspace'));
$('#backlinks-non-mainspace').on('click', () => handleBacklinksAction('non-mainspace'));
$('#transclusions-all').on('click', () => handleTransclusionsAction('all'));
$('#transclusions-mainspace').on('click', () => handleTransclusionsAction('mainspace'));
$('#transclusions-non-mainspace').on('click', () => handleTransclusionsAction('non-mainspace'));
$('#prefix-search').on('click', handlePrefixAction);
$('#search-results').on('click', handleSearchAction);
// Control button handlers
$('#pause-btn').on('click', handlePauseResume);
$('#stop-btn').on('click', handleStop);
};
// ===== CONTROL FUNCTIONS =====
const setOperationState = function(isRunning) {
if (isRunning) {
OPERATION_STATE.shouldStop = false;
OPERATION_STATE.isPaused = false;
$('#pause-btn').prop('disabled', false).text('⏸️ Pause');
$('#stop-btn').prop('disabled', false);
$('.button-group button').prop('disabled', true);
} else {
OPERATION_STATE.shouldStop = false;
OPERATION_STATE.isPaused = false;
OPERATION_STATE.currentOperation = null;
$('#pause-btn').prop('disabled', true).text('⏸️ Pause');
$('#stop-btn').prop('disabled', true);
$('.button-group button').prop('disabled', false);
}
};
const handlePauseResume = function() {
if (OPERATION_STATE.isPaused) {
OPERATION_STATE.isPaused = false;
$('#pause-btn').text('⏸️ Pause');
updateStatus('Operation resumed...');
} else {
OPERATION_STATE.isPaused = true;
$('#pause-btn').text('▶️ Resume');
updateStatus('Operation paused. Click Resume to continue.');
}
};
const handleStop = function() {
OPERATION_STATE.shouldStop = true;
OPERATION_STATE.isPaused = false;
updateStatus('Stop requested - operation will halt after current request...');
};
// ===== ACTION HANDLERS =====
const handleCategoryAction = async function(action) {
const categoryName = $('#category-input').val().trim();
if (!categoryName) {
updateStatus('Please enter a category name.');
return;
}
setOperationState(true);
OPERATION_STATE.currentOperation = `category-${action}`;
const includeUrls = $('#include-urls').is(':checked');
const statusCallback = (msg) => updateStatus(msg);
try {
let items = [];
let filename = '';
switch (action) {
case 'members':
items = await fetchCategoryMembers(categoryName, statusCallback);
filename = `${categoryName}_members`;
break;
case 'members-recursive':
items = await fetchCategoryMembersRecursive(categoryName, statusCallback);
filename = `${categoryName}_members_recursive`;
break;
case 'subcats':
items = await fetchCategorySubcategories(categoryName, statusCallback);
filename = `${categoryName}_subcategories`;
break;
case 'subcats-recursive':
items = await fetchCategorySubcategoriesRecursive(categoryName, statusCallback);
filename = `${categoryName}_subcategories_recursive`;
break;
case 'both':
const [members, subcats] = await Promise.all([
fetchCategoryMembers(categoryName, statusCallback),
fetchCategorySubcategories(categoryName, statusCallback)
]);
items = [...members, ...subcats];
filename = `${categoryName}_both`;
break;
case 'both-recursive':
items = await fetchCategoryBothRecursive(categoryName, statusCallback);
filename = `${categoryName}_both_recursive`;
break;
}
if (OPERATION_STATE.shouldStop) {
updateStatus('Operation stopped by user.');
return;
}
if (items.length === 0) {
updateStatus('No items found.');
return;
}
const formattedText = formatItems(items, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, filename, $('#status-section'));
if (copySuccess) {
updateStatus(`Successfully copied ${items.length} items to clipboard.`);
}
} catch (error) {
if (error.message === 'Operation stopped by user') {
updateStatus('Operation stopped by user.');
} else {
updateStatus(`Error: ${error.message}`);
}
} finally {
setOperationState(false);
}
};
const handleBacklinksAction = async function(type) {
const targetTitle = $('#backlinks-input').val().trim();
if (!targetTitle) {
updateStatus('Please enter a page title.');
return;
}
setOperationState(true);
OPERATION_STATE.currentOperation = `backlinks-${type}`;
const includeUrls = $('#include-urls').is(':checked');
const statusCallback = (msg) => updateStatus(msg);
try {
let items = [];
let filename = '';
switch (type) {
case 'all':
items = await fetchBacklinks(targetTitle, null, statusCallback);
filename = 'all_backlinks';
break;
case 'mainspace':
items = await fetchBacklinks(targetTitle, '0', statusCallback);
filename = 'mainspace_backlinks';
break;
case 'non-mainspace':
const allBacklinks = await fetchBacklinks(targetTitle, null, statusCallback);
if (OPERATION_STATE.shouldStop) return;
updateStatus('Filtering out mainspace backlinks...');
const mainspaceBacklinks = await fetchBacklinks(targetTitle, '0', statusCallback);
const mainspaceSet = new Set(mainspaceBacklinks);
items = allBacklinks.filter(link => !mainspaceSet.has(link));
filename = 'non_mainspace_backlinks';
break;
}
if (OPERATION_STATE.shouldStop) {
updateStatus('Operation stopped by user.');
return;
}
if (items.length === 0) {
updateStatus('No backlinks found.');
return;
}
const formattedText = formatItems(items, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, filename, $('#status-section'));
if (copySuccess) {
updateStatus(`Successfully copied ${items.length} backlinks to clipboard.`);
}
} catch (error) {
if (error.message === 'Operation stopped by user') {
updateStatus('Operation stopped by user.');
} else {
updateStatus(`Error: ${error.message}`);
}
} finally {
setOperationState(false);
}
};
const handleTransclusionsAction = async function(type) {
const targetTitle = $('#transclusions-input').val().trim();
if (!targetTitle) {
updateStatus('Please enter a page title.');
return;
}
setOperationState(true);
OPERATION_STATE.currentOperation = `transclusions-${type}`;
const includeUrls = $('#include-urls').is(':checked');
const statusCallback = (msg) => updateStatus(msg);
try {
let items = [];
let filename = '';
switch (type) {
case 'all':
items = await fetchTransclusions(targetTitle, null, statusCallback);
filename = 'all_transclusions';
break;
case 'mainspace':
items = await fetchTransclusions(targetTitle, '0', statusCallback);
filename = 'mainspace_transclusions';
break;
case 'non-mainspace':
const allTransclusions = await fetchTransclusions(targetTitle, null, statusCallback);
if (OPERATION_STATE.shouldStop) return;
updateStatus('Filtering out mainspace transclusions...');
const mainspaceTransclusions = await fetchTransclusions(targetTitle, '0', statusCallback);
const mainspaceSet = new Set(mainspaceTransclusions);
items = allTransclusions.filter(link => !mainspaceSet.has(link));
filename = 'non_mainspace_transclusions';
break;
}
if (OPERATION_STATE.shouldStop) {
updateStatus('Operation stopped by user.');
return;
}
if (items.length === 0) {
updateStatus('No transclusions found.');
return;
}
const formattedText = formatItems(items, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, filename, $('#status-section'));
if (copySuccess) {
updateStatus(`Successfully copied ${items.length} transclusions to clipboard.`);
}
} catch (error) {
if (error.message === 'Operation stopped by user') {
updateStatus('Operation stopped by user.');
} else {
updateStatus(`Error: ${error.message}`);
}
} finally {
setOperationState(false);
}
};
const handlePrefixAction = async function() {
const fullInput = $('#prefix-input').val().trim();
if (!fullInput) {
updateStatus('Please enter a prefix or page title.');
return;
}
setOperationState(true);
OPERATION_STATE.currentOperation = 'prefix-search';
// Parse namespace and prefix from input
let namespace = '0'; // Default to mainspace
let prefix = fullInput;
// Check if input contains namespace prefix
if (fullInput.includes(':')) {
const [namespaceName, actualPrefix] = fullInput.split(':', 2);
const namespaceMap = {
'User': '2',
'Wikipedia': '4',
'File': '6',
'Image': '6', // Alias for File
'MediaWiki': '8',
'Template': '10',
'Help': '12',
'Category': '14',
'Portal': '100',
'Draft': '118',
'Talk': '1',
'User talk': '3',
'Wikipedia talk': '5',
'File talk': '7',
'MediaWiki talk': '9',
'Template talk': '11',
'Help talk': '13',
'Category talk': '15'
};
if (namespaceMap[namespaceName]) {
namespace = namespaceMap[namespaceName];
prefix = actualPrefix || ''; // Handle cases like "User:" with no prefix after
}
// If not a recognized namespace, treat the whole input as prefix in mainspace
}
const includeUrls = $('#include-urls').is(':checked');
const statusCallback = (msg) => updateStatus(msg);
try {
const items = await fetchPrefixPages(prefix, namespace, statusCallback);
if (OPERATION_STATE.shouldStop) {
updateStatus('Operation stopped by user.');
return;
}
if (items.length === 0) {
updateStatus(`No pages found with prefix "${fullInput}" in namespace ${namespace}.`);
return;
}
const filename = `prefix_${fullInput.replace(/[^a-zA-Z0-9]/g, '_')}`;
const formattedText = formatItems(items, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, filename, $('#status-section'));
if (copySuccess) {
updateStatus(`Successfully copied ${items.length} pages to clipboard.`);
}
} catch (error) {
if (error.message === 'Operation stopped by user') {
updateStatus('Operation stopped by user.');
} else {
updateStatus(`Error: ${error.message}`);
}
} finally {
setOperationState(false);
}
};
const handleSearchAction = async function() {
const query = $('#search-input').val().trim();
if (!query) {
updateStatus('Please enter a search query.');
return;
}
setOperationState(true);
OPERATION_STATE.currentOperation = 'search';
const includeUrls = $('#include-urls').is(':checked');
const statusCallback = (msg) => updateStatus(msg);
try {
const items = await fetchSearchResults(query, statusCallback);
if (OPERATION_STATE.shouldStop) {
updateStatus('Operation stopped by user.');
return;
}
if (items.length === 0) {
updateStatus(`No search results found for "${query}".`);
return;
}
const filename = `search_${query.replace(/[^a-zA-Z0-9]/g, '_')}`;
const formattedText = formatItems(items, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, filename, $('#status-section'));
if (copySuccess) {
updateStatus(`Successfully copied ${items.length} search results to clipboard.`);
}
} catch (error) {
if (error.message === 'Operation stopped by user') {
updateStatus('Operation stopped by user.');
} else {
updateStatus(`Error: ${error.message}`);
}
} finally {
setOperationState(false);
}
};
//console.log('Wikipedia List Generator loaded successfully!');
// </nowiki>