Repository: openstreetmap/iD (JavaScript)

Issue:
Wikipedia link with question mark
### URL

https://www.openstreetmap.org/edit?way=394705975#map=19/44.81767/20.45303

### How to reproduce the issue?

1. Clear the wikipedia tag.
2. Select language "српски / srpski".
3. Start typing "Кафана „?”". Select such value from the drop-down.
4. Click on the button "view on wikipedia.org".
5. You will get the error page in the browser.


### Screenshot(s) or anything else?

Tag "wikipedia=sr:Кафана_„%3F”" works, tag "wikipedia=sr:Кафана „?”" doesn't.
If you save tag "wikipedia=sr:Кафана „?”" problem will stay in the view (not the edit) mode too.

![1](https://github.com/openstreetmap/iD/assets/7187657/c253edd9-fbc9-4074-b2b9-5aaffb36a9d1)
![2](https://github.com/openstreetmap/iD/assets/7187657/ece3bc43-a79b-451d-8a43-56727d0efb04)
![image](https://github.com/openstreetmap/iD/assets/7187657/01ff2a9a-3222-43b9-9ca9-7747537531ca)


### Which deployed environments do you see the issue in?

Released version at openstreetmap.org/edit

### What version numbers does this issue effect?

2.27.3

### Which browsers are you seeing this problem on?

Chrome


Discussion: In URL encoding, "?" is represented as "%3F" 
That is why Кафана_„%3F” works while the former doesn't.



Fix (patch):
diff --git a/modules/ui/fields/wikipedia.js b/modules/ui/fields/wikipedia.js
index 832881c770b..30f313d2a19 100644
--- a/modules/ui/fields/wikipedia.js
+++ b/modules/ui/fields/wikipedia.js
@@ -11,6 +11,8 @@ import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';
 
 
 export function uiFieldWikipedia(field, context) {
+  const scheme = 'https://';
+  const domain = 'wikipedia.org';
   const dispatch = d3_dispatch('change');
   const wikipedia = services.wikipedia;
   const wikidata = services.wikidata;
@@ -132,7 +134,7 @@ export function uiFieldWikipedia(field, context) {
     link = link.enter()
       .append('button')
       .attr('class', 'form-field-button wiki-link')
-      .attr('title', t('icons.view_on', { domain: 'wikipedia.org' }))
+      .attr('title', t('icons.view_on', { domain }))
       .call(svgIcon('#iD-icon-out-link'))
       .merge(link);
 
@@ -280,24 +282,13 @@ export function uiFieldWikipedia(field, context) {
       const nativeLangName = tagLangInfo[1];
       utilGetSetValue(_langInput, nativeLangName);
       utilGetSetValue(_titleInput, tagArticleTitle + (anchor ? ('#' + anchor) : ''));
-      if (anchor) {
-        try {
-          // Best-effort `anchorencode:` implementation
-          anchor = encodeURIComponent(anchor.replace(/ /g, '_')).replace(/%/g, '.');
-        } catch (e) {
-          anchor = anchor.replace(/ /g, '_');
-        }
-      }
-      _wikiURL = 'https://' + tagLang + '.wikipedia.org/wiki/' +
-        tagArticleTitle.replace(/ /g, '_') + (anchor ? ('#' + anchor) : '');
-
-    // unrecognized value format
+      _wikiURL = `${scheme}${tagLang}.${domain}/wiki/${wiki.encodePath(tagArticleTitle, anchor)}`;
     } else {
       utilGetSetValue(_titleInput, value);
       if (value && value !== '') {
         utilGetSetValue(_langInput, '');
         const defaultLangInfo = defaultLanguageInfo();
-        _wikiURL = `https://${defaultLangInfo[2]}.wikipedia.org/w/index.php?fulltext=1&search=${value}`;
+        _wikiURL = `${scheme}${defaultLangInfo[2]}.${domain}/w/index.php?fulltext=1&search=${value}`;
       } else {
         const shownOrDefaultLangInfo = language(true /* skipEnglishFallback */);
         utilGetSetValue(_langInput, shownOrDefaultLangInfo[1]);
@@ -306,6 +297,18 @@ export function uiFieldWikipedia(field, context) {
     }
   }
 
+  wiki.encodePath = (tagArticleTitle, anchor) => {
+    const underscoredTitle = tagArticleTitle.replace(/ /g, '_');
+    const uriEncodedUnderscoredTitle = encodeURIComponent(underscoredTitle);
+    const uriEncodedAnchorFragment = wiki.encodeURIAnchorFragment(anchor);
+    return `${uriEncodedUnderscoredTitle}${uriEncodedAnchorFragment}`;
+  };
+
+  wiki.encodeURIAnchorFragment = (anchor) => {
+    if (!anchor) return '';
+    const underscoredAnchor = anchor.replace(/ /g, '_');
+    return '#' + encodeURIComponent(underscoredAnchor);
+  };
 
   wiki.entityIDs = (val) => {
     if (!arguments.length) return _entityIDs;