forked from MapComplete/MapComplete
		
	Improve URL handling: typing http(s) is not needed anymore for URLs
This commit is contained in:
		
							parent
							
								
									c6b1ef4a71
								
							
						
					
					
						commit
						057b4a3192
					
				
					 2 changed files with 111 additions and 45 deletions
				
			
		| 
						 | 
					@ -22,12 +22,18 @@ import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
 | 
				
			||||||
import Table from "../Base/Table";
 | 
					import Table from "../Base/Table";
 | 
				
			||||||
import Combine from "../Base/Combine";
 | 
					import Combine from "../Base/Combine";
 | 
				
			||||||
import Title from "../Base/Title";
 | 
					import Title from "../Base/Title";
 | 
				
			||||||
 | 
					import InputElementMap from "./InputElementMap";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TextFieldDef {
 | 
					interface TextFieldDef {
 | 
				
			||||||
    name: string,
 | 
					    name: string,
 | 
				
			||||||
    explanation: string,
 | 
					    explanation: string,
 | 
				
			||||||
    isValid: ((s: string, country?: () => string) => boolean),
 | 
					    isValid: ((s: string, country?: () => string) => boolean),
 | 
				
			||||||
    reformat?: ((s: string, country?: () => string) => string),
 | 
					    reformat?: ((s: string, country?: () => string) => string),
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Modification to make before the string is uploaded to OSM
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    postprocess?: (s: string) => string;
 | 
				
			||||||
 | 
					    undoPostprocess?: (s: string) => string;
 | 
				
			||||||
    inputHelper?: (value: UIEventSource<string>, options?: {
 | 
					    inputHelper?: (value: UIEventSource<string>, options?: {
 | 
				
			||||||
        location: [number, number],
 | 
					        location: [number, number],
 | 
				
			||||||
        mapBackgroundLayer?: UIEventSource<any>,
 | 
					        mapBackgroundLayer?: UIEventSource<any>,
 | 
				
			||||||
| 
						 | 
					@ -187,6 +193,85 @@ class OpeningHoursTextField implements TextFieldDef {
 | 
				
			||||||
        return new OpeningHoursInput(value, prefix, postfix)
 | 
					        return new OpeningHoursInput(value, prefix, postfix)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UrlTextfieldDef implements TextFieldDef {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = "url"
 | 
				
			||||||
 | 
					    explanation = "The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user"
 | 
				
			||||||
 | 
					    inputmode: "url"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    postprocess(str: string) {
 | 
				
			||||||
 | 
					        if (str === undefined) {
 | 
				
			||||||
 | 
					            return undefined
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!str.startsWith("http://") || !str.startsWith("https://")) {
 | 
				
			||||||
 | 
					            return "https://" + str
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return str;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    undoPostprocess(str: string) {
 | 
				
			||||||
 | 
					        if (str === undefined) {
 | 
				
			||||||
 | 
					            return undefined
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (str.startsWith("http://")) {
 | 
				
			||||||
 | 
					            return str.substr("http://".length)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (str.startsWith("https://")) {
 | 
				
			||||||
 | 
					            return str.substr("https://".length)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return str;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    reformat(str: string): string {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            let url: URL
 | 
				
			||||||
 | 
					            str = str.toLowerCase()
 | 
				
			||||||
 | 
					            if (!str.startsWith("http://") && !str.startsWith("https://") && !str.startsWith("http:")) {
 | 
				
			||||||
 | 
					                url = new URL("https://" + str)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                url = new URL(str);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const blacklistedTrackingParams = [
 | 
				
			||||||
 | 
					                "fbclid",// Oh god, how I hate the fbclid. Let it burn, burn in hell!
 | 
				
			||||||
 | 
					                "gclid",
 | 
				
			||||||
 | 
					                "cmpid", "agid", "utm", "utm_source", "utm_medium",
 | 
				
			||||||
 | 
					                "campaignid","campaign","AdGroupId","AdGroup","TargetId","msclkid"]
 | 
				
			||||||
 | 
					            for (const dontLike of blacklistedTrackingParams) {
 | 
				
			||||||
 | 
					                url.searchParams.delete(dontLike.toLowerCase()  )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            let cleaned = url.toString();
 | 
				
			||||||
 | 
					            if (cleaned.endsWith("/") && !str.endsWith("/")) {
 | 
				
			||||||
 | 
					                // Do not add a trailing '/' if it wasn't typed originally
 | 
				
			||||||
 | 
					                cleaned = cleaned.substr(0, cleaned.length - 1)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (cleaned.startsWith("https://")) {
 | 
				
			||||||
 | 
					                cleaned = cleaned.substr("https://".length)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return cleaned;
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            console.error(e)
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isValid(str: string): boolean {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if (!str.startsWith("http://") && !str.startsWith("https://") &&
 | 
				
			||||||
 | 
					                !str.startsWith("http:")) {
 | 
				
			||||||
 | 
					                str = "https://" + str
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const url = new URL(str);
 | 
				
			||||||
 | 
					            const dotIndex = url.host.indexOf(".")
 | 
				
			||||||
 | 
					            return dotIndex > 0 && url.host[url.host.length - 1 ] !== ".";
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class ValidatedTextField {
 | 
					export default class ValidatedTextField {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static tpList: TextFieldDef[] = [
 | 
					    public static tpList: TextFieldDef[] = [
 | 
				
			||||||
| 
						 | 
					@ -350,54 +435,23 @@ export default class ValidatedTextField {
 | 
				
			||||||
            "email",
 | 
					            "email",
 | 
				
			||||||
            "An email adress",
 | 
					            "An email adress",
 | 
				
			||||||
            (str) => {
 | 
					            (str) => {
 | 
				
			||||||
                if(str.startsWith("mailto:")){
 | 
					                if (str.startsWith("mailto:")) {
 | 
				
			||||||
                    str = str.substring("mailto:".length)
 | 
					                    str = str.substring("mailto:".length)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return EmailValidator.validate(str);
 | 
					                return EmailValidator.validate(str);
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            str => {
 | 
					            str => {
 | 
				
			||||||
                if(str === undefined){return undefined}
 | 
					                if (str === undefined) {
 | 
				
			||||||
                if(str.startsWith("mailto:")){
 | 
					                    return undefined
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (str.startsWith("mailto:")) {
 | 
				
			||||||
                    str = str.substring("mailto:".length)
 | 
					                    str = str.substring("mailto:".length)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return str;
 | 
					                return str;
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            undefined,
 | 
					            undefined,
 | 
				
			||||||
            "email"),
 | 
					            "email"),
 | 
				
			||||||
        ValidatedTextField.tp(
 | 
					        new UrlTextfieldDef(),
 | 
				
			||||||
            "url",
 | 
					 | 
				
			||||||
            "A url",
 | 
					 | 
				
			||||||
            (str) => {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    new URL(str);
 | 
					 | 
				
			||||||
                    return true;
 | 
					 | 
				
			||||||
                } catch (e) {
 | 
					 | 
				
			||||||
                    return false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (str) => {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    const url = new URL(str);
 | 
					 | 
				
			||||||
                    const blacklistedTrackingParams = [
 | 
					 | 
				
			||||||
                        "fbclid",// Oh god, how I hate the fbclid. Let it burn, burn in hell!
 | 
					 | 
				
			||||||
                        "gclid",
 | 
					 | 
				
			||||||
                        "cmpid", "agid", "utm", "utm_source", "utm_medium"]
 | 
					 | 
				
			||||||
                    for (const dontLike of blacklistedTrackingParams) {
 | 
					 | 
				
			||||||
                        url.searchParams.delete(dontLike)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    let cleaned = url.toString();
 | 
					 | 
				
			||||||
                    if (cleaned.endsWith("/") && !str.endsWith("/")) {
 | 
					 | 
				
			||||||
                        // Do not add a trailing '/' if it wasn't typed originally
 | 
					 | 
				
			||||||
                        cleaned = cleaned.substr(0, cleaned.length - 1)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return cleaned;
 | 
					 | 
				
			||||||
                } catch (e) {
 | 
					 | 
				
			||||||
                    console.error(e)
 | 
					 | 
				
			||||||
                    return undefined;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            undefined,
 | 
					 | 
				
			||||||
            "url"),
 | 
					 | 
				
			||||||
        ValidatedTextField.tp(
 | 
					        ValidatedTextField.tp(
 | 
				
			||||||
            "phone",
 | 
					            "phone",
 | 
				
			||||||
            "A phone number",
 | 
					            "A phone number",
 | 
				
			||||||
| 
						 | 
					@ -405,13 +459,13 @@ export default class ValidatedTextField {
 | 
				
			||||||
                if (str === undefined) {
 | 
					                if (str === undefined) {
 | 
				
			||||||
                    return false;
 | 
					                    return false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if(str.startsWith("tel:")){
 | 
					                if (str.startsWith("tel:")) {
 | 
				
			||||||
                    str = str.substring("tel:".length)
 | 
					                    str = str.substring("tel:".length)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return parsePhoneNumberFromString(str, (country())?.toUpperCase() as any)?.isValid() ?? false
 | 
					                return parsePhoneNumberFromString(str, (country())?.toUpperCase() as any)?.isValid() ?? false
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            (str, country: () => string) => {
 | 
					            (str, country: () => string) => {
 | 
				
			||||||
                if(str.startsWith("tel:")){
 | 
					                if (str.startsWith("tel:")) {
 | 
				
			||||||
                    str = str.substring("tel:".length)
 | 
					                    str = str.substring("tel:".length)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return parsePhoneNumberFromString(str, (country())?.toUpperCase() as any).formatInternational();
 | 
					                return parsePhoneNumberFromString(str, (country())?.toUpperCase() as any).formatInternational();
 | 
				
			||||||
| 
						 | 
					@ -491,7 +545,7 @@ export default class ValidatedTextField {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        options.inputMode = tp.inputmode;
 | 
					        options.inputMode = tp.inputmode;
 | 
				
			||||||
        if(tp.inputmode === "text") {
 | 
					        if (tp.inputmode === "text") {
 | 
				
			||||||
            options.htmlType = "area"
 | 
					            options.htmlType = "area"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -568,13 +622,21 @@ export default class ValidatedTextField {
 | 
				
			||||||
                a => [a, a]
 | 
					                a => [a, a]
 | 
				
			||||||
            ).SetClass("block w-full");
 | 
					            ).SetClass("block w-full");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (tp.postprocess !== undefined) {
 | 
				
			||||||
 | 
					            input = new InputElementMap<string, string>(input,
 | 
				
			||||||
 | 
					                (a, b) => a === b,
 | 
				
			||||||
 | 
					                tp.postprocess,
 | 
				
			||||||
 | 
					                tp.undoPostprocess
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return input;
 | 
					        return input;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static HelpText(): BaseUIElement {
 | 
					    public static HelpText(): BaseUIElement {
 | 
				
			||||||
        const explanations : BaseUIElement[]= 
 | 
					        const explanations: BaseUIElement[] =
 | 
				
			||||||
            ValidatedTextField.tpList.map(type =>
 | 
					            ValidatedTextField.tpList.map(type =>
 | 
				
			||||||
                new Combine([new Title(type.name,3), type.explanation]).SetClass("flex flex-col"))
 | 
					                new Combine([new Title(type.name, 3), type.explanation]).SetClass("flex flex-col"))
 | 
				
			||||||
        return new Combine([
 | 
					        return new Combine([
 | 
				
			||||||
            new Title("Available types for text fields", 1),
 | 
					            new Title("Available types for text fields", 1),
 | 
				
			||||||
            "The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them",
 | 
					            "The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								test.ts
									
										
									
									
									
								
							| 
						 | 
					@ -1,2 +1,6 @@
 | 
				
			||||||
import NewNoteUi from "./UI/Popup/NewNoteUi";
 | 
					import ValidatedTextField from "./UI/Input/ValidatedTextField";
 | 
				
			||||||
import {UIEventSource} from "./Logic/UIEventSource";
 | 
					import {VariableUiElement} from "./UI/Base/VariableUIElement";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tf = ValidatedTextField.InputForType("url")
 | 
				
			||||||
 | 
					tf.AttachTo("maindiv")
 | 
				
			||||||
 | 
					new VariableUiElement(tf.GetValue()).AttachTo("extradiv")
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue