2   let abortController = null;
 
   3   const languagesToRequest = [...new Set(OSM.preferred_languages.map(l => l.toLowerCase()))];
 
   4   const wikisToRequest = [...new Set([...OSM.preferred_languages, "en"].map(l => l.split("-")[0] + "wiki"))];
 
   5   const isOfExpectedLanguage = ({ language }) => languagesToRequest[0].startsWith(language) || language === "mul";
 
   7   $(document).on("click", "button.wdt-preview", e => previewWikidataValue($(e.currentTarget)));
 
   9   OSM.Element = type => function () {
 
  12     page.pushstate = page.popstate = function (path, id, version) {
 
  13       OSM.loadSidebarContent(path, function () {
 
  14         page._addObject(type, id, version);
 
  15         $(".numbered_pagination").trigger("numbered_pagination:enable");
 
  16         abortController = new AbortController();
 
  20     page.load = function (path, id, version) {
 
  21       page._addObject(type, id, version, true);
 
  22       $(".numbered_pagination").trigger("numbered_pagination:enable");
 
  23       abortController = new AbortController();
 
  26     page.unload = function () {
 
  28       $(".numbered_pagination").trigger("numbered_pagination:disable");
 
  29       abortController?.abort();
 
  32     page._addObject = function () {};
 
  33     page._removeObject = function () {};
 
  38   OSM.MappedElement = type => function (map) {
 
  39     const page = OSM.Element(type)(map);
 
  41     page._addObject = function (type, id, version, center) {
 
  42       const hashParams = OSM.parseHash();
 
  43       map.addObject({ type: type, id: parseInt(id, 10), version: version && parseInt(version, 10) }, function (bounds) {
 
  44         if (!hashParams.center && bounds.isValid() &&
 
  45             (center || !map.getBounds().contains(bounds))) {
 
  46           OSM.router.withoutMoveListener(function () {
 
  47             map.fitBounds(bounds);
 
  53     page._removeObject = function () {
 
  60   function previewWikidataValue($btn) {
 
  61     if (!OSM.WIKIDATA_API_URL) return;
 
  62     const items = $btn.data("qids");
 
  63     if (!items?.length) return;
 
  64     $btn.prop("disabled", true);
 
  65     fetch(OSM.WIKIDATA_API_URL + "?" + new URLSearchParams({
 
  66       action: "wbgetentities",
 
  70       props: "labels|sitelinks/urls|claims|descriptions",
 
  71       languages: languagesToRequest.join("|"),
 
  73       sitefilter: wikisToRequest.join("|")
 
  75       headers: { "Api-User-Agent": "OSM-TagPreview (https://github.com/openstreetmap/openstreetmap-website)" },
 
  76       signal: abortController?.signal
 
  78       .then(response => response.ok ? response.json() : Promise.reject(response))
 
  79       .then(({ entities }) => {
 
  80         if (!entities) return Promise.reject(entities);
 
  85               .filter(qid => entities[qid])
 
  86               .map(qid => getLocalizedResponse(entities[qid]))
 
  87               .filter(data => data.label || data.icon || data.description || data.article)
 
  88               .map(data => renderWikidataResponse(data, $btn.siblings(`a[href*="wikidata.org/entity/${data.qid}"]`)))
 
  91       .catch(() => $btn.prop("disabled", false));
 
  94   function getLocalizedResponse(entity) {
 
  95     const rank = ({ rank }) => ({ preferred: 1, normal: 0, deprecated: -1 })[rank] ?? 0;
 
  96     const toBestClaim = (out, claim) => (rank(claim) > rank(out)) ? claim : out;
 
  97     const toFirstOf = (property) => (out, localization) => out ?? property[localization];
 
 100       label: languagesToRequest.reduce(toFirstOf(entity.labels), null),
 
 102         "P8972", // small logo or icon
 
 103         "P154", // logo image
 
 104         "P14" // traffic sign
 
 105       ].reduce((out, prop) => out ?? entity.claims[prop]?.reduce(toBestClaim)?.mainsnak?.datavalue?.value, null),
 
 106       description: languagesToRequest.reduce(toFirstOf(entity.descriptions), null),
 
 107       article: wikisToRequest.reduce(toFirstOf(entity.sitelinks), null)
 
 109     if (data.article) data.article.language = data.article.site.replace("wiki", "");
 
 113   function renderWikidataResponse({ icon, label, article, description }, $link) {
 
 114     const localeName = new Intl.DisplayNames(OSM.preferred_languages, { type: "language" });
 
 115     const cell = $("<td>")
 
 117       .addClass("bg-body-tertiary");
 
 119     if (icon && OSM.WIKIMEDIA_COMMONS_URL) {
 
 120       let src = OSM.WIKIMEDIA_COMMONS_URL + "Special:Redirect/file/" + encodeURIComponent(icon) + "?mobileaction=toggle_view_desktop";
 
 121       if (!icon.endsWith(".svg")) src += "&width=128";
 
 123         .attr("href", OSM.WIKIMEDIA_COMMONS_URL + "File:" + encodeURIComponent(icon) + `?uselang=${OSM.i18n.locale}`)
 
 124         .append($("<img>").attr({ src, height: "32" }))
 
 125         .addClass("float-end mb-1 ms-2")
 
 129       const link = $link.clone()
 
 133       if (!isOfExpectedLanguage(label)) {
 
 134         link.attr("lang", label.language);
 
 135         link.after($("<sup>").text(" " + localeName.of(label.language)));
 
 139       const link = $("<a>")
 
 140         .attr("href", article.url + `?uselang=${OSM.i18n.locale}`)
 
 141         .text(label ? OSM.i18n.t("javascripts.element.wikipedia") : article.title)
 
 148       if (!isOfExpectedLanguage(article)) {
 
 149         link.attr("lang", article.language);
 
 150         link.after($("<sup>").text(" " + localeName.of(article.language)));
 
 154       const text = $("<div>")
 
 155         .text(description.value)
 
 159       if (!isOfExpectedLanguage(description)) {
 
 160         text.attr("lang", description.language);
 
 163     return $("<tr>").append(cell);