// Wikipedia List Generator - Special:BlankPage/Listgen
// Universal Wikipedia List Copier adapted for dedicated blank page interface
$(document).ready(function() {
// 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('Wikipedia List Generator');
document.title = 'Wikipedia 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'
},
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 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>
<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 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'));
$('#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 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!');