]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/index/element.js
Merge remote-tracking branch 'upstream/pull/7009'
[rails.git] / app / assets / javascripts / index / element.js
1 (function () {
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";
6
7   $(document).on("click", "button.wdt-preview", e => previewWikidataValue($(e.currentTarget)));
8
9   OSM.Element = type => function () {
10     const page = {};
11
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();
17       });
18     };
19
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();
24     };
25
26     page.unload = function () {
27       page._removeObject();
28       $(".numbered_pagination").trigger("numbered_pagination:disable");
29       abortController?.abort();
30     };
31
32     page._addObject = function () {};
33     page._removeObject = function () {};
34
35     return page;
36   };
37
38   OSM.MappedElement = type => function (map) {
39     const page = OSM.Element(type)(map);
40
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);
48           });
49         }
50       });
51     };
52
53     page._removeObject = function () {
54       map.removeObject();
55     };
56
57     return page;
58   };
59
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",
67       format: "json",
68       origin: "*",
69       ids: items.join("|"),
70       props: "labels|sitelinks/urls|claims|descriptions",
71       languages: languagesToRequest.join("|"),
72       languagefallback: 1,
73       sitefilter: wikisToRequest.join("|")
74     }), {
75       headers: { "Api-User-Agent": "OSM-TagPreview (https://github.com/openstreetmap/openstreetmap-website)" },
76       signal: abortController?.signal
77     })
78       .then(response => response.ok ? response.json() : Promise.reject(response))
79       .then(({ entities }) => {
80         if (!entities) return Promise.reject(entities);
81         $btn
82           .closest("tr")
83           .after(
84             items
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}"]`)))
89           );
90       })
91       .catch(() => $btn.prop("disabled", false));
92   }
93
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];
100     const data = {
101       qid: entity.id,
102       label: languagesToRequest.reduce(toFirstOf(entity.labels), null),
103       icon: [
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)
110     };
111     if (data.article) data.article.language = data.article.site.replace("wiki", "");
112     return data;
113   }
114
115   function renderWikidataResponse({ icon, label, article, description }, $link) {
116     const localeName = new Intl.DisplayNames(OSM.preferred_languages, { type: "language" });
117     const cell = $("<td>")
118       .attr("colspan", 2)
119       .addClass("bg-body-tertiary");
120
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";
124       $("<a>")
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")
128         .appendTo(cell);
129     }
130     if (label) {
131       const link = $link.clone()
132         .text(label.value)
133         .attr("dir", "auto")
134         .appendTo(cell);
135       if (!isOfExpectedLanguage(label)) {
136         link.attr("lang", label.language);
137         link.after($("<sup>").text(" " + localeName.of(label.language)));
138       }
139     }
140     if (article) {
141       const link = $("<a>")
142         .attr("href", article.url + `?uselang=${OSM.i18n.locale}`)
143         .text(label ? OSM.i18n.t("javascripts.element.wikipedia") : article.title)
144         .attr("dir", "auto")
145         .appendTo(cell);
146       if (label) {
147         link.before(" (");
148         link.after(")");
149       }
150       if (!isOfExpectedLanguage(article)) {
151         link.attr("lang", article.language);
152         link.after($("<sup>").text(" " + localeName.of(article.language)));
153       }
154     }
155     if (description) {
156       const text = $("<div>")
157         .text(description.value)
158         .addClass("small")
159         .attr("dir", "auto")
160         .appendTo(cell);
161       if (!isOfExpectedLanguage(description)) {
162         text.attr("lang", description.language);
163       }
164     }
165     return $("<tr>").append(cell);
166   }
167 }());