MediaWiki:Common.js
MediaWiki interface page
More actions
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.
/* Landrace.wiki – Leaflet map with SMW Ask API support (Conservation Priority) */
(function () {
var LVER = '1.9.4';
var CDN = 'https://unpkg.com/leaflet@' + LVER + '/dist/';
function addCSS(href, id) {
if (id && document.getElementById(id)) return;
var l = document.createElement('link');
l.rel = 'stylesheet';
l.href = href;
if (id) l.id = id;
document.head.appendChild(l);
}
function addJS(src, cb) {
if (window.L && typeof window.L.map === 'function') return cb();
var existing = document.querySelector('script[data-lw-leaflet="1"]');
if (existing) {
existing.addEventListener('load', cb);
return;
}
var s = document.createElement('script');
s.src = src;
s.setAttribute('data-lw-leaflet', '1');
s.onload = cb;
s.onerror = function () { console.error('Leaflet failed to load:', src); };
document.head.appendChild(s);
}
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
return ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]);
});
}
// Conservation Priority colors: Critical / High / Medium / Low
function statusColor(s) {
return ({
'Critical': '#e74c3c',
'High': '#e67e22',
'Medium': '#f1c40f',
'Low': '#2ecc71'
})[s] || '#3498db';
}
function popupForAccession(p) {
var title = [p.id, p.name].filter(Boolean).join(' — ');
var status = p.status ? esc(p.status) : '';
var link = p.page_url ? '<br><a href="' + esc(p.page_url) + '">Open accession</a>' : '';
return '<b>' + esc(title) + '</b>' + (status ? '<br>' + status : '') + link;
}
// Convert SMW Ask API response to GeoJSON FeatureCollection
function smwToGeoJSON(data) {
var features = [];
var results = (data && data.query && data.query.results) ? data.query.results : (data && data.results ? data.results : null);
if (!results) {
console.log('[lw-map] No results in SMW response');
return { type: 'FeatureCollection', features: [] };
}
for (var pageName in results) {
if (!Object.prototype.hasOwnProperty.call(results, pageName)) continue;
var item = results[pageName] || {};
var printouts = item.printouts || {};
var coordsArr = printouts['Has GPS coordinates'];
if (!coordsArr || !coordsArr.length) continue;
var coord = coordsArr[0];
var lat = null, lon = null;
// SMW coords often come back as an object with lat/lon
if (coord && typeof coord === 'object') {
lat = coord.lat;
lon = coord.lon != null ? coord.lon : (coord.lng != null ? coord.lng : coord.long);
} else if (typeof coord === 'string') {
// Try "lat, lon"
var parts = coord.split(',');
if (parts.length === 2) {
lat = parseFloat(parts[0].trim());
lon = parseFloat(parts[1].trim());
}
}
if (lat == null || lon == null || isNaN(lat) || isNaN(lon)) {
console.log('[lw-map] Invalid coordinates for', pageName, coord);
continue;
}
// Name
var nameArr = printouts['Has descriptive name'];
var name = (nameArr && nameArr.length) ? nameArr[0] : pageName;
// Conservation Priority
var statusArr = printouts['Has conservation priority'];
var status = (statusArr && statusArr.length) ? statusArr[0] : '';
// Accession ID
var idArr = printouts['Has accession ID'];
var idVal = (idArr && idArr.length) ? idArr[0] : '';
if (idVal && typeof idVal === 'object' && idVal.fulltext) idVal = idVal.fulltext;
// Page URL (prefer SMW fullurl, otherwise MW util)
var pageUrl = item.fullurl || (window.mw && mw.util && mw.util.getUrl ? mw.util.getUrl(pageName) : ('/wiki/' + encodeURIComponent(pageName)));
features.push({
type: 'Feature',
properties: {
id: idVal || '',
name: name || pageName,
status: status || '',
page_url: pageUrl
},
geometry: {
type: 'Point',
coordinates: [lon, lat]
}
});
}
console.log('[lw-map] Converted', features.length, 'features from SMW');
return { type: 'FeatureCollection', features: features };
}
function fetchSMW(askQuery, callback) {
if (!window.mw || !mw.config) {
callback(new Error('MediaWiki config not available'), null);
return;
}
var apiUrl = mw.config.get('wgScriptPath') + '/api.php';
var params = new URLSearchParams({
action: 'ask',
query: askQuery,
format: 'json'
});
console.log('[lw-map] Fetching SMW query:', askQuery);
fetch(apiUrl + '?' + params.toString())
.then(function (r) {
if (!r.ok) throw new Error('SMW API error: ' + r.status);
return r.json();
})
.then(function (data) {
var geojson = smwToGeoJSON(data);
callback(null, geojson);
})
.catch(function (err) {
console.error('[lw-map] SMW fetch error:', err);
callback(err, null);
});
}
function buildLegendControl() {
var legend = L.control({ position: 'bottomleft' });
legend.onAdd = function () {
var div = L.DomUtil.create('div', 'lw-legend-map');
div.style.cssText =
'background: white;' +
'padding: 12px;' +
'border-radius: 8px;' +
'box-shadow: 0 2px 10px rgba(0,0,0,0.2);' +
'border: 1px solid #ccc;' +
'font-size: 12px;' +
'line-height: 1.4;';
var html = '<div style="font-weight:700;margin-bottom:8px;color:#333;">Conservation Priority</div>';
var items = [
['Critical', '#e74c3c'],
['High', '#e67e22'],
['Medium', '#f1c40f'],
['Low', '#2ecc71']
];
items.forEach(function (it) {
html +=
'<div style="display:flex;align-items:center;margin:6px 0;">' +
'<div style="width:12px;height:12px;background:' + it[1] + ';border-radius:50%;margin-right:8px;border:1px solid rgba(0,0,0,0.2);"></div>' +
'<span style="color:#555;">' + esc(it[0]) + '</span>' +
'</div>';
});
div.innerHTML = html;
return div;
};
return legend;
}
function initOne(el) {
if (el.dataset.init) return;
el.dataset.init = '1';
if (el.clientHeight < 100) el.style.height = '70vh';
var map = L.map(el, { minZoom: 2, maxZoom: 15, zoomControl: true });
map.setView([15, 105], 5);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OSM' }).addTo(map);
buildLegendControl().addTo(map);
var layers = [];
var done = 0;
var expected = 0;
// External/inline sources (optional)
['regions', 'populations', 'accessions'].forEach(function (k) {
if (el.dataset[k]) expected++;
});
// SMW query source (optional)
if (el.dataset.smwQuery) expected++;
function finishOne() {
done++;
if (done >= expected) fitToDataOrDefault();
}
function fitToDataOrDefault() {
try {
if (layers.length) {
var group = L.featureGroup(layers);
var b = group.getBounds();
if (b && b.isValid()) {
map.fitBounds(b.pad(0.2), { maxZoom: 10 });
} else {
map.setView([15, 105], 5);
}
} else {
map.setView([15, 105], 5);
}
} catch (e) {
console.error('[lw-map] fit error:', e);
map.setView([15, 105], 5);
}
setTimeout(function () { map.invalidateSize(true); }, 100);
}
function addGeoJSON(kind, g) {
if (!g || !g.features || !g.features.length) {
console.log('[lw-map] No features for', kind);
finishOne();
return;
}
var layer = L.geoJSON(g, {
style: function (f) {
return {
color: statusColor((f.properties || {}).status),
weight: 2,
fillOpacity: 0.2,
opacity: 0.8
};
},
pointToLayer: function (f, ll) {
return L.circleMarker(ll, {
radius: 5,
color: '#fff',
weight: 2,
fillColor: statusColor((f.properties || {}).status),
fillOpacity: 0.9
});
},
onEachFeature: function (f, ly) {
var p = (f && f.properties) ? f.properties : {};
ly.bindPopup(popupForAccession(p));
var tip = p.id || p.name || '';
if (tip) ly.bindTooltip(esc(tip), { direction: 'top', offset: [0, -8], opacity: 0.9 });
}
}).addTo(map);
layers.push(layer);
finishOne();
}
function loadData(kind, data) {
var trimmed = (data || '').trim();
if (!trimmed) { finishOne(); return; }
// Inline JSON
if (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {
try {
var parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) parsed = { type: 'FeatureCollection', features: parsed };
addGeoJSON(kind, parsed);
} catch (e) {
console.error('[lw-map] JSON parse error for', kind, e);
finishOne();
}
return;
}
// URL fetch
fetch(trimmed)
.then(function (r) { if (!r.ok) throw new Error(kind + ' fetch ' + r.status); return r.json(); })
.then(function (json) { addGeoJSON(kind, json); })
.catch(function (err) { console.error('[lw-map] Fetch error for', kind, err); finishOne(); });
}
// Load any explicit sources (optional)
['regions', 'populations', 'accessions'].forEach(function (kind) {
if (el.dataset[kind]) loadData(kind, el.dataset[kind]);
});
// Load SMW query as accessions
if (el.dataset.smwQuery) {
fetchSMW(el.dataset.smwQuery, function (err, geojson) {
if (err) { finishOne(); return; }
addGeoJSON('accessions', geojson);
});
}
if (expected === 0) {
// No data configured
map.setView([15, 105], 5);
setTimeout(function () { map.invalidateSize(true); }, 100);
}
// Tiny zoom readout
var zoomDisplay = L.control({ position: 'topright' });
zoomDisplay.onAdd = function () {
var div = L.DomUtil.create('div');
div.style.cssText =
'background: rgba(255,255,255,0.9);' +
'padding: 5px 8px;' +
'border-radius: 4px;' +
'font-size: 11px;' +
'color: #666;' +
'border: 1px solid #ccc;';
div.textContent = 'Zoom: ' + map.getZoom();
map.on('zoomend', function () { div.textContent = 'Zoom: ' + map.getZoom(); });
return div;
};
zoomDisplay.addTo(map);
}
function init(root) {
(root || document).querySelectorAll('.lw-map').forEach(initOne);
}
addCSS(CDN + 'leaflet.css', 'leaflet-css');
addJS(CDN + 'leaflet.js', function () {
console.log('[lw-map] Leaflet loaded; initializing maps');
init();
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function ($c) {
init($c && $c[0] ? $c[0] : document);
});
}
});
})();