This time, we’ll add a button to the #p-cactions
dropdown (how to do that was initially introduced in Gadget - Search results new page hotkey) that gives you a button to click to copy all of the members of a category, one per line.
(Note: All code in this post was originally written by me for Leaguepedia and is licensed under CC BY-SA 3.0.)
Motivation
Sometimes we want to copy all of the members of a category. This could be to:
- Ctrl+F through all of the titles in Notepad++
- Create a
pairsfile
for PWB’s movepages script - copy all the category members, then run a regex to generate the destination (something like$1\r\n$1 - new
as the replacement term with(.*)
as the search to move frompagename
topagename - new
) - Provide a list of pages to someone who may be less comfortable with built-in features of tooling, but still wants to run tooling operations on a category (see below)
Things we would (probably) not need this for:
- Working with AutoWikiBrowser - it has a category select as a generator option, and pretty robust filtering options of its own
- Working with PWB if it’s not to move pages -
-cat:
or-categoryname:
is a generator - Writing Python scripts - we’d just use
site.categories
frommwclient
to get the listing (site.client.categories
if working with mwcleric)
Can’t we just highlight and Ctrl+C?
There’s a couple issues with copying the page list as-is:
- You get the single-letter section titles included as well
- The category could span more than one page, so not all members are included (though this gadget doesn’t actually continue, so it’s limited at
max
members which for my purposes is 5000 since I haveapihighlimits
on every wiki I work with) - There’s leading whitespace at the beginning of each line
So it’s pretty annoying to sanitize the input we’d get if we just copied what’s already on the page. (Exercise to the reader - generate a single regex to sanitize the input we get! You’ll have to use at least one pipe.)
Code
|
|
It uses this function from Gadget - utils.js:
|
|
Explanation
First, immediately return if we’re not on a category page. Then add a portlet link into p-cactions
with our action; I’ve explained this before.
API query
Next, we do an API call: a simple query
(which is a get
request as opposed to a postWithToken
) of the list
of categorymembers
. We can check the categorymembers documentation to see what parameters are supported, though I almost always just use the API sandbox to check parameters.
We want to specify two parameters:
cmtitle
- this is the title of the category we’re querying, and in this case it’s the name of the current page we’re oncmlimit
- set it to max; by default, if we’re an admin, it’ll be 5000, and this should be plenty
Limitation
Note that this code does NOT support copying the entire category’s members for “large” categories, and if you don’t have the apihighlimits
right, you’ll be gated at only 500 results (by default). If we wanted to change this, we’d have to restructure the code slightly, adding another function called something like doQuery
that accepts a cmcontinue
parameter as well as the current value of our list of known pages. Our then
would then append the new data, and if we have a query-continue
as part of our response from the server (i.e. if we’re not done getting data), would re-call doQuery
with the next value of cmcontinue
set to the query-continue
value.
If we were to take this approach, we’d probably want to set our own limit (perhaps 5000) so that (a) we’re never inserting a list of say 50,000 elements to the DOM if a user calls !Copy members
unwittingly on a huge category page and also (b) we’re not making the user wait for dozens of sequential round trips to the server and back before the result is ready.
Display
After we’ve gotten our result, we’ll iterate over the result and push the title of each page into an array, concatenate the result, and then display the output text.
The displayOutputText
function makes a textarea, sets it to readonly, and then places it right after #contentSub
. It highlights and selects the text inside, but doesn’t execute any copy operation, in case you have sensitive information on your clipboard already.
Why delegate to a helper function instead of just inserting the element immediately? This code is pretty simple.
- Laziness/ease - there’s a few different gadgets that create copyable output like this, and I’d rather not copy-paste the same code multiple times
- Single point of change - if the DOM ever changes so that
#contentSub
is no longer the place I’m going to insert the text (hey, that’s happening!) or if I ever want to change other CSS properties about it, there’s a single place to do so
TL;DR: I actually do use it enough that DRY applies.
Conclusion
Like many of my tools, this gadget is a pretty quick and somewhat lazy but still very useful piece of code - effectively I do only 10% of a proper implementation (which would incorporate continuing) but get 90% of the benefit (continuing is almost never needed). Because I make it available only by opt-in, instead of enabling it by default, its roughness is less of a problem, and it’s always open to extension and completion if someone’s needs are ever not being met.
You should always feel free to write incomplete tools and use them! A hacked-together script that does 90% of what you could imagine it capable of doing can be among your most valuable assets! Just don’t mistake incomplete for untested or incorrect; your incomplete tools should still work.