forked from MapComplete/MapComplete
UX: fix #1729: ignore accents in theme search
This commit is contained in:
parent
8e73369308
commit
4eb8b086e2
2 changed files with 93 additions and 85 deletions
|
@ -118,7 +118,7 @@ export default class MoreScreen extends Combine {
|
||||||
if (search === undefined) {
|
if (search === undefined) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
search = search.toLocaleLowerCase()
|
search = Utils.RemoveDiacritics(search.toLocaleLowerCase())
|
||||||
if (search.length > 3 && layout.id.toLowerCase().indexOf(search) >= 0) {
|
if (search.length > 3 && layout.id.toLowerCase().indexOf(search) >= 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ export default class MoreScreen extends Combine {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const term = entity["*"] ?? entity[Locale.language.data]
|
const term = entity["*"] ?? entity[Locale.language.data]
|
||||||
if (term?.toLowerCase()?.indexOf(search) >= 0) {
|
if (Utils.RemoveDiacritics(term?.toLowerCase())?.indexOf(search) >= 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
174
src/Utils.ts
174
src/Utils.ts
|
@ -11,7 +11,7 @@ export class Utils {
|
||||||
public static readonly assets_path = "./assets/svg/"
|
public static readonly assets_path = "./assets/svg/"
|
||||||
public static externalDownloadFunction: (
|
public static externalDownloadFunction: (
|
||||||
url: string,
|
url: string,
|
||||||
headers?: any
|
headers?: any,
|
||||||
) => Promise<{ content: string } | { redirect: string }>
|
) => Promise<{ content: string } | { redirect: string }>
|
||||||
public static Special_visualizations_tagsToApplyHelpText = `These can either be a tag to add, such as \`amenity=fast_food\` or can use a substitution, e.g. \`addr:housenumber=$number\`.
|
public static Special_visualizations_tagsToApplyHelpText = `These can either be a tag to add, such as \`amenity=fast_food\` or can use a substitution, e.g. \`addr:housenumber=$number\`.
|
||||||
This new point will then have the tags \`amenity=fast_food\` and \`addr:housenumber\` with the value that was saved in \`number\` in the original feature.
|
This new point will then have the tags \`amenity=fast_food\` and \`addr:housenumber\` with the value that was saved in \`number\` in the original feature.
|
||||||
|
@ -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) {
|
||||||
|
@ -172,7 +172,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
*/
|
*/
|
||||||
public static ParseVisArgs(
|
public static ParseVisArgs(
|
||||||
specs: { name: string; defaultValue?: string }[],
|
specs: { name: string; defaultValue?: string }[],
|
||||||
args: string[]
|
args: string[],
|
||||||
): Record<string, string> {
|
): Record<string, string> {
|
||||||
const parsed: Record<string, string> = {}
|
const parsed: Record<string, string> = {}
|
||||||
if (args.length > specs.length) {
|
if (args.length > specs.length) {
|
||||||
|
@ -320,7 +320,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
object: any,
|
object: any,
|
||||||
name: string,
|
name: string,
|
||||||
init: () => any,
|
init: () => any,
|
||||||
whenDone?: () => void
|
whenDone?: () => void,
|
||||||
) {
|
) {
|
||||||
Object.defineProperty(object, name, {
|
Object.defineProperty(object, name, {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
|
@ -343,7 +343,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
object: any,
|
object: any,
|
||||||
name: string,
|
name: string,
|
||||||
init: () => Promise<any>,
|
init: () => Promise<any>,
|
||||||
whenDone?: () => void
|
whenDone?: () => void,
|
||||||
) {
|
) {
|
||||||
Object.defineProperty(object, name, {
|
Object.defineProperty(object, name, {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
|
@ -483,7 +483,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
public static SubstituteKeys(
|
public static SubstituteKeys(
|
||||||
txt: string | undefined,
|
txt: string | undefined,
|
||||||
tags: Record<string, any> | undefined,
|
tags: Record<string, any> | undefined,
|
||||||
useLang?: string
|
useLang?: string,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (txt === undefined) {
|
if (txt === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -519,7 +519,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
"SubstituteKeys received a BaseUIElement to substitute in - this is probably a bug and will be downcast to a string\nThe key is",
|
"SubstituteKeys received a BaseUIElement to substitute in - this is probably a bug and will be downcast to a string\nThe key is",
|
||||||
key,
|
key,
|
||||||
"\nThe value is",
|
"\nThe value is",
|
||||||
v
|
v,
|
||||||
)
|
)
|
||||||
v = v.InnerConstructElement()?.textContent
|
v = v.InnerConstructElement()?.textContent
|
||||||
}
|
}
|
||||||
|
@ -561,38 +561,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
target.push(...source)
|
target.push(...source)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively rewrites all keys from `+key`, `key+` and `=key` into `key
|
|
||||||
*
|
|
||||||
* Utils.CleanMergeObject({"condition":{"and+":["xyz"]}} // => {"condition":{"and":["xyz"]}}
|
|
||||||
* @param obj
|
|
||||||
* @constructor
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private static CleanMergeObject(obj: any) {
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
const result = []
|
|
||||||
for (const el of obj) {
|
|
||||||
result.push(Utils.CleanMergeObject(el))
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
if (typeof obj !== "object") {
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
const newObj = {}
|
|
||||||
for (let objKey in obj) {
|
|
||||||
let cleanKey = objKey
|
|
||||||
if (objKey.startsWith("+") || objKey.startsWith("=")) {
|
|
||||||
cleanKey = objKey.substring(1)
|
|
||||||
} else if (objKey.endsWith("+") || objKey.endsWith("=")) {
|
|
||||||
cleanKey = objKey.substring(0, objKey.length - 1)
|
|
||||||
}
|
|
||||||
newObj[cleanKey] = Utils.CleanMergeObject(obj[objKey])
|
|
||||||
}
|
|
||||||
return newObj
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies all key-value pairs of the source into the target. This will change the target
|
* Copies all key-value pairs of the source into the target. This will change the target
|
||||||
* If the key starts with a '+', the values of the list will be appended to the target instead of overwritten
|
* If the key starts with a '+', the values of the list will be appended to the target instead of overwritten
|
||||||
|
@ -664,7 +632,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)) {
|
||||||
|
@ -672,9 +640,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 {
|
||||||
|
@ -719,7 +687,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
path: string[],
|
path: string[],
|
||||||
object: any,
|
object: any,
|
||||||
replaceLeaf: (leaf: any, travelledPath: string[]) => any,
|
replaceLeaf: (leaf: any, travelledPath: string[]) => any,
|
||||||
travelledPath: string[] = []
|
travelledPath: string[] = [],
|
||||||
): void {
|
): void {
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
return
|
return
|
||||||
|
@ -750,7 +718,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
if (Array.isArray(sub)) {
|
if (Array.isArray(sub)) {
|
||||||
sub.forEach((el, i) =>
|
sub.forEach((el, i) =>
|
||||||
Utils.WalkPath(path.slice(1), el, replaceLeaf, [...travelledPath, head, "" + i])
|
Utils.WalkPath(path.slice(1), el, replaceLeaf, [...travelledPath, head, "" + i]),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -767,7 +735,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
path: string[],
|
path: string[],
|
||||||
object: any,
|
object: any,
|
||||||
collectedList: { leaf: any; path: string[] }[] = [],
|
collectedList: { leaf: any; path: string[] }[] = [],
|
||||||
travelledPath: string[] = []
|
travelledPath: string[] = [],
|
||||||
): { leaf: any; path: string[] }[] {
|
): { leaf: any; path: string[] }[] {
|
||||||
if (object === undefined || object === null) {
|
if (object === undefined || object === null) {
|
||||||
return collectedList
|
return collectedList
|
||||||
|
@ -797,7 +765,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
|
|
||||||
if (Array.isArray(sub)) {
|
if (Array.isArray(sub)) {
|
||||||
sub.forEach((el, i) =>
|
sub.forEach((el, i) =>
|
||||||
Utils.CollectPath(path.slice(1), el, collectedList, [...travelledPath, "" + i])
|
Utils.CollectPath(path.slice(1), el, collectedList, [...travelledPath, "" + i]),
|
||||||
)
|
)
|
||||||
return collectedList
|
return collectedList
|
||||||
}
|
}
|
||||||
|
@ -841,7 +809,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
json: any,
|
json: any,
|
||||||
f: (v: object | number | string | boolean | undefined, path: string[]) => any,
|
f: (v: object | number | string | boolean | undefined, path: string[]) => any,
|
||||||
isLeaf: (object) => boolean = undefined,
|
isLeaf: (object) => boolean = undefined,
|
||||||
path: string[] = []
|
path: string[] = [],
|
||||||
) {
|
) {
|
||||||
if (json === undefined || json === null) {
|
if (json === undefined || json === null) {
|
||||||
return f(json, path)
|
return f(json, path)
|
||||||
|
@ -880,7 +848,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
json: any,
|
json: any,
|
||||||
collect: (v: number | string | boolean | undefined, path: string[]) => any,
|
collect: (v: number | string | boolean | undefined, path: string[]) => any,
|
||||||
isLeaf: (object) => boolean = undefined,
|
isLeaf: (object) => boolean = undefined,
|
||||||
path = []
|
path = [],
|
||||||
): void {
|
): void {
|
||||||
if (json === undefined) {
|
if (json === undefined) {
|
||||||
return
|
return
|
||||||
|
@ -955,7 +923,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
|
||||||
|
@ -982,7 +950,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
url: string,
|
url: string,
|
||||||
headers?: any,
|
headers?: any,
|
||||||
method: "POST" | "GET" | "PUT" | "UPDATE" | "DELETE" | "OPTIONS" = "GET",
|
method: "POST" | "GET" | "PUT" | "UPDATE" | "DELETE" | "OPTIONS" = "GET",
|
||||||
content?: string
|
content?: string,
|
||||||
): Promise<
|
): Promise<
|
||||||
| { content: string }
|
| { content: string }
|
||||||
| { redirect: string }
|
| { redirect: string }
|
||||||
|
@ -1047,7 +1015,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
public static async downloadJsonCached(
|
public static async downloadJsonCached(
|
||||||
url: string,
|
url: string,
|
||||||
maxCacheTimeMs: number,
|
maxCacheTimeMs: number,
|
||||||
headers?: any
|
headers?: any,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const result = await Utils.downloadJsonAdvanced(url, headers)
|
const result = await Utils.downloadJsonAdvanced(url, headers)
|
||||||
if (result["content"]) {
|
if (result["content"]) {
|
||||||
|
@ -1059,7 +1027,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
public static async downloadJsonCachedAdvanced(
|
public static async downloadJsonCachedAdvanced(
|
||||||
url: string,
|
url: string,
|
||||||
maxCacheTimeMs: number,
|
maxCacheTimeMs: number,
|
||||||
headers?: any
|
headers?: any,
|
||||||
): Promise<{ content: any } | { error: string; url: string; statuscode?: number }> {
|
): Promise<{ content: any } | { error: string; url: string; statuscode?: number }> {
|
||||||
const cached = Utils._download_cache.get(url)
|
const cached = Utils._download_cache.get(url)
|
||||||
if (cached !== undefined) {
|
if (cached !== undefined) {
|
||||||
|
@ -1069,9 +1037,9 @@ 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(
|
/*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced(
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -1086,7 +1054,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
|
|
||||||
public static async downloadJsonAdvanced(
|
public static async downloadJsonAdvanced(
|
||||||
url: string,
|
url: string,
|
||||||
headers?: any
|
headers?: any,
|
||||||
): Promise<{ content: any } | { error: string; url: string; statuscode?: number }> {
|
): Promise<{ content: any } | { error: string; url: string; statuscode?: number }> {
|
||||||
const injected = Utils.injectedDownloads[url]
|
const injected = Utils.injectedDownloads[url]
|
||||||
if (injected !== undefined) {
|
if (injected !== undefined) {
|
||||||
|
@ -1095,7 +1063,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
const result = await Utils.downloadAdvanced(
|
const result = await Utils.downloadAdvanced(
|
||||||
url,
|
url,
|
||||||
Utils.Merge({ accept: "application/json" }, headers ?? {})
|
Utils.Merge({ accept: "application/json" }, headers ?? {}),
|
||||||
)
|
)
|
||||||
if (result["error"] !== undefined) {
|
if (result["error"] !== undefined) {
|
||||||
return <{ error: string; url: string; statuscode?: number }>result
|
return <{ error: string; url: string; statuscode?: number }>result
|
||||||
|
@ -1115,7 +1083,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
"due to",
|
"due to",
|
||||||
e,
|
e,
|
||||||
"\n",
|
"\n",
|
||||||
e.stack
|
e.stack,
|
||||||
)
|
)
|
||||||
return { error: "malformed", url }
|
return { error: "malformed", url }
|
||||||
}
|
}
|
||||||
|
@ -1136,7 +1104,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
| "{gpx=application/gpx+xml}"
|
| "{gpx=application/gpx+xml}"
|
||||||
| "application/json"
|
| "application/json"
|
||||||
| "image/png"
|
| "image/png"
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
const element = document.createElement("a")
|
const element = document.createElement("a")
|
||||||
let file
|
let file
|
||||||
|
@ -1240,7 +1208,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
false
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1324,7 +1292,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
public static sortedByLevenshteinDistance<T>(
|
public static sortedByLevenshteinDistance<T>(
|
||||||
reference: string,
|
reference: string,
|
||||||
ts: T[],
|
ts: T[],
|
||||||
getName: (t: T) => string
|
getName: (t: T) => string,
|
||||||
): T[] {
|
): T[] {
|
||||||
const withDistance: [T, number][] = ts.map((t) => [
|
const withDistance: [T, number][] = ts.map((t) => [
|
||||||
t,
|
t,
|
||||||
|
@ -1350,7 +1318,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
track[j][i] = Math.min(
|
track[j][i] = Math.min(
|
||||||
track[j][i - 1] + 1, // deletion
|
track[j][i - 1] + 1, // deletion
|
||||||
track[j - 1][i] + 1, // insertion
|
track[j - 1][i] + 1, // insertion
|
||||||
track[j - 1][i - 1] + indicator // substitution
|
track[j - 1][i - 1] + indicator, // substitution
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1359,7 +1327,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
|
|
||||||
public static MapToObj<V, T>(
|
public static MapToObj<V, T>(
|
||||||
d: Map<string, V>,
|
d: Map<string, V>,
|
||||||
onValue: (t: V, key: string) => T
|
onValue: (t: V, key: string) => T,
|
||||||
): Record<string, T> {
|
): Record<string, T> {
|
||||||
const o = {}
|
const o = {}
|
||||||
const keys = Array.from(d.keys())
|
const keys = Array.from(d.keys())
|
||||||
|
@ -1376,7 +1344,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
* Utils.TransposeMap({"a" : ["b", "c"], "x" : ["b", "y"]}) // => {"b" : ["a", "x"], "c" : ["a"], "y" : ["x"]}
|
* Utils.TransposeMap({"a" : ["b", "c"], "x" : ["b", "y"]}) // => {"b" : ["a", "x"], "c" : ["a"], "y" : ["x"]}
|
||||||
*/
|
*/
|
||||||
public static TransposeMap<K extends string, V extends string>(
|
public static TransposeMap<K extends string, V extends string>(
|
||||||
d: Record<K, V[]>
|
d: Record<K, V[]>,
|
||||||
): Record<V, K[]> {
|
): Record<V, K[]> {
|
||||||
const newD: Record<V, K[]> = <any>{}
|
const newD: Record<V, K[]> = <any>{}
|
||||||
|
|
||||||
|
@ -1450,7 +1418,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
|
|
||||||
public static asDict(
|
public static asDict(
|
||||||
tags: { key: string; value: string | number }[]
|
tags: { key: string; value: string | number }[],
|
||||||
): Map<string, string | number> {
|
): Map<string, string | number> {
|
||||||
const d = new Map<string, string | number>()
|
const d = new Map<string, string | number>()
|
||||||
|
|
||||||
|
@ -1491,21 +1459,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
element.scrollIntoView({ behavior: "smooth", block: "nearest" })
|
element.scrollIntoView({ behavior: "smooth", block: "nearest" })
|
||||||
}
|
}
|
||||||
|
|
||||||
private static findParentWithScrolling(element: HTMLBaseElement): HTMLBaseElement {
|
|
||||||
// Check if the element itself has scrolling
|
|
||||||
if (element.scrollHeight > element.clientHeight) {
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the element does not have scrolling, check if it has a parent element
|
|
||||||
if (!element.parentElement) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the element has a parent, repeat the process for the parent element
|
|
||||||
return Utils.findParentWithScrolling(<HTMLBaseElement>element.parentElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the contents of `a` are the same (and in the same order) as `b`.
|
* Returns true if the contents of `a` are the same (and in the same order) as `b`.
|
||||||
* Might have false negatives in some cases
|
* Might have false negatives in some cases
|
||||||
|
@ -1572,7 +1525,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static splitIntoSubstitutionParts(
|
public static splitIntoSubstitutionParts(
|
||||||
template: string
|
template: string,
|
||||||
): ({ message: string } | { subs: string })[] {
|
): ({ message: string } | { subs: string })[] {
|
||||||
const preparts = template.split("{")
|
const preparts = template.split("{")
|
||||||
const spec: ({ message: string } | { subs: string })[] = []
|
const spec: ({ message: string } | { subs: string })[] = []
|
||||||
|
@ -1633,6 +1586,13 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RemoveDiacritics(str?: string): string {
|
||||||
|
if(!str){
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return str.normalize("NFD").replace(/\p{Diacritic}/gu, "")
|
||||||
|
}
|
||||||
|
|
||||||
public static randomString(length: number): string {
|
public static randomString(length: number): string {
|
||||||
let result = ""
|
let result = ""
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
|
@ -1641,9 +1601,57 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively rewrites all keys from `+key`, `key+` and `=key` into `key
|
||||||
|
*
|
||||||
|
* Utils.CleanMergeObject({"condition":{"and+":["xyz"]}} // => {"condition":{"and":["xyz"]}}
|
||||||
|
* @param obj
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private static CleanMergeObject(obj: any) {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
const result = []
|
||||||
|
for (const el of obj) {
|
||||||
|
result.push(Utils.CleanMergeObject(el))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if (typeof obj !== "object") {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
const newObj = {}
|
||||||
|
for (let objKey in obj) {
|
||||||
|
let cleanKey = objKey
|
||||||
|
if (objKey.startsWith("+") || objKey.startsWith("=")) {
|
||||||
|
cleanKey = objKey.substring(1)
|
||||||
|
} else if (objKey.endsWith("+") || objKey.endsWith("=")) {
|
||||||
|
cleanKey = objKey.substring(0, objKey.length - 1)
|
||||||
|
}
|
||||||
|
newObj[cleanKey] = Utils.CleanMergeObject(obj[objKey])
|
||||||
|
}
|
||||||
|
return newObj
|
||||||
|
}
|
||||||
|
|
||||||
|
private static findParentWithScrolling(element: HTMLBaseElement): HTMLBaseElement {
|
||||||
|
// Check if the element itself has scrolling
|
||||||
|
if (element.scrollHeight > element.clientHeight) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the element does not have scrolling, check if it has a parent element
|
||||||
|
if (!element.parentElement) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the element has a parent, repeat the process for the parent element
|
||||||
|
return Utils.findParentWithScrolling(<HTMLBaseElement>element.parentElement)
|
||||||
|
}
|
||||||
|
|
||||||
private static colorDiff(
|
private static colorDiff(
|
||||||
c0: { r: number; g: number; b: number },
|
c0: { r: number; g: number; b: number },
|
||||||
c1: { r: number; g: number; b: number }
|
c1: { r: number; g: number; b: number },
|
||||||
) {
|
) {
|
||||||
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b)
|
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue