User:Infrastruktur/cat-wd-linked.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/*
* cat-wd-linked: In category pages on Commons and Wikipedia, adds a menu item 'Category WD links'
* that when clicked shows which subcategories are linked to Wikidata.
*
* It also shows which of the subcategories link to a Wikidata item that are missing claims for
* P301:'category's main topic' or are missing P910:'topic's main category' claims.
* It also shows which of the subcategories link to a Wikidata 'main' item, when it has
* an associated 'category' item that it ought to be relinked to.
*
* By: User:Infrastruktur (@www.wikidata.org)
*
* License: CC-BY-SA 4.0
*
* Add to common.js on the wiki you want to use it on, or alternatively to your global.js
* on metawiki:
* mw.loader.load('//www.wikidata.org/w/index.php?title=User:Infrastruktur/cat-wd-linked.js&action=raw&ctype=text/javascript'); // [[User:Infrastruktur/cat-wd-linked.js]]
*
* Test categories:
* https://commons.wikimedia.org/wiki/Category:Fortifications_by_type
* https://en.wikipedia.org/wiki/Category:Fortifications_by_type
* https://commons.wikimedia.org/wiki/Category:Defensive_walls_by_type
* https://commons.wikimedia.org/wiki/Category:Museums_in_Cameroon
* https://commons.wikimedia.org/wiki/Category:Buddhist_sculptures_by_subject
*
*/
(function() {
'use strict';
const UA_STRING = 'Cat-WD-Linked/current (User:Infrastruktur)';
const max_anchor_uris = 2000; // to remain below the maximum query size
const max_cat_expansion = 0; // use this for testing purposes only
let on_commons = false, on_wikipedia = false;
function cats_query(uris, uri_prefix)
{
console.assert(typeof uris === 'object');
console.assert(typeof uri_prefix === 'string');
const wdqs_endpoint = 'https://query.wikidata.org/sparql';
const wdqs_query_prefix =
'select distinct ?article (substr(str(?item_), 32) as ?item) ?missing_main_topic ?missing_main_cat ?relinking_needed ' +
'where {' +
' values ?article_ {';
const wdqs_query_postfix =
' }' +
' bind(uri(concat("' + uri_prefix + '", ?article_)) as ?article)' +
' optional {' +
' ?article schema:about ?item_ .' +
' optional {' +
' ?item_ wdt:P31 wd:Q4167836 .' +
' filter not exists { ?item_ wdt:P301 [] . }' +
' bind("yes" as ?missing_main_topic)' +
' }' +
' optional {' +
' filter not exists { ?item_ wdt:P31 wd:Q4167836 . }' +
' filter not exists { ?item_ wdt:P910 [] . }' +
' bind("yes" as ?missing_main_cat)' +
' }' +
' optional {' +
' ?item_ wdt:P910|(^wdt:P301) [] .' +
' filter not exists { ?item_ wdt:P31 wd:Q4167836 . }' +
' bind("yes" as ?relinking_needed)' +
' }' +
' }' +
'}';
const wdqs_query = wdqs_query_prefix + uris.map(uri => '"' + uri + '"').join(' ') + wdqs_query_postfix;
const wdqs_fulluri = wdqs_endpoint + '?timeout=20';
const promise = fetch(wdqs_fulluri, {
method: 'POST',
headers: {
'Accept': 'application/sparql-results+json',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Api-User-Agent': UA_STRING
},
body: 'query=' + encodeURIComponent(wdqs_query)
});
return promise;
}
function check_cats(e)
{
const plink_elem = this;
const anchor_elems = document.querySelectorAll('div#mw-subcategories a[href]');
const uri_prefix = 'https://' + mw.config.get('wgServerName');
let anchor_uris = [];
anchor_elems.forEach(function(elem) {
const uri = elem.getAttribute('href');
anchor_uris.push(uri);
// console.log(uri);
});
if (anchor_uris.length > max_anchor_uris) {
console.log('Requested to check ' + anchor_uris.length + ' subcategory links, which is too many. Checking only the first ' + max_anchor_uris + '.');
} else {
console.log('Requested to check ' + anchor_uris.length + ' subcategory links.');
}
cats_query(anchor_uris.slice(0, max_anchor_uris), uri_prefix)
.then((response) => response.json())
.then(function(data) {
console.log('Fetched data for categories.');
const results = data.results.bindings;
data.results.bindings.forEach(function(row) {
const uri = row.article.value.slice(uri_prefix.length);
// console.log(uri);
if ('item' in row) { // linked
document.querySelectorAll('div#mw-subcategories a[href="' + uri + '"]').forEach(function(elem) {
// console.log(elem);
if (('relinking_needed' in row)) {
elem.setAttribute('style', 'color: #ff00ff');
} else if ((('missing_main_topic' in row) || ('missing_main_cat' in row))) {
elem.setAttribute('style', 'color: #7f7f00');
} else {
elem.setAttribute('style', 'color: #007f00');
}
const qid = row.item.value;
// console.log(qid);
let an = document.createElement('a');
an.setAttribute('href', 'https://www.wikidata.org/wiki/' + qid);
an.setAttribute('target', '_blank');
an.innerText = '(WD)';
// console.log(an);
let tn = document.createTextNode(' ');
elem.after(tn, an);
});
} else { // not linked
document.querySelectorAll('div#mw-subcategories a[href="' + uri + '"]').forEach(function(elem) {
//console.log(elem);
elem.setAttribute('style', 'color: #7f0000');
let an = document.createElement('a');
an.setAttribute('href', 'https://www.wikidata.org/wiki/Special:Search');
an.setAttribute('target', '_blank');
an.innerText = '(WD search)';
// console.log(an);
let tn = document.createTextNode(' ');
elem.after(tn, an);
});
}
});
})
.catch((error) => console.error('Cat-WD-Links query failed: ' + error));
plink_elem.removeEventListener('click', check_cats);
e.preventDefault();
}
function expand_cats(e)
{
const plink_elem = this;
const anchor_elems = document.querySelectorAll('div#mw-subcategories a.CategoryTreeToggle[aria-expanded="false"]');
/* Expanding subcategories fires off one API call per subcategory to be expanded so
this is something you ought not to do except for testing purposes. */
if (anchor_elems.length >= max_cat_expansion) {
console.log('Max category expansion exceeded with ' + anchor_elems.length + ' expandable subcategories.');
plink_elem.removeEventListener('click', expand_cats);
} else {
for (let ai = anchor_elems.length - 1; ai > -1; ai--) {
setTimeout(function() { anchor_elems.item(ai).click(); }, 0);
}
}
e.preventDefault();
}
function main() {
on_commons = mw.config.get('wgServerName') === 'commons.wikimedia.org';
on_wikipedia = mw.config.get('wgServerName').match(/^[a-z]+\.wikipedia\.org$/);
if (!(on_commons || on_wikipedia)) { return; }
if (mw.config.get('wgNamespaceNumber') !== 14 ||
mw.config.get('wgAction') !== 'view') { return; }
let plink = mw.util.addPortletLink(
'p-tb', '#', 'Category WD links', 't-cat-wd-links', 'Category WD links'
);
plink.addEventListener('click', check_cats);
if (max_cat_expansion > 0) {
let plink_expand = mw.util.addPortletLink(
'p-tb', '#', 'Expand subcategories', 't-cat-expand', 'Expand subcategories'
);
plink_expand.addEventListener('click', expand_cats); // turn your cat into a chonk
}
}
mw.loader.using([ 'mediawiki.util', 'mediawiki.api' ])
.then(function() {
/* Wait for DOM ready */
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
main();
});
} else {
main();
}
});
})();