forked from MapComplete/MapComplete
Fix: chromium browsers don't handle spaces in tel:
-links well
This commit is contained in:
parent
b79835074f
commit
4168ef01e3
5 changed files with 315 additions and 279 deletions
|
@ -175,13 +175,23 @@
|
|||
"cs": "Jaké je telefonní číslo {title()}?"
|
||||
},
|
||||
"render": {
|
||||
"*": "<a href='tel:{phone}'>{phone}</a>"
|
||||
"special": {
|
||||
"type": "link",
|
||||
"href": "tel:{phone}",
|
||||
"text": "{phone}"
|
||||
}
|
||||
},
|
||||
"icon": "./assets/layers/questions/phone.svg",
|
||||
"mappings": [
|
||||
{
|
||||
"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,
|
||||
"icon": "./assets/layers/questions/phone.svg"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import Icon from "../Map/Icon.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let text: Store<string>
|
||||
export let href: Store<string>
|
||||
|
@ -13,7 +14,7 @@
|
|||
</script>
|
||||
|
||||
<a
|
||||
href={$href}
|
||||
href={Utils.prepareHref($href)}
|
||||
aria-label={$ariaLabel}
|
||||
title={$ariaLabel}
|
||||
target={$newTab ? "_blank" : undefined}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let text: string
|
||||
export let href: string
|
||||
|
||||
|
||||
export let classnames: string = undefined
|
||||
export let download: string = undefined
|
||||
export let ariaLabel: string = undefined
|
||||
|
@ -9,7 +13,7 @@
|
|||
</script>
|
||||
|
||||
<a
|
||||
{href}
|
||||
href={Utils.prepareHref(href)}
|
||||
aria-label={ariaLabel}
|
||||
title={ariaLabel}
|
||||
target={newTab ? "_blank" : undefined}
|
||||
|
|
File diff suppressed because it is too large
Load diff
79
src/Utils.ts
79
src/Utils.ts
|
@ -114,7 +114,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
"version",
|
||||
"wayHandling",
|
||||
"widenFactor",
|
||||
"width",
|
||||
"width"
|
||||
]
|
||||
private static extraKeys = [
|
||||
"nl",
|
||||
|
@ -133,7 +133,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
"yes",
|
||||
"no",
|
||||
"true",
|
||||
"false",
|
||||
"false"
|
||||
]
|
||||
private static injectedDownloads = {}
|
||||
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) {
|
||||
return
|
||||
}
|
||||
DOMPurify.addHook("afterSanitizeAttributes", function (node) {
|
||||
DOMPurify.addHook("afterSanitizeAttributes", function(node) {
|
||||
// set all elements owning target to target=_blank + add noopener noreferrer
|
||||
const target = node.getAttribute("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 {
|
||||
return DOMPurify.sanitize(src, {
|
||||
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)
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -368,7 +368,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
whenDone()
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -651,7 +651,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
if (!Array.isArray(targetV)) {
|
||||
throw new Error(
|
||||
"Cannot concatenate: value to add is not an array: " +
|
||||
JSON.stringify(targetV)
|
||||
JSON.stringify(targetV)
|
||||
)
|
||||
}
|
||||
if (Array.isArray(sourceV)) {
|
||||
|
@ -659,9 +659,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
} else {
|
||||
throw new Error(
|
||||
"Could not merge concatenate " +
|
||||
JSON.stringify(sourceV) +
|
||||
" and " +
|
||||
JSON.stringify(targetV)
|
||||
JSON.stringify(sourceV) +
|
||||
" and " +
|
||||
JSON.stringify(targetV)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -922,7 +922,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
continue
|
||||
}
|
||||
const i = part.charCodeAt(0)
|
||||
result += '"' + keys[i] + '":' + part.substring(1)
|
||||
result += "\"" + keys[i] + "\":" + part.substring(1)
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -960,6 +960,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
if (!result["error"]) {
|
||||
return result
|
||||
}
|
||||
const error = result.error
|
||||
if (error.statuscode === 429 || error.statuscode === 509) {
|
||||
// rate limited
|
||||
return result
|
||||
}
|
||||
console.log(
|
||||
`Request to ${url} failed, Trying again in a moment. Attempt ${
|
||||
i + 1
|
||||
|
@ -1000,7 +1005,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
resolve({
|
||||
error: "other error: " + xhr.statusText + ", " + xhr.responseText,
|
||||
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>) =>
|
||||
reject(
|
||||
"Could not get " +
|
||||
url +
|
||||
", xhr status code is " +
|
||||
xhr.status +
|
||||
" (" +
|
||||
xhr.statusText +
|
||||
")"
|
||||
url +
|
||||
", xhr status code is " +
|
||||
xhr.status +
|
||||
" (" +
|
||||
xhr.statusText +
|
||||
")"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -1077,12 +1082,13 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
const promise =
|
||||
/*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced<T>(
|
||||
url,
|
||||
headers
|
||||
)
|
||||
url,
|
||||
headers
|
||||
)
|
||||
Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() })
|
||||
return await promise
|
||||
}
|
||||
|
||||
public static async downloadJson<T = object | []>(
|
||||
url: 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[] {
|
||||
const withDistance: [T, number][] = ts.map((t) => [
|
||||
t,
|
||||
Utils.levenshteinDistance(getName(t), reference),
|
||||
Utils.levenshteinDistance(getName(t), reference)
|
||||
])
|
||||
withDistance.sort(([_, a], [__, b]) => a - b)
|
||||
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 {
|
||||
r: Utils.percentageToNumber(match[1]),
|
||||
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 {
|
||||
r: parseInt(hex.substr(1, 1), 16),
|
||||
g: parseInt(hex.substr(2, 1), 16),
|
||||
b: parseInt(hex.substr(3, 1), 16),
|
||||
b: parseInt(hex.substr(3, 1), 16)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
r: parseInt(hex.substr(1, 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),
|
||||
column: Number(column),
|
||||
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"]
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue