User:Polygnotus/Scripts/CategoryToClipboard.js: Difference between revisions

Content deleted Content added
No edit summary
add "both" and "both recursive" buttons, re-order
 
(3 intermediate revisions by the same user not shown)
Line 1:
// Wikipedia Category Items Copier - ImprovedFixed VersionRedirect Filtering
 
const API_DELAY = 500; // Delay between API requests in milliseconds
Line 31:
copyItemsBtn.style.cursor = 'pointer';
addTooltip(copyItemsBtn, 'Copy all items in this category. Not recursive.');
// Create the "Copy All Items" button
const copyAllItemsBtn = document.createElement('button');
copyAllItemsBtn.textContent = 'Copy items recursively';
copyAllItemsBtn.style.marginRight = '10px';
copyAllItemsBtn.style.padding = '8px 12px';
copyAllItemsBtn.style.cursor = 'pointer';
addTooltip(copyAllItemsBtn, 'Copy all items in this category AND all items in its subcategories.');
// Create the "Copy Subcats from this Category" button
Line 39 ⟶ 47:
copyDirectSubcatsBtn.style.cursor = 'pointer';
addTooltip(copyDirectSubcatsBtn, 'Copy all subcategories of this category. Not recursive.');
// Create the "Copy All Items" button
const copyAllItemsBtn = document.createElement('button');
copyAllItemsBtn.textContent = 'Copy items recursively.';
copyAllItemsBtn.style.marginRight = '10px';
copyAllItemsBtn.style.padding = '8px 12px';
copyAllItemsBtn.style.cursor = 'pointer';
addTooltip(copyAllItemsBtn, 'Copy all items in this category AND all items in its subcategories.');
// Create the "Copy Subcategories" button
const copySubcatsBtn = document.createElement('button');
copySubcatsBtn.textContent = 'Copy subcategories recursively';
copySubcatsBtn.style.marginRight = '10px';
copySubcatsBtn.style.padding = '8px 12px';
copySubcatsBtn.style.cursor = 'pointer';
addTooltip(copySubcatsBtn, 'Copy all subcategories of this category and its subcategories.');
// Create the "Copy Both" button
const copyBothBtn = document.createElement('button');
copyBothBtn.textContent = 'Copy both';
copyBothBtn.style.marginRight = '10px';
copyBothBtn.style.padding = '8px 12px';
copyBothBtn.style.cursor = 'pointer';
addTooltip(copyBothBtn, 'Copy all items and subcategories from this category. Not recursive.');
// Create the "Copy Both Recursively" button
const copyBothRecursiveBtn = document.createElement('button');
copyBothRecursiveBtn.textContent = 'Copy both recursively';
copyBothRecursiveBtn.style.marginRight = '10px';
copyBothRecursiveBtn.style.padding = '8px 12px';
copyBothRecursiveBtn.style.cursor = 'pointer';
addTooltip(copyBothRecursiveBtn, 'Copy all items and subcategories from this category and all its subcategories.');
// Add checkbox for URL export
Line 67 ⟶ 84:
addTooltip(urlLabel, 'Include full Wikipedia URLs for each item in the export');
 
// Add checkbox for ignoring redirects
const ignoreRedirectsCheckbox = document.createElement('input');
ignoreRedirectsCheckbox.type = 'checkbox';
ignoreRedirectsCheckbox.id = 'ignoreRedirects';
ignoreRedirectsCheckbox.style.marginLeft = '15px';
const ignoreRedirectsLabel = document.createElement('label');
ignoreRedirectsLabel.htmlFor = 'ignoreRedirects';
ignoreRedirectsLabel.textContent = 'Ignore Redirects';
ignoreRedirectsLabel.style.marginLeft = '5px';
addTooltip(ignoreRedirectsLabel, 'Skip redirect pages and only include actual articles and content pages');
// Create status text
Line 84 ⟶ 91:
statusText.style.color = '#555';
// Add buttons to container in the requested order
container.appendChild(copyItemsBtn);
container.appendChild(copyDirectSubcatsBtn);
container.appendChild(copyAllItemsBtn);
container.appendChild(copyDirectSubcatsBtn);
container.appendChild(copySubcatsBtn);
container.appendChild(copyBothBtn);
container.appendChild(copyBothRecursiveBtn);
container.appendChild(urlCheckbox);
container.appendChild(urlLabel);
container.appendChild(ignoreRedirectsCheckbox);
container.appendChild(ignoreRedirectsLabel);
container.appendChild(statusText);
Line 275 ⟶ 282:
}
}
 
 
 
// Function to get all subcategories of a category
Line 310 ⟶ 319:
// Function to get all non-category members of a category
async function getNonCategoryMembers(categoryTitle, continueToken = null, ignoreRedirects = false) {
try {
// Base API URL for non-category members (exclude namespace 14, which is Category)
// Add maxlag parameter to be respectful of server load
let apiUrl = `https://en.wikipedia.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:${encodeURIComponent(categoryTitle)}&cmnamespace=0|1|2|3|4|5|6|7|8|9|10|11|12|13|15&cmlimit=max&maxlag=5&format=json&origin=*`;
// Add filter to exclude redirects if requested
if (ignoreRedirects) {
apiUrl += '&cmtype=page'; // Only get regular pages, not redirects
}
// Add continue token if provided
Line 335 ⟶ 339:
}
// Extract members and continue token
const members = data.query.categorymembers.map(member => member.title);
const nextContinueToken = data.continue ? data.continue.cmcontinue : null;
Line 348 ⟶ 352:
// Function to get all members of a category, handling pagination
async function getAllCategoryMembers(categoryTitle, ignoreRedirects = false) {
let allMembers = [];
let continueToken = null;
Line 354 ⟶ 358:
do {
const { members, continueToken: nextToken } = await getNonCategoryMembers(categoryTitle, continueToken, ignoreRedirects);
allMembers = allMembers.concat(members);
continueToken = nextToken;
Line 419 ⟶ 423:
// Function to recursively get all items from a category and all its subcategories
async function getAllItemsRecursive(categoryTitle, ignoreRedirects = false) {
const visited = new Set();
const allItems = [];
Line 441 ⟶ 445:
// Get items from current category
const currentItems = await getAllCategoryMembers(currentCategory, ignoreRedirects);
allItems.push(...currentItems);
Line 456 ⟶ 460:
return { items: allItems, totalCategories };
}
// Function to get both items and subcategories from a category (non-recursive)
async function getBothItemsAndSubcategories(categoryTitle) {
statusText.innerHTML = 'Gathering items and subcategories from this category...';
const items = await getAllCategoryMembers(categoryTitle);
const subcategories = await getAllSubcategories(categoryTitle);
return { items, subcategories };
}
// Function to recursively get both items and subcategories from a category and all its subcategories
async function getBothItemsAndSubcategoriesRecursive(categoryTitle) {
const visited = new Set();
const allItems = [];
const allSubcategories = [];
const queue = [categoryTitle]; // Start without prefix for consistency
let totalCategories = 0;
while (queue.length > 0) {
const currentCategory = queue.shift();
const categoryKey = `Category:${currentCategory}`;
// Skip if already visited (circular reference detection)
if (visited.has(categoryKey) || globalVisited.has(categoryKey)) {
continue;
}
visited.add(categoryKey);
globalVisited.add(categoryKey);
totalCategories++;
statusText.innerHTML = `Getting items and subcategories from "${currentCategory}" (processed ${totalCategories} categories, found ${allItems.length} items, ${allSubcategories.length} subcategories, queue: ${queue.length})...`;
// Get items from current category
const currentItems = await getAllCategoryMembers(currentCategory);
allItems.push(...currentItems);
// Get direct subcategories
const directSubcategories = await getAllSubcategories(currentCategory);
// Add subcategories to results and queue
for (const subcategory of directSubcategories) {
if (!visited.has(subcategory) && !globalVisited.has(subcategory)) {
allSubcategories.push(subcategory);
// Remove "Category:" prefix for queue consistency
const subcategoryName = subcategory.replace('Category:', '');
queue.push(subcategoryName);
}
}
}
return { items: allItems, subcategories: allSubcategories, totalCategories };
}
Line 463 ⟶ 521:
try {
const ignoreRedirectsitems = ignoreRedirectsCheckbox.checkedawait getAllCategoryMembers(categoryName);
const items = await getAllCategoryMembers(categoryName, ignoreRedirects);
if (items.length === 0) {
Line 476 ⟶ 533:
const copySuccess = await copyToClipboardOrDownload(formattedText, categoryName);
if (copySuccess) {
const redirectTextstatusText.innerHTML = ignoreRedirects ?`Successfully 'copied (excluding${items.length} redirects)'items :to ''clipboard.`;
}
statusText.innerHTML = `Successfully copied ${items.length} items to clipboard${redirectText}.`;
} catch (error) {
statusText.innerHTML = `Error: ${error.message}`;
console.error('Error:', error);
}
});
 
// Handle "Copy All Items" button click
copyAllItemsBtn.addEventListener('click', async () => {
statusText.innerHTML = 'Gathering items from this category and all subcategories recursively via API (this may take a while)...';
try {
// Clear global visited set for this operation
globalVisited.clear();
// Get all items recursively
const { items: allItems, totalCategories } = await getAllItemsRecursive(categoryName);
// Deduplicate items
const uniqueItems = [...new Set(allItems)];
if (uniqueItems.length === 0) {
statusText.innerHTML = 'No items found in this category or its subcategories.';
return;
}
const includeUrls = urlCheckbox.checked;
const formattedText = formatItems(uniqueItems, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, categoryName + '_all_recursive');
if (copySuccess) {
statusText.innerHTML = `Successfully copied ${uniqueItems.length} unique items to clipboard from ${totalCategories} categories.`;
}
} catch (error) {
Line 510 ⟶ 598:
});
 
// Handle "Copy All ItemsSubcategories" button click
copyAllItemsBtncopySubcatsBtn.addEventListener('click', async () => {
statusText.innerHTML = 'Gathering items from this category and all subcategories recursively via API (this may take a while)...';
try {
Line 518 ⟶ 606:
globalVisited.clear();
const ignoreRedirectsallSubcategories = ignoreRedirectsCheckbox.checkedawait getAllSubcategoriesRecursive(categoryName);
// Get all items recursively
const { items: allItems, totalCategories } = await getAllItemsRecursive(categoryName, ignoreRedirects);
// Deduplicate itemssubcategories
const uniqueItemsuniqueSubcategories = [...new Set(allItemsallSubcategories)];
if (uniqueItemsuniqueSubcategories.length === 0) {
statusText.innerHTML = 'No itemssubcategories found in this category or its subcategories.';
return;
}
const includeUrls = urlCheckbox.checked;
const formattedText = formatItems(uniqueItemsuniqueSubcategories, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, categoryName + '_all_recursive_subcategories');
if (copySuccess) {
statusText.innerHTML = `Successfully copied ${uniqueSubcategories.length} unique subcategories to clipboard.`;
const redirectText = ignoreRedirects ? ' (excluding redirects)' : '';
statusText.innerHTML = `Successfully copied ${uniqueItems.length} unique items to clipboard from ${totalCategories} categories${redirectText}.`;
}
} catch (error) {
Line 544 ⟶ 629:
});
 
// Handle "Copy SubcategoriesBoth" button click
copySubcatsBtncopyBothBtn.addEventListener('click', async () => {
statusText.innerHTML = 'Gathering allboth subcategoriesitems recursivelyand viasubcategories APIfrom (this maycategory takevia a while)API...';
try {
const { items, subcategories } = await getBothItemsAndSubcategories(categoryName);
if (items.length === 0 && subcategories.length === 0) {
statusText.innerHTML = 'No items or subcategories found in this category.';
return;
}
// Combine items and subcategories
const combinedResults = [...items, ...subcategories];
const includeUrls = urlCheckbox.checked;
const formattedText = formatItems(combinedResults, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, categoryName + '_both');
if (copySuccess) {
statusText.innerHTML = `Successfully copied ${items.length} items and ${subcategories.length} subcategories (${combinedResults.length} total) to clipboard.`;
}
} catch (error) {
statusText.innerHTML = `Error: ${error.message}`;
console.error('Error:', error);
}
});
 
// Handle "Copy Both Recursively" button click
copyBothRecursiveBtn.addEventListener('click', async () => {
statusText.innerHTML = 'Gathering both items and subcategories recursively via API (this may take a while)...';
try {
Line 552 ⟶ 665:
globalVisited.clear();
const { items: allItems, subcategories: allSubcategories, totalCategories } = await getAllSubcategoriesRecursivegetBothItemsAndSubcategoriesRecursive(categoryName);
// Deduplicate items and subcategories
const uniqueItems = [...new Set(allItems)];
const uniqueSubcategories = [...new Set(allSubcategories)];
if (uniqueItems.length === 0 && uniqueSubcategories.length === 0) {
statusText.innerHTML = 'No items or subcategories found in this category or its subcategories.';
return;
}
// Combine items and subcategories
const combinedResults = [...uniqueItems, ...uniqueSubcategories];
const includeUrls = urlCheckbox.checked;
const formattedText = formatItems(uniqueSubcategoriescombinedResults, includeUrls);
const copySuccess = await copyToClipboardOrDownload(formattedText, categoryName + '_subcategories_both_recursive');
if (copySuccess) {
statusText.innerHTML = `Successfully copied ${uniqueItems.length} unique items and ${uniqueSubcategories.length} unique subcategories (${combinedResults.length} total) to clipboard from ${totalCategories} categories.`;
}
} catch (error) {