This page looks best with JavaScript enabled

Gadget - Copy search results

 ·  ☕ 6 min read

A screenshot of the !Copy titles button

This week’s gadget is pretty similar in functionality to copyCategoryMembers - in fact, on Leaguepedia it’s part of the same physical JavaScript file. However, I decided to split up the posts because I’m going to discuss different aspects of them.

(Note: I wrote this post before the switch to the FandomDesktop skin, and this gadget currently isn’t working on FandomDesktop because p-cactions isn’t accessible on Special pages; so if you attempt to use this on Leaguepedia, it will not work. There’s a ticket open with Fandom to fix the issue though!)

(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 results of a search into a file, one per line, and then use these as the inputs somewhere. There’s a lot of reasons for wanting to do this:

  • Populate an AWB (AutoWikiBrowser) result with all the instances of a template having a particular parameter value after doing an insource search
  • Operate on the results of an intitle search in PWB, AWB, or a Python script - this could be moving pages, editing pages, deleting pages, etc
  • Perform an automated refactor in Lua such as renaming a specific function - operating on the results of a search would let us manually prune certain pages (such as those that contain the legacy function definitions, which we want to edit last)

Of course, you need insource search to actually exist for most of this to be useful, and as a result I haven’t personally used this gadget since Fandom migrated to UCP last October. Nonetheless this is still an interesting gadget.

Code (version 1)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$(function() {
	if (mw.config.get('wgTitle') != 'Search') return;
	$(mw.util.addPortletLink('p-cactions', 'javascript:;', '!Copy Results', 'ca-copy-search-results', 'Copy Search Results', null, '#ca-move-to-user')).click(function() {
		var pageList = [];
		$('.mw-search-result-heading a:first-of-type').each(function() {
			pageList.push($(this).attr('title'));
		});
		var str = pageList.join('\n');
		displayOutputText(str, true);
	});
});

This version should work on most MediaWiki installations. However, Fandom’s search changed the DOM for search results, and it doesn’t function. Instead we use this, the differences are in lines 5 and 6:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$(function() {
	if (mw.config.get('wgTitle') != 'Search') return;
	$(mw.util.addPortletLink('p-cactions', 'javascript:;', '!Copy Results', 'ca-copy-search-results', 'Copy Search Results', null, '#ca-move-to-user')).click(function() {
		var pageList = [];
		$('.unified-search__result__title').each(function() {
			pageList.push($(this).attr('data-title'));
		});
		var str = pageList.join('\n');
		displayOutputText(str, true);
	});
});

Explanation (version 1)

Most of this gadget should look pretty familiar to you if you read the copyCategoryMembers post. So I’m going to talk about how to figure out the selector to use.

A screenshot demonstrating where to hover a link to find it in the inspector

The first step is to open the inspector, which you can do with Ctrl+Shift+C. Then, you want to make sure that the button labeled in the screenshot with a 1 is engaged (blue). That will let you hover elements and jump/highlight their HTML in the main part of the window below (to the right by default if you’re using Chrome, which you shouldn’t; Firefox has much better DevTools). Highlight the link you care about (indicated by a 2). Then you can look at its HTML (pink box).

Here’s the HTML for the h1 and its child a in Fandom’s search (this is NOT vanilla MediaWiki!):

1
2
3
4
<h1 class="unified-search__result__header">
						<a href="https://lol.fandom.com/wiki/Fnatic/Tournament_Results" class="unified-search__result__title" data-wiki-id="2293615" data-page-id="246661" data-title="Fnatic/Tournament Results" data-thumbnail="https://static.wikia.nocookie.net/lolesports_gamepedia_en/images/0/0e/LoL_Championship_Serieslogo_std.png/revision/latest/thumbnail-down/width/200/height/200?cb=20210330143745" data-position="1">
				Fnatic/Tournament Results			</a>
		</h1>

Once we have that HTML we can hopefully find a selector that doesn’t suck too much. I talked a bit about this in Gadget - Improving Special:ListGroupRights CSS - sometimes you’re stuck with a :first-of-type selector, but sometimes there’s a class name you can use. In the Fandom search there’s actually a unique class directly on the link, so we got a bit lucky there. However, they removed the title attribute and changed it to data-title, so we had to make that change from the vanilla MediaWiki version.

Code (version 2)

It turns out there are a couple other places we might want to copy titles from:

  • User contributions
  • Recent changes

The code is nearly identical; all we have to do is use a different selector and maybe a different attribute.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* copy titles from search, recentchanges, contribs */
$(function() {
	var selectors = [
		{ pattern: 'Special:Search', selector: '.unified-search__result__title', attr: 'data-title' },
		{ pattern: 'Special:Contributions/', selector: '.mw-contributions-title', attr: 'title' },
		{ pattern: 'Special:RecentChanges', selector: '.mw-changeslist-line-inner .mw-title a', attr: 'title' },
	];
	var pageName = mw.config.get('wgPageName');
	for (i in selectors) {
		if (!pageName.includes(selectors[i].pattern)) continue;
		$(mw.util.addPortletLink('p-cactions', 'javascript:;', '!Copy Titles', 'ca-copy-search-results', 'Copy Titles', null, '#ca-move-to-user')).click(function() {
			var pageList = [];
			$(selectors[i].selector).each(function() {
				pageList.push($(this).attr(selectors[i].attr));
			});
			var str = pageList.join('\n');
			displayOutputText(str, true);
		});
		// don't let i keep incrementing
		return;
	}
});

Explanation (version 2)

I’ll break this up into two parts.

Data structure

Here’s where I hold the information about each type of page, what I’m going to use as my selector, and what attribute is holding the page title. If we were on vanilla MediaWiki, the attr variable wouldn’t be required; everything would explicitly use title.

1
2
3
4
5
	var selectors = [
		{ pattern: 'Special:Search', selector: '.unified-search__result__title', attr: 'data-title' },
		{ pattern: 'Special:Contributions/', selector: '.mw-contributions-title', attr: 'title' },
		{ pattern: 'Special:RecentChanges', selector: '.mw-changeslist-line-inner .mw-title a', attr: 'title' },
	];

I could’ve done this two ways.

  • Dict - This would’ve looked like "Special:Search": { stuff goes here } etc.
  • List - This is what I did.

I decided to go with a list just because I don’t love using dict keys with special characters, feels kinda weird. It really doesn’t matter that much. Because MediaWiki doesn’t support the of keyword, the code is identical between the two cases.

Logic

1
2
3
4
5
	for (i in selectors) {
		if (!pageName.includes(selectors[i].pattern)) continue;
		// otherwise do stuff here!
		return;
	}

I iterate through my selectors. If I don’t match this particular one, I continue; and if I have successfully matched, I return at the end because there’s no point in going on at that point.

The remaining code looks like this:

1
2
3
4
5
6
7
8
		$(mw.util.addPortletLink('p-cactions', 'javascript:;', '!Copy Titles', 'ca-copy-search-results', 'Copy Titles', null, '#ca-move-to-user')).click(function() {
			var pageList = [];
			$(selectors[i].selector).each(function() {
				pageList.push($(this).attr(selectors[i].attr));
			});
			var str = pageList.join('\n');
			displayOutputText(str, true);
		});

Which is pretty much identical to what we had above, except that I’ve replaced the explicit selector and attribute name with the variables from the current selectors[i] that we’re on (lines 3 and 4 in this snippet).

Yay, and just like that we’ve generalized our search-copy-titles to an any-page-copy-titles, and we could easily add another view to it if we wanted as well!

(By the way - if you don’t have a “Move to user” gadget, you might want to change that last “before” value in the mw.util.addPortletLink.)

Conclusion

With just a bit of work, we can make a gadget to easily copy-paste all of the titles of a search result, user contributions, recent changes, or any other page on the wiki that lists out titles. We can then use this gadget to fluidly go between MediaWiki and third-party tools like AWB, PWB, or our own Python scripts.

The moral of the story is: If one tool doesn’t fully support your entire workflow, start-to-finish, you don’t have to shoehorn yourself into using it all the way. It’s fine to break up the process across two separate tools!

Share on

river
WRITTEN BY
River
River is a developer most at home in MediaWiki and known for building Leaguepedia. She likes cats.

What's on this Page