Fix: chromium browsers don't handle spaces in tel:-links well

This commit is contained in:
Pieter Vander Vennet 2024-08-28 12:02:04 +02:00
parent b79835074f
commit 4168ef01e3
5 changed files with 315 additions and 279 deletions

View file

@ -175,13 +175,23 @@
"cs": "Jaké je telefonní číslo {title()}?" "cs": "Jaké je telefonní číslo {title()}?"
}, },
"render": { "render": {
"*": "<a href='tel:{phone}'>{phone}</a>" "special": {
"type": "link",
"href": "tel:{phone}",
"text": "{phone}"
}
}, },
"icon": "./assets/layers/questions/phone.svg", "icon": "./assets/layers/questions/phone.svg",
"mappings": [ "mappings": [
{ {
"if": "contact:phone~*", "if": "contact:phone~*",
"then": "<a href='tel:{contact:phone}'>{contact:phone}</a>", "then":{
"special": {
"type": "link",
"href": "tel:{contact:phone}",
"text": "{contact:phone}"
}
},
"hideInAnswer": true, "hideInAnswer": true,
"icon": "./assets/layers/questions/phone.svg" "icon": "./assets/layers/questions/phone.svg"
} }

View file

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ImmutableStore, Store } from "../../Logic/UIEventSource" import { ImmutableStore, Store } from "../../Logic/UIEventSource"
import Icon from "../Map/Icon.svelte" import Icon from "../Map/Icon.svelte"
import { Utils } from "../../Utils"
export let text: Store<string> export let text: Store<string>
export let href: Store<string> export let href: Store<string>
@ -13,7 +14,7 @@
</script> </script>
<a <a
href={$href} href={Utils.prepareHref($href)}
aria-label={$ariaLabel} aria-label={$ariaLabel}
title={$ariaLabel} title={$ariaLabel}
target={$newTab ? "_blank" : undefined} target={$newTab ? "_blank" : undefined}

View file

@ -1,6 +1,10 @@
<script lang="ts"> <script lang="ts">
import { Utils } from "../../Utils"
export let text: string export let text: string
export let href: string export let href: string
export let classnames: string = undefined export let classnames: string = undefined
export let download: string = undefined export let download: string = undefined
export let ariaLabel: string = undefined export let ariaLabel: string = undefined
@ -9,7 +13,7 @@
</script> </script>
<a <a
{href} href={Utils.prepareHref(href)}
aria-label={ariaLabel} aria-label={ariaLabel}
title={ariaLabel} title={ariaLabel}
target={newTab ? "_blank" : undefined} target={newTab ? "_blank" : undefined}

File diff suppressed because it is too large Load diff

View file

@ -114,7 +114,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
"version", "version",
"wayHandling", "wayHandling",
"widenFactor", "widenFactor",
"width", "width"
] ]
private static extraKeys = [ private static extraKeys = [
"nl", "nl",
@ -133,7 +133,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
"yes", "yes",
"no", "no",
"true", "true",
"false", "false"
] ]
private static injectedDownloads = {} private static injectedDownloads = {}
private static _download_cache = new Map< private static _download_cache = new Map<
@ -150,7 +150,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (Utils.runningFromConsole) { if (Utils.runningFromConsole) {
return return
} }
DOMPurify.addHook("afterSanitizeAttributes", function (node) { DOMPurify.addHook("afterSanitizeAttributes", function(node) {
// set all elements owning target to target=_blank + add noopener noreferrer // set all elements owning target to target=_blank + add noopener noreferrer
const target = node.getAttribute("target") const target = node.getAttribute("target")
if (target) { if (target) {
@ -163,7 +163,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
public static purify(src: string): string { public static purify(src: string): string {
return DOMPurify.sanitize(src, { return DOMPurify.sanitize(src, {
USE_PROFILES: { html: true }, USE_PROFILES: { html: true },
ADD_ATTR: ["target"], // Don't remove target='_blank'. Note that Utils.initDomPurify does add a hook which automatically adds 'rel=noopener' ADD_ATTR: ["target"] // Don't remove target='_blank'. Note that Utils.initDomPurify does add a hook which automatically adds 'rel=noopener'
}) })
} }
@ -344,7 +344,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
console.error("Error while calculating a lazy property", e) console.error("Error while calculating a lazy property", e)
return undefined return undefined
} }
}, }
}) })
} }
@ -368,7 +368,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
whenDone() whenDone()
} }
}) })
}, }
}) })
} }
@ -651,7 +651,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (!Array.isArray(targetV)) { if (!Array.isArray(targetV)) {
throw new Error( throw new Error(
"Cannot concatenate: value to add is not an array: " + "Cannot concatenate: value to add is not an array: " +
JSON.stringify(targetV) JSON.stringify(targetV)
) )
} }
if (Array.isArray(sourceV)) { if (Array.isArray(sourceV)) {
@ -659,9 +659,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
} else { } else {
throw new Error( throw new Error(
"Could not merge concatenate " + "Could not merge concatenate " +
JSON.stringify(sourceV) + JSON.stringify(sourceV) +
" and " + " and " +
JSON.stringify(targetV) JSON.stringify(targetV)
) )
} }
} else { } else {
@ -922,7 +922,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
continue continue
} }
const i = part.charCodeAt(0) const i = part.charCodeAt(0)
result += '"' + keys[i] + '":' + part.substring(1) result += "\"" + keys[i] + "\":" + part.substring(1)
} }
return result return result
@ -960,6 +960,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (!result["error"]) { if (!result["error"]) {
return result return result
} }
const error = result.error
if (error.statuscode === 429 || error.statuscode === 509) {
// rate limited
return result
}
console.log( console.log(
`Request to ${url} failed, Trying again in a moment. Attempt ${ `Request to ${url} failed, Trying again in a moment. Attempt ${
i + 1 i + 1
@ -1000,7 +1005,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
resolve({ resolve({
error: "other error: " + xhr.statusText + ", " + xhr.responseText, error: "other error: " + xhr.statusText + ", " + xhr.responseText,
url, url,
statuscode: xhr.status, statuscode: xhr.status
}) })
} }
} }
@ -1014,12 +1019,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
xhr.onerror = (ev: ProgressEvent<EventTarget>) => xhr.onerror = (ev: ProgressEvent<EventTarget>) =>
reject( reject(
"Could not get " + "Could not get " +
url + url +
", xhr status code is " + ", xhr status code is " +
xhr.status + xhr.status +
" (" + " (" +
xhr.statusText + xhr.statusText +
")" ")"
) )
}) })
} }
@ -1077,12 +1082,13 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
} }
const promise = const promise =
/*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced<T>( /*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced<T>(
url, url,
headers headers
) )
Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() }) Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() })
return await promise return await promise
} }
public static async downloadJson<T = object | []>( public static async downloadJson<T = object | []>(
url: string, url: string,
headers?: Record<string, string> headers?: Record<string, string>
@ -1271,7 +1277,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
): T[] { ): T[] {
const withDistance: [T, number][] = ts.map((t) => [ const withDistance: [T, number][] = ts.map((t) => [
t, t,
Utils.levenshteinDistance(getName(t), reference), Utils.levenshteinDistance(getName(t), reference)
]) ])
withDistance.sort(([_, a], [__, b]) => a - b) withDistance.sort(([_, a], [__, b]) => a - b)
return withDistance.map((n) => n[0]) return withDistance.map((n) => n[0])
@ -1393,7 +1399,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return { return {
r: Utils.percentageToNumber(match[1]), r: Utils.percentageToNumber(match[1]),
g: Utils.percentageToNumber(match[2]), g: Utils.percentageToNumber(match[2]),
b: Utils.percentageToNumber(match[3]), b: Utils.percentageToNumber(match[3])
} }
} }
@ -1404,14 +1410,14 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return { return {
r: parseInt(hex.substr(1, 1), 16), r: parseInt(hex.substr(1, 1), 16),
g: parseInt(hex.substr(2, 1), 16), g: parseInt(hex.substr(2, 1), 16),
b: parseInt(hex.substr(3, 1), 16), b: parseInt(hex.substr(3, 1), 16)
} }
} }
return { return {
r: parseInt(hex.substr(1, 2), 16), r: parseInt(hex.substr(1, 2), 16),
g: parseInt(hex.substr(3, 2), 16), g: parseInt(hex.substr(3, 2), 16),
b: parseInt(hex.substr(5, 2), 16), b: parseInt(hex.substr(5, 2), 16)
} }
} }
@ -1586,7 +1592,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
line: Number(line), line: Number(line),
column: Number(column), column: Number(column),
markdownLocation, markdownLocation,
filename: path.substring(path.lastIndexOf("/") + 1), filename: path.substring(path.lastIndexOf("/") + 1)
} }
} }
@ -1705,6 +1711,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
} }
private static readonly _metrixPrefixes = ["", "k", "M", "G", "T", "P", "E"] private static readonly _metrixPrefixes = ["", "k", "M", "G", "T", "P", "E"]
/** /**
* Converts a big number (e.g. 1000000) into a rounded postfixed verion (e.g. 1M) * Converts a big number (e.g. 1000000) into a rounded postfixed verion (e.g. 1M)
* *
@ -1727,6 +1734,26 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
} }
} }
/**
* Removes or rewrites some characters in links, as some blink/chromium based browsers are picky about them
*
* Utils.prepareHref("tel:+32 123 456") // => "tel:+32123456"
* Utils.prepareHref("https://osm.org/user/User Name") // => "https://osm.org/user/User%20Name"
*/
static prepareHref(href: string): string {
if (href.startsWith("tel:")) {
// Telephone numbers are not allowed to contain spaces in chromium-based browsers
href = "tel:" + href.replaceAll(/[^+0-9]/g, "")
}
/* Chromium based browsers eat the spaces */
href = href.replaceAll(
/ /g,
"%20"
)
return href
}
private static emojiRegex = /[\p{Extended_Pictographic}🛰]$/u private static emojiRegex = /[\p{Extended_Pictographic}🛰]$/u
/** /**