]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/index_modules/element.js
Fix social link form resubmission after removing invalid link (#6945)
[rails.git] / app / assets / javascripts / index_modules / element.js
1 let abortController = null;
2 const languagesToRequest = [...new Set(OSM.preferred_languages.map(l => l.toLowerCase()))];
3 const wikisToRequest = [...new Set([...OSM.preferred_languages, "en"].map(l => l.split("-")[0] + "wiki"))];
4 const isOfExpectedLanguage = ({ language }) => languagesToRequest[0].startsWith(language) || language === "mul";
5
6 export function element(type) {
7   return function () {
8     const page = {};
9
10     page.load = function (path, id, version) {
11       OSM.loadSidebarContent(path)
12         .then(() => page.init(path, id, version, true));
13     };
14
15     page.init = function (path, id, version, keepViewport) {
16       page._addObject(type, id, version, keepViewport);
17       $(".numbered_pagination").trigger("numbered_pagination:enable");
18       abortController = new AbortController();
19     };
20
21     page.unload = function () {
22       page._removeObject();
23       $(".numbered_pagination").trigger("numbered_pagination:disable");
24       abortController?.abort();
25     };
26
27     page._addObject = function () {};
28     page._removeObject = function () {};
29
30     return page;
31   };
32 };
33
34 export function mappedElement(type) {
35   return function (map) {
36     const page = element(type)(map);
37
38     page._addObject = function (type, id, version, keepViewport) {
39       const hashParams = OSM.parseHash();
40       map.addObject({ type: type, id: parseInt(id, 10), version: version && parseInt(version, 10) }, function (bounds) {
41         if (!hashParams.center && bounds.isValid() &&
42             (!keepViewport || !map.getBounds().contains(bounds))) {
43           OSM.router.withoutMoveListener(function () {
44             map.fitBounds(bounds);
45           });
46         }
47       });
48     };
49
50     page._removeObject = function () {
51       map.removeObject();
52     };
53
54     return page;
55   };
56 };
57
58 $(document).on("click", "button.wdt-preview", e => previewWikidataValue($(e.currentTarget)));
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 = OSM.isDark("bs") ? "Q6545942" : "Q101608434";
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 }