From 62b4087b78d9281f34c9e27ccecb976c3660d133 Mon Sep 17 00:00:00 2001 From: Marwin Hochfelsner <50826859+hlfan@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:55:09 +0200 Subject: [PATCH] Add human-readable wikidata explainations --- app/assets/javascripts/index/element.js | 115 ++++++++++++++++++++++ app/assets/javascripts/osm.js.erb | 2 + app/assets/stylesheets/common.scss | 11 ++- app/controllers/application_controller.rb | 3 +- app/helpers/browse_tags_helper.rb | 8 +- config/locales/en.yml | 5 + config/settings.yml | 3 + test/helpers/browse_tags_helper_test.rb | 14 ++- 8 files changed, 151 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/index/element.js b/app/assets/javascripts/index/element.js index 1571c7475..9561a8b14 100644 --- a/app/assets/javascripts/index/element.js +++ b/app/assets/javascripts/index/element.js @@ -1,10 +1,18 @@ (function () { + let abortController = null; + const languagesToRequest = [...new Set([...OSM.preferred_languages.map(l => l.toLowerCase()), "mul", "en"])]; + const wikisToRequest = [...new Set(languagesToRequest.filter(l => l !== "mul").map(l => l.split("-")[0] + "wiki"))]; + const localeName = new Intl.DisplayNames(OSM.preferred_languages, { type: "language" }); + const isOfExpectedLanguage = ({ language }) => languagesToRequest[0].startsWith(language) || language === "mul"; + $(document).on("click", "a[href='#versions-navigation-active-page-item']", function (e) { scrollToActiveVersion(); $("#versions-navigation-active-page-item a.page-link").trigger("focus"); e.preventDefault(); }); + $(document).on("click", "button.wdt-preview", e => previewWikidataValue($(e.currentTarget))); + OSM.Element = function (map, type) { const page = {}; let scrollStartObserver, scrollEndObserver; @@ -13,12 +21,14 @@ OSM.loadSidebarContent(path, function () { initVersionsNavigation(); page._addObject(type, id, version); + abortController = new AbortController(); }); }; page.load = function (path, id, version) { initVersionsNavigation(); page._addObject(type, id, version, true); + abortController = new AbortController(); }; page.unload = function () { @@ -27,6 +37,7 @@ scrollStartObserver = null; scrollEndObserver?.disconnect(); scrollEndObserver = null; + abortController?.abort(); }; page._addObject = function () {}; @@ -101,4 +112,108 @@ scrollableList.scrollLeft = scrollableList.scrollWidth - scrollableList.offsetWidth; } } + + function previewWikidataValue($btn) { + if (!OSM.WIKIDATA_API_URL) return; + const items = $btn.data("qids"); + if (!items?.length) return; + $btn.prop("disabled", true); + fetch(OSM.WIKIDATA_API_URL + "?" + new URLSearchParams({ + action: "wbgetentities", + format: "json", + origin: "*", + ids: items.join("|"), + props: "labels|sitelinks/urls|claims|descriptions", + languages: languagesToRequest.join("|"), + sitefilter: wikisToRequest.join("|") + }), { + headers: { "Api-User-Agent": "OSM-TagPreview (https://github.com/openstreetmap/openstreetmap-website)" }, + signal: abortController?.signal + }) + .then(response => response.ok ? response.json() : Promise.reject(response)) + .then(({ entities }) => { + if (!entities) return Promise.reject(entities); + $btn + .closest("tr") + .after( + items + .filter(qid => entities[qid]) + .map(qid => getLocalizedResponse(entities[qid])) + .filter(data => data.label || data.icon || data.description || data.article) + .map(data => renderWikidataResponse(data, $btn.siblings(`a[href*="wikidata.org/entity/${data.qid}"]`))) + ); + }) + .catch(() => $btn.prop("disabled", false)); + } + + function getLocalizedResponse(entity) { + const rank = ({ rank }) => ({ preferred: 1, normal: 0, deprecated: -1 })[rank] ?? 0; + const toBestClaim = (out, claim) => (rank(claim) > rank(out)) ? claim : out; + const toFirstOf = (property) => (out, localization) => out ?? entity[property][localization]; + const data = { + qid: entity.id, + label: languagesToRequest.reduce(toFirstOf("labels"), null), + icon: [ + "P8972", // small logo or icon + "P154", // logo image + "P14" // traffic sign + ].reduce((out, prop) => out ?? entity.claims[prop]?.reduce(toBestClaim)?.mainsnak?.datavalue?.value, null), + description: languagesToRequest.reduce(toFirstOf("descriptions"), null), + article: wikisToRequest.reduce(toFirstOf("sitelinks"), null) + }; + if (data.article) data.article.language = data.article.site.replace("wiki", ""); + return data; + } + + function renderWikidataResponse({ icon, label, article, description }, $link) { + const cell = $("