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,10 +193,89 @@ 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[] = [
|
||||||
|
|
||||||
ValidatedTextField.tp(
|
ValidatedTextField.tp(
|
||||||
"string",
|
"string",
|
||||||
"A basic string"),
|
"A basic string"),
|
||||||
|
@ -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…
Reference in a new issue