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 siteScheme = ({ light: "Q101608434", dark: "Q6545942" })[$("html").data("bs-theme")];
96 const scheme = ({ qualifiers }) => qualifiers?.P8798?.some(q => q?.datavalue?.value?.id === siteScheme) ?? 0;
97 const rank = ({ rank }) => ({ preferred: 2, normal: 0, deprecated: -2 })[rank] ?? 0;
98 const toBestClaim = (out, claim) => (rank(claim) + scheme(claim) > rank(out) + scheme(out)) ? claim : out;
99 const toFirstOf = (property) => (out, localization) => out ?? property[localization];
102 label: languagesToRequest.reduce(toFirstOf(entity.labels), null),
104 "P8972", // small logo or icon
105 "P154", // logo image
106 "P14" // traffic sign
107 ].reduce((out, prop) => out ?? entity.claims[prop]?.reduce(toBestClaim)?.mainsnak?.datavalue?.value, null),
108 description: languagesToRequest.reduce(toFirstOf(entity.descriptions), null),
109 article: wikisToRequest.reduce(toFirstOf(entity.sitelinks), null)
111 if (data.article) data.article.language = data.article.site.replace("wiki", "");
115 function renderWikidataResponse({ icon, label, article, description }, $link) {
116 const localeName = new Intl.DisplayNames(OSM.preferred_languages, { type: "language" });
117 const cell = $("<td>")
119 .addClass("bg-body-tertiary");
121 if (icon && OSM.WIKIMEDIA_COMMONS_URL) {
122 let src = OSM.WIKIMEDIA_COMMONS_URL + "Special:Redirect/file/" + encodeURIComponent(icon) + "?mobileaction=toggle_view_desktop";
123 if (!icon.endsWith(".svg")) src += "&width=128";
125 .attr("href", OSM.WIKIMEDIA_COMMONS_URL + "File:" + encodeURIComponent(icon) + `?uselang=${OSM.i18n.locale}`)
126 .append($("<img>").attr({ src, height: "32" }))
127 .addClass("float-end mb-1 ms-2")
131 const link = $link.clone()
135 if (!isOfExpectedLanguage(label)) {
136 link.attr("lang", label.language);
137 link.after($("<sup>").text(" " + localeName.of(label.language)));
141 const link = $("<a>")
142 .attr("href", article.url + `?uselang=${OSM.i18n.locale}`)
143 .text(label ? OSM.i18n.t("javascripts.element.wikipedia") : article.title)
150 if (!isOfExpectedLanguage(article)) {
151 link.attr("lang", article.language);
152 link.after($("<sup>").text(" " + localeName.of(article.language)));
156 const text = $("<div>")
157 .text(description.value)
161 if (!isOfExpectedLanguage(description)) {
162 text.attr("lang", description.language);
165 return $("<tr>").append(cell);